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

Task 7: Dress step, shared plan tools, and the Storm Temple from Tower of Hera (board #1457)

Files:

  • Create:
    • tools/temples/questPlan.ts: Schedule, PlanQuest, SCHEDULES and templateFor, moved out of regenerate.ts.
    • tools/temples/renderModule.ts: literal, camel, and renderModule(id, temple, header: string[]), moved out of regenerate.ts.
    • tools/temples/classic/dress.ts
    • tests/unit/ClassicTemples.spec.ts
    • tests/e2e/specs/classic-hera.spec.js
  • Modify:
    • tools/temples/regenerate.ts (imports the moved pieces; PLANS drops storm-temple)
    • src/lib/engine/temples/storm-temple.ts (generated)
    • tests/unit/ShippedTemplesAudit.spec.ts (split the regenerated block from a new classic block)
    • the seed refresh in src/lib/engine/core/dungeons/importAllTemples.ts if Step 1's refresh test fails

Interfaces:

  • Consumes: ClassicDungeon (Task 5); RoomDrops, isBombSource (Task 1); generateTemple's GeneratedTemple shape (rooms keyed by token, maze per floor).
  • Produces, from dress.ts:
    • interface ClassicPlan { name: string; theme: string; element: string; relic: string; shop?: string; trap: [string, number]; puzzle: string; mustClear: PlanQuest[]; minibosses: PlanQuest[]; boss: PlanQuest }
    • CLASSIC_PLANS: Record<string, ClassicPlan>, CLASSIC_IDS: string[]
    • dressClassic(id: string): GeneratedTemple
    • renderClassicModule(id: string, temple: GeneratedTemple): string, with a header naming tools/temples/classic/dress.ts and the dungeon's source
    • classicReport(id): { mustClear: number; repeats: string[] }, the beasts a must-clear room had to repeat
    • Run with esbuild exactly as regenerate.ts is: … --outfile=/tmp/dress-temples.cjs && node /tmp/dress-temples.cjs [id]

Dress rules (from the spec's Dressing table):

  • Fights:

    • One fight per LTTP room: the screen with the most enemies (Review Focus 2). A shutter or enemy key-drop room is a must-clear monster with lockOnEnter; the key goes in drops, and a big key drop means drops: { chest: 'dungeon', dungeon: 'master-key' }.
    • A shutter room whose other side leads straight to the Big Key chest or the Big Chest is a miniboss.
    • If the plan has more minibosses than that rule finds, the must-clear rooms nearest the Big Key chest, then the Big Chest, become the extra minibosses.
    • Other enemy rooms are side monsters without a seal.
    • The boss cell is boss with lockOnEnter. master (the Pegasus chest) goes in a free cell next to the boss, growing that floor's grid by one row or column if none is free.
  • Chests:

    • big-key → loot { chest: 'dungeon', dungeon: 'master-key' }.
    • big-chest → loot { chest: 'dungeon', dungeon: plan.relic }.
    • map, compass and small-key → loot dungeon chests of the same item.
    • potKey → a key room.
    • other → loot { chest: 'gold', gold: 25 }.
  • Other cells:

    • puzzle → puzzle { puzzleType: plan.puzzle }.
    • stairs and hole → stairs-up/stairs-down/pit with targetLevel/targetCoords; teleport → a teleport pair.
    • Empty cells, nearest the entrance first: one shop (with specialItem: plan.shop), one save, one camp { healthPoints: 15 } per floor, and the rest empty.
  • Sides go into sides: open → door, small-key → locked, big-key → masterlocked (and every side into the boss cell), bomb → bombable, shutter → door, item → door, wall → wall.

  • Bomb chests: when solveTemple fails because of bombable sides and the temple has no reachable Bomb source, the nearest reachable empty cell before each such wall gets loot { chest: 'consumable', items: ['Bomb'] }.

  • Chores:

    • mustClear templates go to must-clear monster rooms in path order from the entrance, easiest first.
    • Side rooms take the beasts of their floor's must-clear rooms in turn; a floor with none borrows the nearest floor's.
    • A plan short of beasts repeats one and lists it in classicReport().repeats.
    • Today's quests in regenerate.ts PLANS[id].quests must all stay in the plan, so starter quests still fill rooms.
  • Step 1: Write failing tests in ClassicTemples.spec.ts, over CLASSIC_IDS:

    • the file is exactly renderClassicModule(id, dressClassic(id));
    • every JSON cell is a room at the same position, and every side matches the JSON per the side rules;
    • each LTTP room holds at most one fight;
    • every must-clear room has lockOnEnter;
    • must-clear beasts are distinct and of the theme, and side-room beasts come from must-clear rooms;
    • every key drop sits in drops;
    • every side into the boss cell is masterlocked;
    • the audit passes and solveTemple(...).solvable holds with no wall blown by hand;
    • every chest and every fight cell is reachable (Review Focus 3).

    In ShippedTemplesAudit.spec.ts:

    • split "the eight regenerated temples" into the remaining SHIPPED_IDS and a classic block;
    • the size checks read templeSize from the dressed rooms (≥ 21);
    • "used once" applies to must-clear rooms only.

    A refresh test (Review Focus 1): a stored, unedited copy of the old storm layout, with a hero's saved run positioned on a cell that is a wall in the new one. After seed sync and whenProgressReady, the hero stands on the entrance with no keys and empty openedChests.

  • Step 2: Run chemx test tests/unit/ClassicTemples.spec.ts. Expected: FAIL.

  • Step 3: Move questPlan.ts and renderModule.ts out of regenerate.ts. Then run chemx test tests/unit/ShippedTemplesAudit.spec.ts: the eight regenerated temples must still render byte-for-byte.

  • Step 4: Implement dress.ts and the Storm plan. The theme is bedroom and clutter, the relic is Storm's current relic chest item, and the plan keeps every beast in today's Storm plan. Generate storm-temple.ts and drop storm-temple from PLANS.

  • Step 5: Run ClassicTemples.spec.ts, ShippedTemplesAudit.spec.ts and ClassicDungeonJson.spec.ts. Expected: PASS.

  • Step 6: Write classic-hera.spec.js:

    • cy.seedWorld(), then log a hero in with window.__xp.loginUser.
    • Open the Storm Temple and walk solveTemple's order with the arrow controls, winning each sealed fight through the battle seam the quest-rooms.spec.js e2e uses.
    • Assert the Master Room's Pegasus chest opens.
    • Run: pnpm vue-cli-service test:e2e --headless --browser chrome --url http://localhost:<worktree port> --spec tests/e2e/specs/classic-hera.spec.js against the worktree's own server, which the builder stops when done. Expected: PASS.
  • Step 7: Commit feat(temples): the Storm Temple follows the Tower of Hera, dressed from its JSON.

Updates since the plan was written (binding)

  • The converter was split: tools/temples/classic/convert.py (CLI + run) and lttp_tables.py, lttp_layout.py, lttp_doors.py, lttp_contents.py; the pinned copies are under tools/temples/classic/vendor/. classicDungeon.ts gained ClassicLink.lock?: "small-key" | "big-key" (a locked stairway, recorded on the stairs link of the cell holding the locked door; 8 of them: storm 1, light 1, water 1, shadow 1, ice 2, fire 2). report.mustClear = distinct LTTP rooms with a cell holding enemies and (shutter or keyDrop); bosses/minibosses not counted.
  • Locked climbs land in task #1484 before you start: a stairs room with content.locked: 'small' | 'master' asks for a small key (spent once per run) or the Boss Key on its first Climb, and the solver links it with that lock. Dress writes content.locked from ClassicLink.lock (small-key -> small, big-key -> master). Check the exact field name in master when you start (chemx q locked in RoomInteractions/templeGraph).
  • Keep every file under 500 lines (the audit ratchet): dress.ts will be large, so split it (e.g. dressRooms.ts, dressDoors.ts, dressChores.ts, dressPlans.ts for CLASSIC_PLANS) with dress.ts as the entry. Name conditions in ifs; give object-literal types a named interface.

Lead rulings (binding)

  • Bomb chests: put a Bomb chest (loot { chest: 'consumable', items: ['Bomb'] }) in the nearest reachable empty cell before EVERY cracked wall whose blocking (bombable read as wall) cuts the boss off, whether or not a shop is reachable (the spec: "a Bomb chest sits before any cracked wall on the only way forward"). This replaces the plan's "only when the solver fails" wording.
  • Chores: must-clear plans start from today's beasts in PLANS[id].quests (every beast of STARTER_QUESTS that today's plan holds must keep a must-clear room, so starter quests still fill rooms), topped up with other beasts of the theme to the must-clear count; extras beyond the must-clear count leave that temple. Side rooms cycle through their own floor's must-clear beasts (a floor with none borrows the nearest floor's).
  • ClassicTemples.spec's "must-clear beasts distinct" exempts the beasts listed in classicReport(id).repeats.

