Skip to content

Pin renderedstep/engine#94's head (9a665dd): lay out a floor to its own count, and keep a moved doorway's lock - #369

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

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

Conversation

@calebl

@calebl calebl commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Stage 2 of the dungeon plan, server half: floor layout. This is the pin PR for renderedstep/engine#94 (head 9a665dd), and it does every item of that PR's "What the server pin PR must change". A place whose world gives it a floor: is laid out by the engine as exactly that many rooms on one storey. That happens the first time anybody reaches it, and the floor is then left alone (VISION principle 7). A world with no floor: plays exactly as before. No checked-in story world turns a floor on; only the new sweep world does.

What changed

  1. Migration 20261030200000_create_floors.rb, with the engine's version and declaration. db/schema.rb moves to 2026_10_30_200000.
  2. Floor model, with a model test and a factory that rolls no dice.
    • Floor belongs_to :location and :down_location; Location has_one :floor, dependent: :destroy.
    • Validations: rooms in 1..1000, and at least 2 with a way down. danger and hazard come from Location::Parameters' tables. The way down is another floor of the same story, never the floor itself.
    • Location's kind also accepts a Location::Kind::BUILDINGS word, but only on a floor. The floor is looked up only for such a word.
    • Floor.room_count, .grid and .footprint match the engine's floor.rs.
  3. Loader.
    • floor: { rooms, danger, hazard, down } writes every key in both directions on every load: an absent key writes nil, and removing floor: deletes the row.
    • While the floor has no rooms, the loader writes its footprint (6 × columns by 6 × rows). A laid-out floor's footprint and rooms are never rewritten; a re-seed changes the row only.
    • It refuses every case the engine PR lists: an unknown key under floor:; a bad rooms, or rooms < 2 with down; a word outside the tables; a down that is not a floor of this file, is the floor itself, has no connection joining it, or makes a cycle; a floor with a box column or a detail_level other than stub; a location whose parent is a floor; a floor whose kind: is not a building word.
  4. Exporter.
    • Writes floor: {…}.compact, with down by name, and omits the floor's width, depth and rooms.
    • A doorway onto a room of a floor is written onto the floor, which is where the file had it. Doorways between rooms of one floor are not written.
    • A warning names what a laid-out floor held: its rooms and the things and people in them.
    • Round-trip tests cover both an unlaid floor and a laid-out one.
  5. Doctor: report only, never repair, and each code has a test.
    • floor_laid_out_to_another_count
    • floor_going_down_to_no_floor
    • floor_with_a_hand_drawn_room: a room under a floor that is unplaced, off storey 0, or outside the floor's footprint.
    • floor_with_no_way_down
    • floor_way_down_on_its_entry
  6. Location::Generator#open_the_way_in! carry-over.
    • A moved doorway keeps each side's barrier and key. The hazard stays only on the side that had it, so a one-way hazard keeps its direction. A side that was never written takes the other side's barrier and key, with no hazard.
    • Every game's passages are re-made on the new row with their ids kept, and tolls are re-pointed to it. A dropped doorway leaves its tolls naming no doorway; before this change, a toll on the old row made the delete fail its foreign key.
    • Tests cover a keyed doorway, a one-way hazard, a half-written doorway, a passage and a toll.
    • The Ruby generator never lays a floor out, so there is no way-down routing here; the method's header says so.
  7. Copies of a world.
    • Playthrough::SqlitePackage copies floors.
    • Story::Fork and Story::Snapshot carry floors through the exporter and loader, and a fork test covers it.
    • Location::OneRoomFold moves both floors columns, which its schema test requires, and refuses to fold a floor.
  8. Sweep.
    • The world two-floors-and-a-key and the scripts a-floor-laid-out-to-its-count and a-keyed-stair-onto-a-place-stays-keyed are copied with their comments and why: notes.
    • Their goldens are vendored, and engine:checks wrote their checks files. Neither checks file includes any floor finding.
    • Two changes were needed so the invariants hold:
      • EngineSweep::Walk now records a born door's hazard (hazard_from), because a doorway moved during realization now keeps its hazard.
      • geometry_unmoved reads a floor's footprint as the loader derives it.
    • test/ruby_loop_coverage.yml lists tests, not scripts, so it is unchanged.
  9. Pin: ext/renderedstep/Cargo.toml and Cargo.lock now point at 9a665dd95e71972c98f59aa7915e59ce7400225a. When the engine PR merges, I'll move the pin to its merge commit.

