task 7a report - xopherdeep/do-it-for-the-xp GitHub Wiki

Task 7a report: locked climbs (board #1484)

Commit: 399b6947 feat(temples): a flight of stairs can be locked: a small key or the Boss Key opens the climb on feat/classic-temples-2 (committed only my paths; nothing under templeReveals.ts, TempleChestReveal.vue or TempleGrounds.*).

What I built

  • types.ts: ClimbLock = 'small' | 'master' and RoomContent.locked?: ClimbLock (the climb FROM a stairs room is locked; the landing is free).
  • authoredDoors.ts: climbLockOf(room), the one place that reads content.locked (a stray value locks nothing). The engine's climb, the sheet and the solver graph all ask it, so they agree.
  • templeSystem/doors.ts: climbKey(level, position) = ${roomPositionKey}:climb, isClimbUnlocked(runs, templeId, level, position), unlockClimb(runs, templeId, level, position, kind). State is TempleState.openedDoors, so a saved run keeps it and a blood moon (openedDoors: [] in templeReset) locks it again. unlockClimb('small') spends one key and refuses with none; 'master' needs the Boss Key and keeps it; an already-open climb returns true and spends nothing. The mark cannot be mistaken for a door when a save is loaded (heroRuns: no >, so the direction check skips it; covered by the reload test).
  • TempleSystem.ts: two thin public wrappers isClimbUnlocked(templeId, level, position) and unlockClimb(templeId, level, position, kind) (+ a type import). File was already over 500 (685 -> 700).
  • roomInteractions/travel.ts: new stairsSheet(room, ctx, way); climb now checks rubble first, then opens the lock (no key is spent on stairs to nowhere), then climbs. Lines: sheet "Stairs lead up." with subHeader "A small lock bars the way." / "The Boss Door's lock bars the way."; actions [] without the key, Unlock and climb with it, plain Climb on a free or already-open flight. Unlock-and-climb outcome: "Unlocked! You climb up to 2F." (small) / "The big lock falls away. You climb up to 2F." (master), sound useKey, moved: true. A stale sheet tapped without the key answers with the lock line and error.
  • RoomInteractions.ts: the two stairs cases call stairsSheet.
  • templeGraph.ts linkStairs: the stairs door leaving a room carries climbLockOf(room) ?? "none"; the solver already knew small / master.
  • dungeonCounts.ts countKeysAndLocks: a stairs room with locked: 'small' adds one lock (actualLocks = ceil(doors / 2) + lockedClimbs); a Boss Key climb adds none.

TDD evidence

RED (before any implementation):

chemx test tests/unit/TempleKeyBalance.spec.ts
  Test Failures (2 failed out of 10)  (stairs counted as one small lock; stairs' lock added to the locked doors: Expected 2, Received 1)
chemx test tests/unit/TempleSolver.spec.ts
  Test Failures (4 failed out of 53)  (keyless locked climb solvable: Expected false, Received true; graph stairs door lock; R3 count; Boss Key climb solvable: Expected false, Received true)
chemx test tests/unit/RoomInteractions.spec.ts -t "a flight of stairs with a lock"
  Test suite failed to run: TS2339 Property 'isClimbUnlocked' does not exist on type 'TempleSystem'

(RoomInteractions went red at compile because the API did not exist yet; its behaviour assertions first ran after the implementation.)

GREEN (after):

chemx test tests/unit/TempleKeyBalance.spec.ts     All tests passed (10 passed)
chemx test tests/unit/TempleSolver.spec.ts         All tests passed (52 passed, 1 skipped)   # the skip is the existing it.skip for hand-made temples (line ~468)
chemx test tests/unit/RoomInteractions.spec.ts     All tests passed (146 passed)
chemx test tests/unit/TempleDataModules.spec.ts    All tests passed (62 passed)   # extra: it also covers countKeysAndLocks

Tests added:

  • TempleSolver: locked small climb solvable with the key on 1F and not without it (R2 reason, names the stairs door); the stairs door carries small / master one way (landing none); R3 counts the stairs' lock; Boss Key climb solvable only with the Boss Key reachable before it (the same layout with the key above the climb is stuck, and solvable with free stairs).
  • RoomInteractions (9 tests in "a flight of stairs with a lock on it"): no key = barred sheet, no actions; key = Unlock and climb spends exactly one and climbs; a second climb in the run is free; the landing climbs back with no key; stale sheet with no key is turned back; a small key does not open a master lock; Boss Key opens it and is not spent (small keys untouched); rubble comes before any key is taken; saved run keeps it open across a reload, and bloodMoon locks it again.
  • TempleKeyBalance: small-locked stairs = 1 lock, Boss Key climb = 0, added on top of the halved door locks.

Files changed

src/lib/engine/core/dungeons/types.ts, templeSystem/doors.ts, TempleSystem.ts; src/lib/services/temples/authoredDoors.ts, roomInteractions/travel.ts, RoomInteractions.ts; src/lib/engine/dungeons/generator/templeGraph.ts; src/app/Admin/XpCompendium/views/XpTemples/hooks/templeData/dungeonCounts.ts; tests/unit/TempleSolver.spec.ts (476 lines), RoomInteractions.spec.ts, TempleKeyBalance.spec.ts. All source files touched stay under 500 except TempleSystem.ts (already over; thin wrappers only).

Self-review

  • Names in ifs: lockToOpen, hasOpened, hasTheKey, isFreeOrOpen, isSmallLock, isOutOfKeys, isKnownLock, isSmallLockedClimb. No object-literal parameter types added; CLIMB_*_LINES are Record<ClimbLock, string>.
  • Not run (per rules): typecheck, lint, verify, build. ts-jest type-checked every file the three specs import, and it was clean.
  • The solver already treated small / master stairs-door locks generically (passable, closedDoorsInReach, R3 isCritical), so only the graph needed the field.

Concerns

  • If the hero's key is spent and then arriveOn finds no spot to stand on (a stairs room whose requested landing and same cell are both not rooms), the climb stays open and the hero gets "blocked by rubble". R8 already flags such stairs, so I left it.
  • The brief's sheet text "Stairs lead up. A small lock bars the way." is split as header "Stairs lead up." + subHeader "A small lock bars the way." (the same header/subHeader shape every other locked thing uses). A consumer matching one joined string would need adjusting.
  • A shared room token (rooms table entry reused by several cells) locks every cell that uses it, as with every other room property; the dress step should give a locked flight its own token. Also, the landing stairs of a locked flight must not use the locked token, or its climb back would be locked too.
  • Docs (docs/Temple-Generation-Contract.md, a submodule) not touched.
⚠️ **GitHub.com Fallback** ⚠️