Skip to content

Grow an overdue arc step only in ground nobody has written - #96

Merged
calebl merged 2 commits into
mainfrom
fm/ta-quest-deadline-generated-once
Oct 30, 2026
Merged

calebl merged 2 commits into
mainfrom
fm/ta-quest-deadline-generated-once

Conversation

@calebl

@calebl calebl commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

VISION principle 7 says the engine does not change the world after it has generated it. The quest deadline broke that rule. When an overdue story goal came due, it hung a new doorway on a room the world had already written (open_the_door off deadline::anchor, the deepest reachable room), and it could drop the goal's thing onto that room's floor. This is section 8, rows 1 and 2, of the dungeon-stage4-c2c audit.

The deadline now grows what the goal needs only in ground nobody has written yet:

  • Where (deadline::way_on). It picks the deepest room the party can reach that is still unwritten. The order is the old one: lowest storey, then farthest, then lowest id, among rooms that are not places and have a doorway to spare. That is "a doorway the world already has that leads somewhere not yet written". If no such room exists, it uses the room whose writing is finishing, which is the room that runs the deadline. That room's writing ends in this same turn and nobody has seen it written, so whatever grows off it is part of its own writing. This is the "next room written" fallback.
  • Place goal. A new place is laid out two levels down off that room, as before.
  • Person goal. First, the deepest room of a place the party can reach. That room may already be written, because bringing a person into a written room is the principle's stated exception. Otherwise a place to keep them is built off the way on. A person now needs no free doorway when a room for them already exists.
  • Thing goal. It lies in the way on. If that is an unwritten room standing on its own, the room first gets the box its writing would give it (lay_out_alone, seeded on the room), so the thing gets a spot. The later writing keeps both the box and the spot.
  • When nothing will take it (every reachable room is written, and the room being written is full or is a place), nothing is placed. The goal waits for the next room written, the same self-healing the game already relies on when a room is at its cap. If a world will never write another room, the doctor's existing story_cannot_progress finding names it ("a world past that with nothing bound is one that has stopped growing"). This takes four doorways into one stub from already-written rooms with nothing else unwritten. The old rule could also find no anchor once every reachable room was full, so this is not a new kind of failure, and I did not treat it as a product decision. If it should be one, the options are: (a) keep the wait (this PR); (b) let the room being written exceed MAX_EXITS by one for the arc; (c) allow a door on a written room in that one case, which breaks principle 7.

deadline::anchor is unchanged and nothing places from it any more. It stays because vectors/deadline.json is exported by the game from Ruby's #anchor. It can be removed together with that vector.

No prompt changes. No model calls. The moving city and the dungeon stages are untouched.

Proof

  • New sweep a-deadline-leaves-written-rooms-as-they-were (yard world, in PASSING, with a records file). Three rooms are written past the grace period, and each places one goal. The walk then goes to all three: the warden in the place built off the Wash House before it was written, the Salt Stair laid out off the bottom of that place, and the iron seal lying in an unwritten room of the stair, which it picks up. All three beats end bound and two are reached.
  • tests/deadline.rs:
    • a_deadline_lands_without_changing_a_room_already_written plays that sweep one step at a time. After each step it asserts that every room written before the step is identical: its row (less last_protagonist_visit), every doorway into or out of it, and every world thing lying in it.
    • with_nothing_left_unwritten_the_step_grows_off_the_room_being_written covers the fallback.
    • a_thing_lies_in_a_room_nobody_has_written_and_its_writing_keeps_it covers a thing in a plain stub, including its box and spot.
    • With the old anchor swapped back in, all three fail. The first fails with "step 1 into-the-cold-store changed room 1000000003, written before it" (the loft).
  • Unit tests in src/deadline.rs for way_on.
  • cargo fmt --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo test --workspace --locked, parity --check parity (105 of 109 agree; the 4 left are the unchanged runner-only scripts), and fight --check sim (10 fight goldens agree, so the reference fights are unchanged).

Goldens re-blessed (only those this change moves)

  • parity/goldens/a-deadline-places-what-the-arc-waits-for.json and parity/records/a-deadline-places-what-the-arc-waits-for.json. The script changed with it: its old last three steps walked Kitchen → Loft → the warden's door bolted onto the Loft. That door no longer exists, so the walk now goes back into the Wash House, which was written after the door was laid on it, and through that door. From step 3 the Wash House's ways out include where Warden Hesk is room 1, and the Salt Stair and the iron seal land in unwritten ground.
  • New: parity/goldens/a-deadline-leaves-written-rooms-as-they-were.json and parity/records/a-deadline-leaves-written-rooms-as-they-were.json.

No other golden, records file or vector moved. The four runner-only scripts write no room under an arc, so they cannot move.