The engine's world dump

I rebuilt parity/worlds/two-floors-and-a-key.sql from this branch's loader, following the engine's parity/README.md (schema:load, pin_ids!, load_world!, .dump). Its rows match the engine PR's dump exactly once timestamps are masked, including both floors rows and the 12×12 and 12×6 footprints. The only differences are statement order (schema:load against migrate) and the schema_sha1. Whoever updates the engine PR can take the file from this branch's loader.

Gates

All gates ran with OPENROUTER_API_KEY and TYPESAFE_API_KEY unset, on the extension built at the new pin:

  • bin/rails test: 5436 runs, 0 failures, 0 errors. This includes game:sweep.
  • bin/rails engine:rust_gates: PASSED for 106 scripts (goldens, invariants, doctor and audit).
  • bin/rails engine:vendored: VENDORED. The goldens, scripts and engine vector portions match 9a665dd byte for byte.
  • bin/rails engine:vectors: no change to test/engine_vectors/.
  • bundle exec rubocop: clean.
  • bin/rails zeitwerk:check: clean.
  • bin/brakeman: no warnings.

No migration collision with main (latest there is 20261030120000). No prompt changed, and no model was called. A post-update step isn't needed: bin/update already migrates and rebuilds the extension when the pin moves.

Out of scope: guards and monster_level, the dungeon world file, other stages, and any prompt change.

…wn count, and keep a moved doorway's lock

Stage 2 of the dungeon plan, server half ("Floor layout"). A place its world
gives a `floor:` is laid out by the engine as exactly that many rooms on one
storey, the first time anybody reaches it, and then left alone (VISION
principle 7). A world with no `floor:` plays exactly as before; no checked-in
story world turns one on.

- Migration 20261030200000 CreateFloors, the engine's version and
  declaration (its store holds every fact of it in OPTIONAL_SHAPES).
- Floor model: rooms 1..1000 (2+ with a way down), danger and hazard from
  Location::Parameters' tables, a way down that is another floor of the
  story. Location has_one :floor, and a floor may carry a building's kind.
  Floor.room_count / grid / footprint are the engine's floor.rs.
- WorldSeed::Loader reads `floor: { rooms, danger, hazard, down }`, every key
  in both directions, derives the footprint while the floor has no rooms, and
  refuses every case the engine PR lists. A re-seed after layout changes the
  row only.
- WorldSeed::Exporter writes `floor:` back, without the floor's footprint or
  rooms; a doorway onto a room of a floor is written onto the floor.
- Story::Doctor reports (never repairs) a floor laid out to another count, a
  way down that is no floor, a hand-drawn room, and a missing way down or one
  on the entry.
- Location::Generator#open_the_way_in! keeps a moved doorway's barrier, key,
  one-way hazard, every game's passages (ids kept) and tolls, as the engine's
  open_the_way_in now does.
- Playthrough::SqlitePackage copies floors; Story::Fork and Story::Snapshot
  carry them through the exporter and loader. Location::OneRoomFold leaves a
  floor alone.
- Sweep: world two-floors-and-a-key and scripts a-floor-laid-out-to-its-count
  and a-keyed-stair-onto-a-place-stays-keyed, goldens vendored and checks
  files written. The walk records a born door's hazard and the invariants
  read a floor's derived footprint.
calebl added a commit to renderedstep/engine that referenced this pull request Oct 30, 2026
…erver 5678109

The game's pin PR (renderedstep/server#369) reads the floor keys, so the
dump is now the one its own loader writes, floors rows, footprints and
kind included. The rows are the same; only timestamps and the order the
floors table and its constraints are dumped in moved. No golden or
records file moves.
The merge adds only the engine's CI, its fight simulator, its README and
the two-floors-and-a-key world dump rebuilt from this branch's loader to
the head pinned before; no golden, script or vector portion this
repository vendors changed.
@calebl
calebl merged commit 7d6324c 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