Handoff items from the converter review (must handle)

  • About 60 converted cells hold several contents (e.g. Skull Woods' entrance cell: big-chest + teleport; Hera's boss cell: stairs + hole + teleport). One cell is one room type: define a precedence (entrance > boss > stairs/hole/teleport links > Big Key / Big Chest > key drop fight > chests > puzzle > enemies) and move the displaced content to the nearest free cell of the same LTTP room, else the nearest free cell on the floor, growing the grid by one row/column only if none is free. Record every move in classicReport(id).moves and test that nothing is dropped (every chest, key, map, compass, Big Key and Big Chest of the JSON appears exactly once in the dressed temple).
  • GT Quad Pot (moon 3F, 12,24) has enemies: 3 that are all minibosses; do not count it as a must-clear monster from enemies > 0 alone (Storm does not hit this, but write the rule now so Task 8's Moon does).
  • Boss rooms reached only by stairs or a fall (Hera, Skull, Ice): the Boss Key must still gate the boss. Use a big-key-locked climb (#1484) on the stairs that lead to the boss's floor when the boss is reached by stairs; when it is reached by a hole or teleport, put the Boss door (masterlocked) on the side into the cell the hero must cross before that hole. Test that the solver needs the Boss Key to reach the boss.
  • Sparse floors (Ice B6 17x3 with 5 cells, Hera 3F 17x2): trim each floor's grid to its used bounding box before writing the module.
  • The JSON's entrance floor is named 1F; keep the temple's entrance on 1F (the floor-wake code assumes the entrance floor is where a run starts).

Tests and gate

Run only chemx test tests/unit/ClassicTemples.spec.ts, chemx test tests/unit/ShippedTemplesAudit.spec.ts, chemx test tests/unit/ClassicDungeonJson.spec.ts (and any spec you create). Before committing, run npx --yes [email protected] audit --no-index --unroll > /tmp/audit-t7.txt 2>&1, check no file you touched appears in LINE_BUDGET_FILE / CONTROL_FLOW_INLINE_BOOLEAN / TYPE_COLOCATION, then restore git checkout -- chemx-ratchet.json (the audit rewrites it; never commit it). The Cypress run (Step 6) needs a dev server: use your own worktree's server on its own port and stop it when done; if that is not possible, write the spec, report that it was not run, and mark DONE_WITH_CONCERNS.

Global Constraints (part 2)

  • You work in your own isolated git worktree branched from origin/master; run pnpm install --frozen-lockfile --prefer-offline first. Commit on your branch only; the lead merges it into feat/classic-temples-2 and ships one PR. Never touch /home/xopher/www/x/do-it-for-the-xp itself (other sessions share it). Never git stash.
  • Use chemx only. Run only the spec(s) your brief names, one at a time: chemx test tests/unit/<Name>.spec.ts. Never raw pnpm test|typecheck|lint|jest or npx jest, never a bare chemx test, never chemx verify, no typecheck/lint/build (the lead runs those once per wave; CI runs the suite on the PR).
  • Keep every file you create or grow under 500 lines (the repo's audit ratchet counts files over 500 as LINE_BUDGET_FILE). Name inline conditions in ifs (const isX = ...; if (isX)), and give object-literal parameter types a named interface (TYPE_COLOCATION) — the ratchet blocks the PR otherwise.
  • Temple ids, names, regions and themes don't change (wind-temple, earth-temple, …). Theme = id.replace("-temple", "") against BEAST_LIBRARY[].theme.
  • Game rules go in pure modules under src/lib/services/** (or src/lib/engine/**) with jest specs; screens stay thin. New components use <script setup lang="ts">; pages keep defineComponent with a sibling .ts/.scss.
  • In-world text comes from the copy catalogue (src/lib/services/copy/catalog/, three voices) through useCopy(); in-world prompts use useRevealPrompt(), never Ionic alerts. Sounds use play$fx(<key of src/assets/fx/index.ts>).
  • Original beasts 001–093 are family art: never overwrite or re-point them.
  • Quest difficulty is Fibonacci: must-clear monsters 1–2, minibosses 3–5, boss 8–13. Schedules follow SCHEDULES (daily 19:00, am 08:00, bed 20:30, school weekdays, weekly 18:00, monthly custom).
  • Every door into a boss room is a Boss door (isBossRoom in authoredDoors.ts). Bombs never open locked doors.
  • Keep comment density and naming like the surrounding code (long, plain-English doc comments).
  • Do not dispatch subagents of any kind, and never a reviewer.

Notes from the locked-climb task (#1484, merged before you start)

  • The field is content.locked: 'small' | 'master' on a stairs-up / stairs-down room: the climb FROM that room is locked; its landing on the other floor climbs back freely. Check the final names in master (chemx q isClimbUnlocked, chemx q climbKey).

  • A locked flight needs its own room token: a shared token would lock every cell that uses it. Its landing stairs on the other floor must not reuse the locked token, or the climb back would be locked too. Test this.

  • TempleGrounds.ts is exactly 500 lines: do not grow it.

  • Never write content.locked on a loot room: DungeonManager reads it there as "chest locked".

⚠️ **GitHub.com Fallback** ⚠️