Skip to content

Pin renderedstep/engine#95's head (6247b4b): stand a floor's guards on its way down, and birth its people at its level - #370

Merged
calebl merged 2 commits into
mainfrom
fm/ta-floors-s3
Oct 30, 2026
Merged

calebl merged 2 commits into
mainfrom
fm/ta-floors-s3

Conversation

@calebl

@calebl calebl commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Stage 3 of the dungeon plan, server half: who stands on a floor. This PR pins renderedstep/engine#95 at its head (6247b4b9df29d202046bba845cdfee529033fc3b) and does every item in that PR's "What the server pin PR must change" list.

  • A guard the world posts to a floor is stood by the engine in the floor's way-down room when it lays the floor out. This happens once, for the world (VISION principle 7).
  • Everybody born on a floor that names a monster_level is born at that level.

A world with neither key plays exactly as before, and no checked-in story world turns either one on. No prompt changed and no model was called.

What changed

  1. Migration 20261030230000_add_guards_and_monster_level_to_floors.rb uses the engine's version and declaration. db/schema.rb moves to 2026_10_30_230000; I hand-placed the lines so the existing column order stays as it was. No later migration is on origin/main (7d6324c).
  2. Models
    • Character belongs_to :posted_to (a Location, optional).
    • Location has_many :posted_guards, nullified when the place is deleted.
    • Floor validates monster_level in Character::LEVELS, nil allowed.
    • Factories gain the :posted character trait and the :deep floor trait. Neither rolls dice.
    • Model tests added.
  3. WorldSeed::Loader reads floor: { monster_level, guards }.
    • Refuses every case the engine PR lists, each with a test:
      • a non-Integer or out-of-range level;
      • guards that is not a list of names;
      • an unknown name;
      • a guard who is not absent: true or who has a location;
      • a guard with no stats;
      • the protagonist or a companion as a guard;
      • one character posted to two floors.
    • Writes monster_level and posted_to in both directions on every load.
    • Re-seed rule: once the guard's floor has rooms, the loader leaves the guard's location_id, deliberately_absent, x and y as they are. Before layout, absent: true is written as for anybody else.
  4. WorldSeed::Exporter writes monster_level and guards (full names, in id order). Each guard is written as absent: true, with no location or spot and with their stats, wherever the layout stood them. The round-trip test lays the floor out first. The floor-rooms warning no longer counts posted guards.
  5. Character::Registry gives a person born on a floor, or in a room of one, that floor's monster_level when it names one. Otherwise they get StatBlock::STARTING_LEVEL. The hit die and abilities are rolled as before (realize.rs admit_new).
  6. Story::Doctor has two new findings. Both are warning/manual and are reported, never repaired:
    • guard_posted_to_no_floor;
    • guard_nowhere_on_a_laid_out_floor.
  7. EngineSweep::Invariants#cast_unmoved now accepts a guard the file keeps nowhere when they stand in a room of the floor they are posted to, at Location::Placement.in_the_world's spot.
    • I used that check rather than rolled_in_the_world?, because rolled_in_the_world? also requires laid_out_alone?, which a room of a floor never is.
    • Removing the exception makes a-guard-waits-at-the-stairs fail with "Brask the Stairwarden is in The Upper Floor room 2 at 9,3 and the file says nowhere", so the check is exercised.
    • stat_blocks_unmoved is unchanged.
  8. Copies of a world
    • Playthrough::SqlitePackage copies SELECT * with ids kept, so posted_to_id already comes along; there is now a test for it.
    • Story::Fork and Story::Snapshot go through the exporter and loader, so a fork's guard is posted to the fork's own floor (test).
    • Location::OneRoomFold::REFERENCES gains characters.posted_to_id. Its schema-walking test requires that.
  9. Sweep
    • Worlds a-guarded-stair and a-deeper-floor, and scripts a-guard-waits-at-the-stairs and a-deeper-floor-births-stronger-people, copied from the engine PR body with their comments and why: notes.
    • Goldens vendored, and test/engine_parity/<script>.checks.json written for both.
    • The engine's parity/records/ files are not vendored in this repository, so there was nothing to copy for the four records files.
    • test/ruby_loop_coverage.yml does not list scripts, so it is unchanged.
    • test/engine_vectors/{plan,kept_requests}.json were re-exported. The only change is that the recorded character rows gain "posted_to_id": null.
  10. Pin: ext/renderedstep/Cargo.toml and Cargo.lock now point at 6247b4b.