What the server pin PR must change (renderedstep/server)

  1. app/models/quest/deadline.rb (the Ruby mirror):
    • after_realizing!(location) keeps passing the realized room. #run! places from way_on(anchor_hint) instead of #anchor.
    • Add #way_on(written): among hops.keys rooms that are not realized, not laid_out?, not a place with a footprint and no box (Location#interior? with no box: the engine's is_place), and with fewer than MAX_EXITS doorways out, take the minimum of [z.to_i, -hops, id]. If there are none, use written when it is reached, not such a place, and has a doorway to spare. Otherwise nil.
    • #place_a_place! and #build_somewhere_to_keep_them! open their door off that room.
    • #place_a_person! tries deepest_room_of_a_place_we_can_reach first even when way_on is nil, and builds a place only when way_on has a room.
    • #place_a_thing! admits into the way on, after Location::Layout's own-box step for a stand-alone room with no box yet (the engine's lay_out_alone).
    • #anchor can stay as it is for lib/engine_vectors/deadline.rb. test/engine_vectors/deadline.json does not change.
    • Update the header's "where" and "NOT A ROUTE ... One door, off a room the party can already stand in" paragraphs to say the door goes off unwritten ground or the room being written, never a written room (VISION principle 7).
  2. test/models/quest/deadline_test.rb: open_rooms builds a chain of realized rooms with no stubs, so every placement goes off the room passed to after_realizing!, the fallback. These tests must change:
    • "a thing target is left lying in the deepest room the party can reach": it now lies in the way on, or in the room just realized.
    • "the anchor is the deepest room by storey, and a tie goes to the furthest out": the laid-out place's rooms are unwritten, so below (z: -2) should still be chosen, now as the way on. Rename it and assert that the cellar gains no door.
    • "the way in lands on a room of it and never on the container": deepest is the realized room, so it still holds as the fallback.
    • Add tests that a written room other than the one just realized never gains a doorway or a thing, that an unwritten reachable room is preferred, and that nothing is placed when neither will take it.
  3. Sweep scripts to vendor. Ready to copy, with their header comments, in data/ta-quest-deadline-generated-once/server/lib/engine_sweep/scripts/. Both re-emit through the README's Ruby YAML command byte-identical to this repo's copies.
    • lib/engine_sweep/scripts/a-deadline-places-what-the-arc-waits-for.yml (changed tail and header)
    • lib/engine_sweep/scripts/a-deadline-leaves-written-rooms-as-they-were.yml (new)
  4. Goldens and records to vendor: the two goldens and two records files above, into test/engine_parity/.
  5. Checks files: run bin/rails engine:checks for both scripts and commit test/engine_parity/a-deadline-places-what-the-arc-waits-for.checks.json (it moves: the warden's place now hangs off the Wash House, and the iron seal lies in a stub) and the new a-deadline-leaves-written-rooms-as-they-were.checks.json. The doctor's own code does not assert the old shape. It reads only Quest::Deadline.overdue? and GRACE_ROOMS, which are unchanged, and its wording still holds. Expect character_in_a_stub-style warnings for the placed targets, as today.
  6. Invariants (optional): EngineSweep::Invariants could add the same check this repo's tests/deadline.rs makes: no room realized before a step gains or loses a doorway or a world thing during it.
  7. Then move the engine pin and SERVER_COMMIT together.

CI at the current pin (SERVER_COMMIT 6b85c8f): the fmt, clippy, test job and the sweep and fight checks pass here. The extension still builds against this checkout (it does not use deadline). The runner job's engine:rust_gates and runner.sh play the server's own copy of a-deadline-places-what-the-arc-waits-for, whose old Loft route no longer exists, so that one script is expected to fail there until the server branch carrying step 3 above exists and SERVER_COMMIT points at it. runner.sh skips the new script until the server has it.

VISION principle 7: the engine does not change the world after it is
generated. The quest deadline hung a new doorway, and could lay a thing,
on a room the world had already written (the old anchor, the deepest
reachable room). It now grows what the step needs off the way on
(deadline::way_on): the deepest reachable room nobody has written yet,
or, with none, the room whose writing is finishing, which nobody has
seen written. A person may still be brought into a written room (the
principle's exception) and needs no free doorway when one exists. A
thing laid in an unwritten room standing on its own first gets the box
its writing would give it, and the writing keeps box and spot. With no
ground to grow in, nothing is placed until the next room is written.

The new sweep a-deadline-leaves-written-rooms-as-they-were places all
three of the yard's steps and walks to each; tests/deadline.rs plays it
step by step and asserts no room written before a step changes during
it, and covers the fallback and a thing in a plain stub.
a-deadline-places-what-the-arc-waits-for walked through the door bolted
onto the loft, so its tail now walks through the wash house; its golden
and records are re-blessed. No other golden, records file or vector
moves, and the reference fights agree.
…ten ground (renderedstep/server b2a4523)

The game's pin PR (renderedstep/server#372) carries the Ruby mirror of
deadline::way_on, both deadline scripts and their goldens, records and
checks, all byte for byte this repository's. No golden moves here.
@calebl
calebl merged commit f9c2247 into main Oct 30, 2026
4 checks passed
calebl added a commit to renderedstep/server that referenced this pull request Oct 30, 2026
calebl added a commit to renderedstep/server that referenced this pull request Oct 30, 2026
…ted-once-server

Pin renderedstep/engine#96's head (d57d7f9): grow an overdue arc step only in ground nobody has written
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