Split along actor lines, but not the obvious way. A plain sim/enemies cut
needs makeEnemy and grantXp going out and enemiesStep coming back -- a cycle.
Nothing here would have caught it: no eslint-plugin-import, and tsc and vitest
both pass on cyclic ES modules. It shows up as a TDZ error in the bundle.
So the graph is a tree instead:
simUnit.ts (42) leaf -- geometry, projectile constants, makeEnemy,
enemySpeed; imports only types and vec
simSurvivor.ts (132) createSurvivor, xp/upgrades, survivorStep
simThreats.ts (168) enemiesStep, projectilesStep, and enemy death
sim.ts (69) createInitialState and step, the only module that
reaches all three
Repointing director.ts's makeEnemy at the leaf also removes the sim <->
director cycle that was already there, surviving on const-arrow late binding.
npm run check and npm run build both pass -- 160 tests, coverage identical at
531/159/101/514, 32 modules bundled at 210.09 kB against 210.11 kB before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY