Repository navigation
Rest an hour in a safe room with no foe in it, and mend eight - #100
Merged
Merged
Conversation
A wait in a sanctuary (locations.sanctuary) with no foe in it and the
player alive with a stat block is a rest. It mends REST.mends (8,
HEALING_POINTS: one healing thing's worth) up to the player's maximum,
and on the told path its scene spends REST.minutes (60) instead of an
action's five. With a foe in the room, or anywhere that is not a
sanctuary, it is the plain five-minute wait it was. A rest is never
refused: one that runs past a floor's countdown is played, and stage 6's
after-time check takes the player with the floor.
Each rest is kept as a row of the new per-game table playthrough_rests
(where, recovered, hp_after, the story time it began), which the turn's
scene claims as it claims a toll. Game::rest_to_s says it in the
engine's words: "{fullname} rested for an hour in {room} and recovered
{n} hit points ({hp} of {max})." The engine-alone report notes it as
"the game: ...". No request carries it: the narrator is asked as for any
wait, and where none answers the engine's words are "You rest here a
while." The engine alone spends no time on a wait, rest or not.
Migration 20261031180000 creates playthrough_rests, applied to every
world dump as Rails' SQLite adapter applies it. store::OPTIONAL_LOADED
and OPTIONAL_SHAPES hold it, so the game at its current pin still opens
and plays: there a rest mends and takes its hour and keeps no row.
SCHEMA_VERSION and shape.txt do not move. No golden or records file
moved, and the fight simulator's reference fights are unchanged.
New world a-place-to-rest (the game's loader at server b696798, then the
migration) and script resting-in-a-safe-room, in PASSING with a golden
and records.
…p/server cfe509a)
… cfe509a The game's pin PR (renderedstep/server#376) carries migration 20261031180000, so the dump is now the one its own loader writes from a schema load, with playthrough_rests in the schema rather than migrated on after. The rows are the same; only timestamps, the schema digest and the order the dump lists them in moved. No golden or records file moves.
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
Pin renderedstep/engine#100's head (6bf995e): rest an hour in a safe room with no foe in it, and mend eight
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage 8 of the dungeon plan, engine half: rest.
wait, with no new verb. It is a rest when three things hold:locations.sanctuary);foes_inis empty);turn::REST = Rest { minutes: 60, mends: HEALING_POINTS }.playthrough_rests. The turn's scene claims the row as it claims a toll, so the game's log entry can show it. No model is told of it.A world with no sanctuary plays and prompts byte for byte as before. No model was called; every command ran with
OPENROUTER_API_KEYandTYPESAFE_API_KEYunset.The words
The plan's §4.3 draft (
turn::rested, said from the row byGame::rest_to_s):For example:
Odile rested for an hour in The Landing and recovered 8 hit points (14 of 18).n = 1it readsrecovered 1 hit point. This follows the toll notice (Playthrough::Toll#to_s), which also sayspointfor one. A rest can recover 1 when the player is one short of full.recovered 0 hit pointsstays plural, as the toll notice has it.REST.minutes. If the owner changes the minutes, the words need changing with them.scene_idis the rest's scene, and the server's log entry andProtocol::V1#entrynoticesshow it from there (server change below);the game: {notice}, as the level-up has.narrate(command, None, …)).tests/rest.rschecks that no request of a rest containsrested,recovered,an hour,safe roomorsanctuary.prompt_excludes: [rested for an hour, hit points].You rest here a while.(turn::RESTED). They replaceYou let time pass where you stand. Nothing else changed., which would be false for a rest.What changed
turn.rs.REST,RESTED,rested: the table, the fallback words and the notice text.Mechanics::resting: sanctuary, no foe, a living player with a condition.apply_physical'sWAIT: for a rest it callsrest, which does three things:REST.mends;playthrough_restsrow (location,recovered,hp_after, andstory_timestamp, the story time the rest began);RESTED.answered_by_the_world: notes each new rest asthe game: {notice}. It tracksrests_beforeexactly as it tracksfloors_before.turn/told.rs.use_physicalreadsresting()before the effect commits. A rest sets the turn'sspendstoREST.minutes * 60.narrateandtell_in_engine_wordsstamp the scene withstory_now() + spendsinstead ofACTION_SECONDS.physical_effectreads it as a rest again and still spends the hour (tested).told_rests: it followstold_floorsand claims the turn's rest rows for its scene. It comes afterfloor_fell_after, so the scene's time is already spent when the countdown is checked.playthrough.rs.Game::restsandGame::rest_to_s. The maximum is the one at the rest's own time (max_hp_at), as a toll's is.store.rs.PLAYTHROUGH_RESTSjoinsOPTIONAL_LOADEDwith its whole shape inOPTIONAL_SHAPES, so a database opens with the table whole or without it.SCHEMA_VERSIONandshape.txtdo not move.CreatePlaythroughRests), applied to all 36 world dumps inparity/worlds/by the method of stages 5–7:bin/rails db:migrateran on a copy of it in a scratch checkout of the game atSERVER_COMMIT(b696798), holding only this migration.20261029154235, its version row was added before the run and deleted after.20261031150000, the latest atSERVER_COMMIT, and is within the day ahead Rails accepts today. Check it against a freshly pulled main before the pin PR.playthroughrow of the module table.Things a reviewer should know
recovered 0, and the notice says so. That is the plan's rule as written: sanctuary and no foe; it says nothing about the player's hit points.is hurt (14 of 18)where a plain wait's would say(6 of 18). That is the player's state, the same as after a healing thing is consumed. No words about resting or recovering reach any request.What did not move
parity --check parity: AGREED 110 of 114. The other four are runner-only, as before.fight --check sim: AGREED, 10 fight goldens, unchanged. Nothing was re-blessed.Sweep
New world
a-place-to-restand scriptresting-in-a-safe-room, inPASSINGwith a golden and a records file.How the world was made. The world file below was loaded with the game's loader at
SERVER_COMMIT(b696798). That loader already readssanctuary:,safe_rooms:andminutes:. Then the migration ran. No row was set by hand. The rows will not change when the pin branch's own loader rebuilds the dump; only timestamps and the schema digest will.The world.
safe_rooms: 1,minutes: 30. Its layout makes room 3 the safe room.resting-in-a-safe-room(12 steps):harm 12: 6 of 18.rested for an hourandhit points./attack Brother Amal: he is a foe. He strikes back: hp 14./waitwith the foe there: +5 min, no mending. He strikes again: hp 10.Write NOBODY into this place./wait: a rest of +60 min, past the floor's 30. It is played, not refused: dead./wait: refused, dead.I checked every
expect:against the engine's dumps: 27 expectations, 0 mismatches.What the records do not show. The records file does not hold
playthrough_rests, becauseparity::WRITTEN_TABLESholds noplaythrough_*table (nor does it forplaythrough_floors). The rows and the notices are asserted intests/rest.rs.The world file (
lib/engine_sweep/worlds/a-place-to-rest.yml)The script with its why: notes (
lib/engine_sweep/scripts/resting-in-a-safe-room.yml)Tests
tests/rest.rs(new; the live path with replayed replies, and the engine alone):recovered 8,hp_after 14,story_timestampthe time the rest began./attack Brother Amal, a wait is +5 min, mends nothing and keeps no row.sanctuarycleared, a wait is +5 min, mends nothing and keeps no row.physical_effectand delivered again mends once (hp 14), spends exactly one hour, and keeps one row.RESTED, with the notethe game: {notice}; hp 14; no time spent.sanctuarycleared, the change isWAITEDwith no note.rest_to_s:1 hit pointin the singular;(20 of 23)after a floor raised Odile to level 4.Store (
tests/engine.rs).playthrough_restsmissing an index is refused.every_world_has_the_shape_the_engine_is_written_againstholds for all 37 dumps.Local runs, all with both provider keys unset:
cargo fmt --check,cargo clippy --workspace --all-targets --locked -- -D warningsandcargo test --workspace --locked: all pass.parity --check parity: 110 of 114 agree (the four runner-only, as before).fight --check sim: AGREED, 10 fight goldens.SERVER_COMMIT(b696798), on its own schema (noplaythrough_rests), with the extension built from this checkout:parity/runner.sh: 113 of 114 agree; the new script is "not in the checkout yet";engine:rust_gates: PASSED for 113 scripts (goldens, invariants, doctor and audit).SERVER_COMMITdoes not move. The game's extension builds against this checkout unchanged: onlypubitems were added, and no struct the extension builds gained a field.What the server pin PR must change
Line numbers are at
b696798.db/migrate/20261031180000_create_playthrough_rests.rb, with exactly this version and these declarations, so the dumps here stay byte-true.store::OPTIONAL_SHAPESholds every fact of it.db/schema.rbmoves to2026_10_31_180000.Playthrough::Rest(app/models/playthrough/rest.rb,self.table_name = "playthrough_rests"), on the pattern ofPlaythrough::FloorReached(app/models/playthrough/floor_reached.rb):belongs_to :playthrough,belongs_to :location,belongs_to :scene, optional: true.recoveredan integer ≥ 0,hp_afteran integer ≥ 0,story_timestamppresent.#to_s, the Ruby mirror, word for word the engine'sGame::rest_to_s:1 hit pointin the singular, and a maximum read at the rest's time after a floor raised the level.floors_reachedneighbour has:Playthrough:has_many :rests, class_name: "Playthrough::Rest", dependent: :destroy, inverse_of: :playthrough(app/models/playthrough.rb:100).Location:has_many :rests, …, dependent: :destroy, inverse_of: :location(app/models/location.rb:71).Scene:has_many :rests, …, dependent: :nullify, inverse_of: :scene(app/models/scene.rb:198).Playthrough#turn_log(app/models/playthrough.rb:533-535) addsrests: [ :location, { playthrough: [ :character, :floors_reached ] } ].app/views/turns/_turn.html.erb:55adds one<p class="notice" id="<%= dom_id(rest) %>">per rest of this game, after the floors, filtered byplaythrough_idlike the tolls and sorted by id.Protocol::V1#entry(app/models/protocol/v1.rb:76) appends this game's rests the scene claimed, by id, after its floors:docs/protocol/v1/openapi.json: no change.notices(:783) is an array of strings with no description.Playthrough::SqlitePackage::PLAYTHROUGH_TABLES(app/models/playthrough/sqlite_package.rb:27) addsplaythrough_rests, in alphabetical order afterplaythrough_passages.lib/engine_sweep/scripts/resting-in-a-safe-room.yml, with itswhy:notes, as above.lib/engine_sweep/worlds/a-place-to-rest.yml, as above.parity/goldens/resting-in-a-safe-room.jsonandparity/records/resting-in-a-safe-room.json, asbin/rails engine:vendoredchecks them.test/engine_parity/resting-in-a-safe-room.checks.json, written by the runner as for every new script.parity/worlds/a-place-to-rest.sqlfrom the pin branch's own loader with the migration in its schema, as stages 2–7 did.b696798.SERVER_COMMITat the pin branch in the same follow-up.Nothing in the game's request builders reads the new table. No prompt template changes.