{ "intent": "Bring reverse_survivors/src/core/sim.ts (361 lines) under the repo-wide 250-line cap with no change to simulation behaviour, so seeded replays and every assertion about the game loop are unaffected.", "scope": [ "reverse_survivors/src/core/sim.ts — reduced to createInitialState and the step orchestration", "reverse_survivors/src/core/simUnit.ts — new leaf: arena/survivor geometry, projectile constants, SPLIT_RADIUS, CENTER, enemySpeed, makeEnemy", "reverse_survivors/src/core/simSurvivor.ts — new: xpForLevel, createSurvivor, applyUpgrade, grantXp, nearestEnemy, survivorStep", "reverse_survivors/src/core/simThreats.ts — new: frenzyDamageMul, detonate, split, killByShot, enemiesStep, findHit, projectilesStep", "Import sites rewritten: core/director.ts, render/draw.ts, App.test.tsx, render/draw.test.ts, and the four sim*.test.ts files", "Non-goal: any behaviour change, any change to the seeded RNG draw order, any symbol rename" ], "changes": [ "Split along actor lines into a deliberately acyclic graph rather than the obvious two-way cut. A naive 'enemies module' would have needed makeEnemy and grantXp while sim.ts needed enemiesStep back, creating a sim <-> simThreats cycle. Instead simUnit.ts is a leaf importing nothing from core/ but types and vec; simSurvivor.ts builds on it; simThreats.ts builds on both; sim.ts is the only module reaching all three.", "Repointed director.ts's makeEnemy import from sim.ts to simUnit.ts. This removes a pre-existing sim <-> director cycle that had survived only because every symbol is a const arrow called at runtime — a latent hazard this split eliminates as a side effect rather than inherits.", "Rewrote eight import sites to name the owning module. Six are test files; all six edits are import-path-only, with no assertion, seed or expected value changed. Test-file edits are expected in this series at the user's explicit instruction to rewrite import sites rather than leave re-export barrels.", "Function bodies moved verbatim, including the comments recording why split() draws no RNG and why applyStatus is called unconditionally; no statement was added, removed or reordered, which is why coverage totals are unchanged." ], "verification": [ { "command": "npm run check (typecheck && eslint . --max-warnings 0 && vitest run --coverage)", "result": "pass", "evidence": "Test Files 13 passed (13); Tests 160 passed (160); Statements 100% (531/531), Branches 100% (159/159), Functions 100% (101/101), Lines 100% (514/514) — byte-identical totals to the baseline captured at HEAD. This is the exact command reverse-survivors CI runs." }, { "command": "npm run build (tsc --noEmit && vite build)", "result": "pass", "evidence": "Built in 124ms, 32 modules transformed (29 at baseline, +3 for the new files), bundle 210.09 kB vs 210.11 kB baseline. This is the second CI step and the only gate that would expose an import cycle, which would surface as a TDZ error in the bundle rather than as a test or typecheck failure." }, { "command": "grep of each new module's import lines", "result": "pass", "evidence": "simUnit.ts imports only './types' and './vec'; simSurvivor.ts has no import of sim.ts or simThreats.ts; simThreats.ts has no import of sim.ts; the only remaining non-test import of sim.ts is App.tsx at the top of the graph. The graph is a tree." }, { "command": "wc -l src/core/sim.ts src/core/simUnit.ts src/core/simSurvivor.ts src/core/simThreats.ts", "result": "pass", "evidence": "sim.ts 69, simUnit.ts 42, simSurvivor.ts 132, simThreats.ts 168 — all four well under the 250-line cap, down from a single 361-line file." }, { "command": "bash ~/utils/scripts/check_file_length.sh --all", "result": "pass", "evidence": "src/core/sim.ts no longer appears in the over-cap listing; src/core/types.ts (295) is the remaining reverse_survivors entry." }, { "command": "pre-commit run --files ", "result": "pass", "evidence": "All configured hooks pass on the touched files." } ], "risks": [ "The new module names (simUnit, simSurvivor, simThreats) deliberately avoid the stems simEnemies/simUnits/simFrenzy, which are existing *test* files with no matching source. Creating simEnemies.ts beside simEnemies.test.ts would have implied a source/test pairing that does not exist, since those test files all exercise sim.ts as a whole.", "This repo has no eslint-plugin-import, so nothing automatically prevents a future edit from reintroducing a cycle; the acyclicity is verified by grep here and enforced only by vite build failing on the resulting TDZ error.", "simUnit.ts holds constants used by the renderer (FIRE_RANGE, SURVIVOR_RADIUS) as well as by the simulation, so render/draw.ts now depends on a core module named for units. The alternative — duplicating the constants — would let the drawn and simulated radii drift apart." ], "rollback": [ "git revert the commit — five edited files, three added files, and no data, config or schema migration.", "After rollback, re-run npm run check and npm run build; expect 160 tests passing at 100% coverage (531/159/101/514) and a successful build at 29 modules." ] }