Gates (both provider keys unset)

  • bin/rails test: 5447 runs, 0 failures, 0 errors. This includes the game sweep and the engine-vectors test.
  • bin/rails engine:rust_gates: PASSED, 108 scripts: goldens, invariants, doctor and audit.
  • bin/rails engine:vendored: VENDORED, byte for byte against 6247b4b.
  • bin/rails engine:kept_requests: the engine reproduces every golden vector and kept request.
  • bundle exec rubocop: no offenses. bin/rails zeitwerk:check: all good. bin/brakeman: no warnings.

Follow-up on the engine side

The engine PR asks for parity/worlds/a-guarded-stair.sql and a-deeper-floor.sql to be rebuilt from this branch's loader, now that it reads guards: and monster_level:. That happens in the engine repository and is not done here. After engine#95 merges, this PR re-pins to the merge commit.

…n its way down, and birth its people at its level

Stage 3 of the dungeon plan, server half ("Who stands on a floor"). A guard a
world posts to a floor is stood by the engine in the floor's way-down room
when it lays the floor out, once, for the world (VISION principle 7); and
everybody born on a floor that names a `monster_level` is born at that level.
A world with neither key plays exactly as before; no checked-in story world
turns either on.

- Migration 20261030230000 AddGuardsAndMonsterLevelToFloors, the engine's
  version and declaration (its store holds every fact of it in
  OPTIONAL_COLUMNS). schema.rb moves to 2026_10_30_230000.
- Character belongs_to :posted_to; Location has_many :posted_guards
  (nullify); Floor validates monster_level in Character::LEVELS. Factories
  gain a :posted character trait and a :deep floor trait, neither rolling.
- WorldSeed::Loader reads `floor: { monster_level, guards }`, refuses every
  case the engine PR lists, writes monster_level and posted_to in both
  directions, and leaves a guard's location, absence and spot alone once
  their floor has rooms.
- WorldSeed::Exporter writes monster_level and guards (full names, id order),
  and writes each guard `absent: true`, nowhere, with stats.
- Character::Registry births a person on a floor, or in a room of one, at
  its monster_level: the engine's realize.rs admit_new.
- Story::Doctor reports guard_posted_to_no_floor and
  guard_nowhere_on_a_laid_out_floor (manual, never repaired).
- EngineSweep::Invariants#cast_unmoved accepts a posted guard the layout
  stood in a room of their floor at the world's spot.
- Location::OneRoomFold moves characters.posted_to_id with the other
  references. SqlitePackage, Fork and Snapshot carry both facts (tests).
- Sweep: worlds a-guarded-stair and a-deeper-floor, scripts
  a-guard-waits-at-the-stairs and a-deeper-floor-births-stronger-people,
  goldens vendored and checks files written. test/engine_vectors re-exported:
  the recorded character rows gain posted_to_id.
calebl added a commit to renderedstep/engine that referenced this pull request Oct 30, 2026
…renderedstep/server 6b85c8f

The game's pin PR (renderedstep/server#370) reads guards: and
monster_level:, so both dumps are now the ones its own loader writes from
a schema load: posted_to_id and monster_level are written by the loader
rather than by hand. The rows are the same; only timestamps and the order
the characters table and its constraints are dumped in moved. No golden
or records file moves. The test that rebuilds characters from before the
migration now cuts out only the posting's key, wherever it is listed.
98cec5f adds to the head pinned before only the engine's CI, its own tests,
and parity/worlds/a-guarded-stair.sql and a-deeper-floor.sql rebuilt from
this branch's loader (6b85c8f). No golden, script or vector portion changed:
bin/rails engine:vendored passes, engine:rust_gates passes all 108 scripts.
@calebl
calebl merged commit 0cd25ed into main Oct 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant