task 3 report - xopherdeep/do-it-for-the-xp GitHub Wiki
Branch worktree-agent-a6c384aecab78e0a9, one commit: 2a2b9dac feat(temples): a beaten beast drops what its room holds, and it waits there for a hero with no fight there (on top of origin/master 62e9e5fb).
Status: DONE_WITH_CONCERNS (concerns at the end; none blocks).
Engine (remembering the take)
-
templeSystem/chests.ts:markDropTaken(runs, templeId, level, position)andisDropTaken(...). They filedropKey(level, position)("1F:2,3:drop") inTempleState.openedChests, apart from the room's own key, so the search and the drop never spend each other. The key already round-trips through saves:heroRunsparsesopenedChestskeys withparseRoomKey, which returns null for a:dropkey, so it is kept and does not touch any room. A blood moon (templeReset,openedChests: []) gives it back; a quest restoring a room only touchesclearedRooms. -
TempleSystem.ts: publicmarkDropTaken/isDropTakenwrappers withoutruns.
Doors bug (pre-existing)
-
templeSystem/doors.ts. Cause: a Bomb and the Boss Key both filed the plaindoorKeyinopenedDoors, anddoorKindread that key as "the boss lock is open". A blown crack into a boss room therefore readopen. - Fix: a cracked wall into a boss room reads
masteruntil the Boss Key has opened it.unlockDoorwrites abossKeyMark(${doorKey}:boss) for that case only, anddoorKindrequires it for a cracked side. Every other boss door and every other blown crack record exactly what they did before:openedDoorskeeps the same keys, so saved runs and the existing exact-set assertions hold.heroRunsignores the mark when it re-locks doors from a save (its direction iseast:boss, not a side). A blood moon clears it with the rest ofopenedDoors. - A run saved before the fix, with a crack into a boss room already blown, now needs the Boss Key at that side again (lead ruling: Bombs never open locked doors).
The sheet
-
vocabulary.ts:RoomIntentgains'takeDrop';RoomSheet.drop?: 'waiting'. -
roomInteractions/treasure.ts:isDropWaiting(room, ctx, isFightLive)=roomDrops(room) !== null && !isDropTaken && !isFightLive. -
RoomInteractions.ts(monster/miniboss/boss case): the fight is live for a quest room whenquestWords.fight, for a classic room while!isRoomCleared. When the drop waits the sheet offers Take it (intenttakeDrop) before the search, setsdrop: 'waiting', and puts the raw line "Something glints where the beast fell." inbody(after the quest's chore line, joined with a space; alone in a classic room). Quest statuses that offer it: cleared, pending, notYours, away, emptyLair (every status with no fight).
Taking it (hook and screen)
-
src/lib/services/temples/lootBag.ts(new, pure):bagItemOf(value)(a supply or the Bomb becomes the bag row; a Bomb is{ name: 'Bomb', kind: 'consumable', faIcon: 'bomb', qty: 1 }andinBagOnly),bagHolds,bagHoldsAll(simulates the bag and storage row by row, so two supplies that each need the one free slot do not both pass). -
src/hooks/temples/templeLoot.ts(new): the extractedhandOutItems(deps, selected, content)(the oldclaimChestItemsloop, with the Bomb branch added),dropRows(drops)(chestLootof the drop,dungeonpassed throughdungeonItemIdso "small-key" becomes "key", every row ticked), andtakeDrop(...): nothing left -> "Nothing left here."; picks filtered to the drop's own rows; nothing picked -> "You took nothing.";bagHoldsAllfalse -> nothing handed out, "Your bag is full. Store something at home first."; otherwisehandOutItems,saveProgress,markDropTaken, "Collected: ...". -
useTemple.ts:claimChestItemsnow callshandOutItems(so chest Bombs go into the bag too, and stay in the chest when the bag is full); newdropContents(computed,[]once taken) andclaimDropItems. File went from 680 to 642 lines. -
templeReveals.ts:useChestRevealgainsshowDropReveal()(opens the reveal open, not shut, with the drop's rows) and routes Take toclaimDropItemswhile a drop is shown (isDropShown, reset byshowChestContentsDialog/showEmptyChest); the Pegasus logic is skipped for a drop. -
useRoomOutcomes.ts: new depshowDropRevealandcase 'takeDrop'. -
TempleGrounds.ts: wired with no net lines (498 stays 498, under the 500 ratchet) by putting the new names on the existing destructure / argument lines.
RED (before any implementation), chemx test tests/unit/RoomInteractions.spec.ts:
✖ Test Failures (1 failed out of 1)
✖ Test suite failed to run
tests/unit/RoomInteractions.spec.ts:1268:18 - error TS2339: Property 'drop' does not exist on type 'RoomSheet'.
RED, chemx test tests/unit/TempleSystemDoors.spec.ts (the bug reproduced):
✖ Test Failures (3 failed out of 24)
✖ ... a cracked wall into a boss room › is still the Boss door once a Bomb has blown it: the blast opens the wall, not the lock
Expected: "master" Received: "open"
✖ ... › takes no small key, and the Boss Key is not spent opening it (Expected: true, Received: false)
✖ ... › stays the Boss door through a reload, and stays open once the Boss Key opened it (Expected: "master", Received: "open")
RED, chemx test tests/unit/FightDrops.spec.ts (after the doors fix, before the engine/hook code):
✖ Test suite failed to run
tests/unit/FightDrops.spec.ts:204:13 - error TS2339: Property 'markDropTaken' does not exist on type 'TempleSystem'.
and, once the engine existed, the hook block failed with vm.dropContents undefined / vm.claimDropItems is not a function (13 failures).
GREEN (final code, run one at a time):
chemx test tests/unit/RoomInteractions.spec.ts ✔ All tests passed (137 passed)
chemx test tests/unit/FightDrops.spec.ts ✔ All tests passed (39 passed)
chemx test tests/unit/TempleSystemDoors.spec.ts ✔ All tests passed (24 passed)
New tests:
-
RoomInteractions.spec.ts(a fight room's drop): lurking offers no Take it and nodrop; cleared, pending, notYours and away offer it (intent: 'takeDrop',drop: 'waiting', the glint line inbody); a classic room not yet cleared does not, and a cleared monster/miniboss/boss does; aftermarkDropTakenit goes and the search stays,isRoomSpentstays false, and the two are independent both ways; no drop on a drop-less room or under a loot room. -
FightDrops.spec.ts:TempleSystem(drop key, apart from the room key, per floor and hero; reload keeps it; another hero's run does not have it; a real blood moon gives it back; a daily quest restoring the room does not) and the hook (rows and ticks, key/Boss Key/map/compass/gold hand-out, second take gives nothing, empty take, rows the drop does not hold ignored, a Bomb into the bag in the shop's shape and merging into an existing stack, chest Bomb into the bag / stays when full, whole-or-nothing when full including the last-two-slots simulation, supply into storage but Bomb bag-only, keys and gold need no slot). -
TempleSystemDoors.spec.ts: a cracked wall into a boss room staysmasterafter the blast (explain line, no walking, small key refused, Boss Key opens it without being spent, survives a reload, the way back out is plain), and other cracks / plain boss doors still record exactly what they did.
src: src/lib/engine/core/dungeons/templeSystem/chests.ts, .../templeSystem/doors.ts, .../TempleSystem.ts, src/lib/services/temples/RoomInteractions.ts, .../roomInteractions/vocabulary.ts, .../roomInteractions/treasure.ts, .../lootBag.ts (new), src/hooks/temples/useTemple.ts, .../templeLoot.ts (new), src/app/Console/TempleGrounds/templeGrounds/templeReveals.ts, .../useRoomOutcomes.ts, src/app/Console/TempleGrounds/TempleGrounds.ts.
tests: tests/unit/RoomInteractions.spec.ts, tests/unit/FightDrops.spec.ts, tests/unit/TempleSystemDoors.spec.ts.
- Line budget: new files 162 and 58 lines;
TempleGrounds.ts498 (unchanged);useTemple.tsshrank to 642;TempleSystem.tsgrew 667 -> 685, but it was already over 500 (wrappers are required by the brief), so the ratchet count (a file count) does not move. - Inline conditions are named; no inline object parameter types (
BagRow,BagLoot,LootHandOut,DropTakeare named interfaces). Comments in the file's long plain-English style. -
claimChestItemsis behaviour-preserving apart from the new Bomb branch: the loop moved verbatim intohandOutItems; the supply branch makes the sameaddInventoryItemcall with the same row. - I did not run typecheck / lint / build (barred).
jesttype-checks every file a spec imports (this caught aBagRowindex-signature error, fixed), so the engine, sheet,useTemple,templeLootandlootBagare type-checked;templeReveals.ts,useRoomOutcomes.tsandTempleGrounds.tsare not imported by the three specs and are only reviewed by eye, so the lead's typecheck is the first compiler pass over them. - Per the constraint I ran only the three named specs. The other specs that exercise code I changed are for CI:
TempleChestTake.spec(claimChestItems),BossKeyChest.spec,DragonEggRoom.spec,TempleKeyCount.spec,AuthoredBossDoor.spec,TempleSystemMaster.spec,ItemUse.spec,TempleSystemFloors.spec,TempleWallSheet.spec(compilestempleReveals.ts).
-
Unticking a row forfeits it. The brief's
claimDropItems(selected)hands out the ticked rows and then marks the whole drop taken (one flag per room), so a hero who unticks the key loses it, and the temple could then be unfinishable although the solver counts that key. I mitigated it by ticking every row of a drop by default (dropRowssetschecked: true; the reveal honours it, Take reads "Take all"), but the hero can still untick. If you want it airtight, either ignoreselectedbeyond the "took nothing" guard, or have the reveal not offer unticking for a drop. I did not change the brief's signature. -
A Bomb needs the bag, not storage (as the temple shop does:
ItemUse.bombInPackreads the pack, so a Bomb in home storage cannot be used). The brief says "no room in the bag or storage" for the whole-drop check; I applied that to supplies (bag or storage) and bag-only to the Bomb. A chest or drop whose Bomb cannot go in the bag says the bag is full even when storage has room. -
Quest status
pending(claim waiting for the Game Master) offers Take it, as doesemptyLair, because the brief's rule is "no fight is live". Say so if the drop should wait for approval. - Old saves: a run with a crack into a boss room already blown reads as the Boss door again (needs the Boss Key), by the lead ruling.
- The reveal for a drop is the chest sprite, opened immediately (no "A chest!" shut state), with the chest titles ("You found...", "Treasure!"). A Bomb row shows the generic gift icon (
lootFacehas nobombentry); I leftChestLoot.tsalone as out of scope (abomb: { icon: 'bomb' ... }face is a one-liner if wanted). -
.superpowers/is git-excluded in this repo, so this report is not part of the commit.
Commit 58fafbd1 fix(temples): a beast's drop is handed out whole, so an unticked row can no longer lose a key the temple counts on (on top of 2a2b9dac).
-
All-or-nothing hand-out (
src/hooks/temples/templeLoot.ts,takeDrop). The nothing-ticked guard stays (picked: the ticked rows that are the drop's own; none -> "You took nothing."), but the bag check andhandOutItemsnow run over every row inheld, notpicked. A small key, the Boss Key or gold the hero did not tick is handed out with the rest instead of being lost when the one flag marks the drop taken. A row the drop does not hold is still never handed out. The header andtakeDropdoc comments say so. -
The drop reveal offers no unticking (small change, so done).
TempleChestReveal.vuegains an optionalwholeprop: every row starts ticked (props.whole || item.checked),togglereturns early once ready, and each row getsaria-disabled="true". A tap before the rows are out still skips the animation as before; ordinary chests and the Pegasus chest are untouched. Wiring with no extra lines inTempleGrounds.ts(still 498):useChestReveal'sisDropShownis now arefexposed inchest, destructured on the existing line, returned on the existingisChestClosed,line, and bound as:whole="isDropShown"on the one<temple-chest-reveal>line inTempleGrounds.vue. -
Nothing left behind, or no mark (
takeDrop). AfterhandOutItems,saveProgressruns (state was changed directly) and then, ifleftBehindis not empty, it answersBAG_FULL_LINEand does not callmarkDropTaken. One named guard (isSomethingLeft). -
Audit ratchet (
src/lib/services/temples/lootBag.ts):const isRefused = !bagHolds(given, loot); if (isRefused) return false;.
Not touched (as instructed): the shared bag-full string with shop.ts, the solver/engine view of a cracked wall into a boss room, the one-per-line style in TempleGrounds.ts, the Bomb row's source.
New or changed in tests/unit/FightDrops.spec.ts (hook block):
- "is all-or-nothing: ticking only the gold of a drop that holds a small key still hands out the key" (key + gold, only
gold:15ticked:playerKeys1, wallet up, drop taken, messageCollected: Small Key, N GP, rows empty). - "hands out the Boss Key and a Bomb of a drop whose gold alone was ticked".
- "does not mark the drop taken when handing it out left something behind, and says the bag is full" (the store's
addInventoryItemis spied to refuse while the bag looks free, sobagHoldsAllpasses andhandOutItemsleaves the Bomb behind: not marked, bag-full message, rows still shown). - The old "a row it does not hold is ignored" test is now "never handed out, whatever was ticked": a lone stray row is "You took nothing."; a stray beside a held row hands out only the held one (key 1, no Boss Key).
In
tests/unit/TempleChestReveal.spec.ts(new describe "a beast's drop (whole)"): rows come ticked whatevercheckedsays and Take hands over every row; a tap on a row changes nothing and the row isaria-disabled; an ordinary chest still unticks and has noaria-disabled.
RED (before the fix):
chemx test tests/unit/FightDrops.spec.ts
✖ Test Failures (3 failed out of 42)
✖ ... is all-or-nothing: ticking only the gold of a drop that holds a small key still hands out the key Expected: 1 Received: 0
✖ ... hands out the Boss Key and a Bomb of a drop whose gold alone was ticked Expected: true Received: false
✖ ... does not mark the drop taken when handing it out left something behind, and says the bag is full Expected: false Received: true
chemx test tests/unit/TempleChestReveal.spec.ts
✖ Test Failures (2 failed out of 31)
✖ ... a beast's drop (whole) › comes with every row ticked, whatever the rows say, and takes them all Expected length: 2 Received length: 0
✖ ... › offers no unticking: a tap on a row changes nothing and the rows say they cannot be toggled Expected length: 2 Received length: 1
GREEN (after the fix, one spec at a time):
chemx test tests/unit/FightDrops.spec.ts ✔ All tests passed (42 passed)
chemx test tests/unit/TempleChestReveal.spec.ts ✔ All tests passed (31 passed)
chemx test tests/unit/TempleChestTake.spec.ts ✔ All tests passed (21 passed)
chemx test tests/unit/RoomInteractions.spec.ts ✔ All tests passed (137 passed)
chemx test tests/unit/TempleWallSheet.spec.ts ✔ All tests passed (9 passed)
TempleChestReveal.spec is the one extra spec beyond the ones you listed: I touched TempleChestReveal.vue and added its tests there, so it is the direct cover.
src/hooks/temples/templeLoot.ts, src/lib/services/temples/lootBag.ts, src/app/Console/TempleGrounds/components/TempleChestReveal.vue, src/app/Console/TempleGrounds/TempleGrounds.vue, src/app/Console/TempleGrounds/TempleGrounds.ts, src/app/Console/TempleGrounds/templeGrounds/templeReveals.ts, tests/unit/FightDrops.spec.ts, tests/unit/TempleChestReveal.spec.ts.
A hero can no longer forfeit a drop's key by unticking it: the reveal does not offer the toggle, and the hand-out ignores the ticks anyway. TempleGrounds.ts / TempleGrounds.vue / templeReveals.ts still have no spec that compiles them together, so typecheck is the first compiler pass over those (the whole binding in the .vue template is not type-checked by jest).