Initial commit: Transcience MVP

Top-down twin-stick bullet-hell, Godot 4.7, server-authoritative dedicated
server with client-side prediction. Clients send input only; the server
resolves every hit for both players and enemies (no PvP).

- SimWorld: whole simulation as plain RefCounted objects (no nodes, no
  physics server), ~0.24ms/tick at peak load -- runs headless for free and
  drives 78 tests in under a second
- BulletPool: struct-of-arrays bullet storage, replicated as spawn/despawn
  events rather than per-tick state
- Emitter framework (Ring/AimedSpread/WallGap/ArcSweep) shared by trash
  enemies and bosses -- a new boss is data in src/content/content.gd, no
  simulation changes
- The Warden of the Fold: stationary 4-phase boss built entirely on that
  format
- Lobby hub with a portal into on-demand dungeon instances; one process
  hosts the hub plus every concurrent dungeon
- Emergency escape: 3s server-owned channel, cancelled by damage
- tools/check.sh, test.sh (GUT), smoke.sh (real server + bot clients over
  ENet), bench.gd; git hooks wired to the same scripts
- docs/ARCHITECTURE.md, NETCODE.md, WORKFLOW.md, ROADMAP.md
This commit is contained in:
2026-09-03 16:03:57 +02:00
commit c4beeae38f
385 changed files with 28725 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
extends GutTest
## The boss format is the thing the MVP has to prove is reusable, so these tests
## are written against [BossDef]/[BossPhase] rather than against the Warden --
## a second boss should pass them unchanged.
var world: SimWorld
func before_each() -> void:
world = SimWorld.new(3)
world.add_player(1, "tester")
world.players[1].pos = Vector2(0.0, 200.0)
func _spawn_warden() -> SimBoss:
return world.spawn_boss(Content.warden())
func test_phase_selection_follows_hp_fraction() -> void:
var def := Content.warden()
assert_eq(def.phase_index_for(1.0), 0)
assert_eq(def.phase_index_for(0.9), 0)
assert_eq(def.phase_index_for(0.5), 1)
assert_eq(def.phase_index_for(0.3), 2)
assert_eq(def.phase_index_for(0.05), 3)
func test_boss_advances_phase_when_damaged() -> void:
var boss := _spawn_warden()
world.step()
assert_eq(boss.phase_index, 0)
boss.hp = int(float(boss.def.max_hp) * 0.5)
world.step()
assert_eq(boss.phase_index, 1)
assert_eq(boss.phase_tick, 1, "entering a phase restarts its timeline")
func test_phase_change_is_announced() -> void:
var boss := _spawn_warden()
world.step()
world.drain_events()
boss.hp = int(float(boss.def.max_hp) * 0.3)
world.step()
var phases := world.events.filter(
func(e: Dictionary) -> bool: return int(e["t"]) == SimEvent.Type.BOSS_PHASE)
assert_eq(phases.size(), 1)
assert_eq(int(phases[0]["phase"]), 2)
func test_telegraph_holds_fire_before_the_phase_starts() -> void:
var boss := _spawn_warden()
var telegraph: int = boss.def.phases[0].telegraph_ticks
for _i in telegraph:
world.step()
assert_eq(world.pool.live_count, 0, "the telegraph window must be silent")
world.step()
assert_gt(world.pool.live_count, 0, "the pattern must start once telegraphed")
func test_the_boss_actually_fills_the_arena() -> void:
_spawn_warden()
for _i in 600:
world.step()
assert_gt(world.pool.live_count, 40,
"a boss phase should keep a real bullet field in the air")
func test_armour_multiplier_scales_incoming_damage() -> void:
var def := Content.warden()
def.phases[0].damage_taken_mult = 0.5
var boss := world.spawn_boss(def)
boss.pos = Vector2.ZERO
world.pool.spawn(Vector2.ZERO, Vector2.ZERO, 4.0, 60, 100,
SimConfig.TEAM_PLAYER, SimConfig.KIND_PLAYER_SHOT)
world.step()
assert_eq(boss.hp, def.max_hp - 50)
func test_boss_death_is_announced_once() -> void:
var boss := _spawn_warden()
boss.pos = Vector2.ZERO
boss.hp = 10
world.pool.spawn(Vector2.ZERO, Vector2.ZERO, 4.0, 60, 100,
SimConfig.TEAM_PLAYER, SimConfig.KIND_PLAYER_SHOT)
world.step()
assert_false(boss.alive)
var deaths := world.events.filter(
func(e: Dictionary) -> bool: return int(e["t"]) == SimEvent.Type.BOSS_DIED)
assert_eq(deaths.size(), 1)
## The point of the format: a boss built from scratch in a test, with no code
## anywhere in the simulation that knows about it, has to work.
func test_a_brand_new_boss_needs_no_engine_changes() -> void:
var ring := RingEmitter.new()
ring.interval = 20
ring.count = 6
var phase := BossPhase.new()
phase.name = "Only Phase"
phase.enter_at_hp_fraction = 1.0
phase.telegraph_ticks = 0
phase.loop_ticks = 100
phase.emitters = [ring]
var def := BossDef.new()
def.id = &"test_boss"
def.display_name = "Test Boss"
def.max_hp = 500
def.radius = 30.0
def.phases = [phase]
world.spawn_boss(def)
world.step()
assert_eq(world.pool.live_count, 6)