Skip to content

Remember what torchlight showed: persist seen cells as map memory - #39

Merged
mmacy merged 1 commit into
mainfrom
fix-seen-cells-persist
Jul 30, 2026
Merged

Remember what torchlight showed: persist seen cells as map memory#39
mmacy merged 1 commit into
mainfrom
fix-seen-cells-persist

Conversation

@mmacy

@mmacy mmacy commented Jul 30, 2026

Copy link
Copy Markdown
Owner

The light reveal (1.2.1) was deliberately ephemeral: the player view recomputed what the party sees by its own light on every build, so the moment the party walked on — or the torch guttered out — every cell it had glimpsed but never stepped on fell dark again. A front end's automap could therefore only remember the physically walked footprint, and a mapped room vanished behind the party (osr-web #23). The client cannot fix this itself; the projection is its only source of geometry.

What changes

  • DungeonState.seen holds the remembered cells beside explored, same "{dungeon}:{level}" key shape. mark_seen is an idempotent bulk append, new cells in sorted (x, y) order so serialization is deterministic across runs.
  • GameSession.execute folds the current light reveal into seen after every accepted command (_persist_sight) — the one hook that covers every reveal-changing action uniformly: entering, moving, stairs, doors, lighting, placement, discovery, battle-flight relocation. Rejected commands change no state, so they never reach it.
  • The projection unions walked, remembered-seen, and currently lit cells; the live reveal stays so lighting a torch draws the room immediately, even when a light effect lands on the ledger without a command.
  • _light_reveal and its radius helpers move from views.py to exploration.py, beside _sight_passes — they are game logic now, not projection.

What deliberately does not change

Seen is memory of sight, never exploration: movement cost still reads the walked footprint (a lit-but-unwalked cell costs the full 30 thirds), drop-pile visibility stays on explored cells (piles only ever form where the party has stood), an undiscovered secret door's sealed alcove is never recorded, and the referee view is untouched. The field is additive with a default — no schema bump, no migration per the versioning policy; an older save loads with empty memory and starts remembering.

Tests

  • New TestSeenPersistence: the issue's walk-away scenario (torch extinguished so live sight can't mask a persistence failure — fails on main), save/load round-trip, a pre-seen save document loads clean, and the sealed alcove stays unrecorded.
  • mark_seen idempotence and deterministic ordering; the overlay round-trip test now carries a populated seen.
  • The leak property test's invariant becomes membership in explored ∪ seen (its old form was already violated in principle by the 1.2.1 ephemeral reveal whenever the fuzz lit a torch); the cross-level test now expects level 1's memory to survive the descent while the live reveal still never bleeds across levels.
  • test_seeing_a_cell_by_light_does_not_change_movement_cost passes unchanged — the guardrail that motivated keeping seen out of explored.

Suite: 1657 passed, 58 skipped (baseline 1652). ruff format and ruff check clean. Verified end to end through osr-web against this branch: the automap keeps every cell seen on a walk after the party returns, and the memory survives save/restore over the wire.

https://claude.ai/code/session_01Gu9KVvucLb7NoquyNjmxxv

The light reveal (1.2.1) was deliberately ephemeral: the player view
recomputed what the party sees by its own light on every build, so the
moment the party walked on — or the torch guttered out — every cell it
had glimpsed but never stepped on fell dark again. A front end's automap
could therefore only remember the physically walked footprint, and a
mapped room vanished behind the party (osr-web issue #23). The client
cannot fix this itself; the projection is its only source of geometry.

Persist sight as its own state, distinct from exploration:

- `DungeonState.seen` holds the remembered cells beside `explored`, same
  `"{dungeon}:{level}"` key shape. `mark_seen` is an idempotent bulk
  append, new cells in sorted (x, y) order so serialization is
  deterministic across runs.
- `GameSession.execute` folds the current light reveal into `seen` after
  every accepted command (`_persist_sight`) — the one hook that covers
  every reveal-changing action uniformly: entering, moving, stairs,
  doors, lighting, placement, discovery, battle-flight relocation.
  Rejected commands change no state, so they never reach it.
- The projection unions walked, remembered-seen, and currently lit
  cells; the live reveal stays so lighting a torch reveals immediately,
  even when a light effect lands on the ledger without a command.
- `_light_reveal` and its radius helpers move from views.py to
  exploration.py, beside `_sight_passes` — they are game logic now, not
  projection.

Seen is memory of sight, never exploration: movement cost still reads
the walked footprint (a lit-but-unwalked cell costs the full 30),
drop-pile visibility stays on explored cells, an undiscovered secret
door's sealed alcove is never recorded, and the referee view is
unchanged. The field is additive with a default — no schema bump, no
migration; an older save loads with empty memory and starts remembering.

Adds TestSeenPersistence (walk-away persistence, save/load round-trip,
pre-`seen` save loads clean, sealed-alcove no-leak) and mark_seen
idempotence/ordering coverage; the leak property test's invariant
becomes explored-or-seen, and the cross-level test now expects level 1's
memory to survive the descent while the live reveal still never bleeds
across levels.

Claude-Session: https://claude.ai/code/session_01Gu9KVvucLb7NoquyNjmxxv
@mmacy
mmacy merged commit cf0ccb1 into main Jul 30, 2026
5 checks passed
@mmacy
mmacy deleted the fix-seen-cells-persist branch July 30, 2026 17:16
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