{ "title": "Split reverse_survivors src/core/sim.ts under the 250-line cap", "objective": "Bring reverse_survivors/src/core/sim.ts (361 lines) under the repo-wide 250-line file cap by splitting the simulation tick along actor lines into an acyclic three-module graph -- a leaf holding shared geometry and enemy construction, a survivor module, and a threats module for enemies and projectiles -- leaving sim.ts owning state construction and the step orchestration, with behaviour proven by the project's own 160-test suite at 100% coverage and by the same npm run check plus npm run build that CI runs.", "acceptance_criteria": [ "sim.ts and all three new modules are each at most 250 lines, and check_file_length.sh --all no longer lists src/core/sim.ts.", "The new import graph is acyclic and introduces no cycle: simUnit.ts imports nothing from core/ except types and vec; simSurvivor.ts imports simUnit but never sim.ts or simThreats.ts; simThreats.ts imports simUnit and simSurvivor but never sim.ts. Verified by grep, since this repo has no eslint-plugin-import and neither tsc nor vitest would catch a cycle -- it would surface as a TDZ error only in the built bundle.", "npm run check (typecheck && eslint . --max-warnings 0 && vitest run --coverage) passes: 13 test files, 160 tests, and 100% statements/branches/functions/lines with the same 531/159/101/514 totals, since no statement is added or removed.", "npm run build (tsc --noEmit && vite build) succeeds, which is the only gate that would expose an import cycle. Module count rises from 29 to 32 for the three new files; bundle size stays within a few bytes of the 210.11 kB baseline.", "Every importer of a moved symbol imports it from the module that now defines it, with no re-export barrel left in sim.ts.", "Zero suppressions added: no eslint-disable, no @ts-ignore, no @ts-expect-error, and no coverage threshold or exclude entry changed." ], "out_of_scope": [ "Any behaviour change to the simulation: the flee-and-drift survivor movement, the multishot angular spread, the deterministic non-RNG split angles, the frenzy damage and speed channels, the contact cooldown and the win/lose conditions all keep their current semantics exactly.", "Changing the seeded RNG draw order, which would shift every replay and every seeded assertion.", "Renaming any exported symbol; only the module a symbol lives in changes.", "Splitting src/core/types.ts (295 lines) -- it is a separate commit.", "Wiring the file-length pre-commit hook -- it must land last." ], "verifier": "npm run check (expect 13 files / 160 tests passed and 100% on all four metrics, totals 531/159/101/514); npm run build (expect success, 32 modules transformed); wc -l on sim.ts and the three new modules (expect all under 250); grep of each new module's import lines to confirm no edge points back into sim.ts; bash ~/utils/scripts/check_file_length.sh --all (expect src/core/sim.ts absent); grep -rn 'eslint-disable\\|ts-ignore\\|ts-expect-error' src/ (expect no hits); pre-commit run --files on every touched file. All captured at HEAD as the baseline and re-run after the split." }