Skip to content

Bullet Heaven — sim architecture

This is the current ownership map for the deterministic /sim layer. The sim is a ~3,000-line conductor plus focused RefCounted modules, not one monolith. The module split originally extracted eleven modules from a 4,784-line sim/sim.gd; the conductor has regrown since then, so use the current file locations below rather than the historical plan as a map.

Gameplay logic lives in:

  • sim/boss_rotation.gd — boss-index lookups and any_boss_alive / is_boss_type / hp_scale. The old rotation dispatch was removed from sim.gd.
  • sim/boss_warden.gd, sim/boss2.gd, sim/funzo.gd, sim/graviton.gd, and sim/eye.gd — one director per boss.
  • sim/drone_director.gd — drones and the decoy.
  • sim/enemy_behaviors.gd — movement steppers.
  • sim/enemy_attacks.gd — ranged attack loops.
  • sim/elemental_system.gd — damage, collision, reactions, zones, and webs. damage_enemy, resolve_collisions, and apply_element live here without a leading underscore. _sweep_dead remains on Sim.
  • sim/upgrade_system.gd — upgrade rolling, application, evolutions, and display data. Its level-up resolvers are apply_upgrade, reroll_choices and skip_upgrade (issue #77): render-side, called by main (and ReplayDriver) between ticks, never by the tick. They take an explicit pilot, draw only from upgrade_rng, and keep PlayerState.rerolls_used / levelup_skips out of the checksums.

Every module is class_name X extends RefCounted, held as a field on Sim (for example sim.elemental_system), and takes sim: Sim as its explicit first parameter, matching the older SpawnDirector and StoryDirector pattern.

Boss2, FunZo, Graviton, and Eye directors are named boss2_director, funzo_director, graviton_director, and eye_director. Sim already has boss2, funzo, graviton, and eye fields containing each boss’s attack-pattern state (Boss2State, etc.). For example, sim.funzo.hp is state, while sim.funzo_director.spawn(sim, ...) invokes the director.

Most tunable constants remain on Sim because modules share them, including SHOOTER_PROJ_RADIUS, FIRE_RATE_CAP, and RULESET_SURVIVAL. Move a constant only when it is confirmed exclusive to one module. When extending a system, grep the owning module first; sim.gd may only contain the conductor call site.