A dungeon now names the boss arena it ends in, and the arena decides the boss. Three entrances in the hub: Warden's Descent, The Choir Vault, Proving Grounds. That replaces the seed coin-flip from the last commit, which was the wrong call. Which boss you are about to fight is the single thing a player decides before walking into a dungeon, and rolling it for them makes that decision unavailable. Only the Proving Grounds still leaves its arena open, because a harness you re-enter every couple of minutes wants whichever fight comes up first -- and it is the one place where "either will do" is true. test_every_boss_has_a_dungeon_that_reaches_it is the check that matters here: adding a boss and forgetting to give it a way in is now a failing test rather than a boss nobody meets. The smoke test asserts all three portals open over a real socket -- bots pick theirs by account id, so a run exercises each. Bullets no longer animate. The sheet's eight frames are a COLOUR cycle rather than a shape change, so running it had every bullet on screen strobing through a palette in unison, which with a few hundred in the air is exactly as hard to look at as it sounds. Each bullet holds one frame and turns slowly instead, offset by its own id so a ring of twenty does not rotate as one rigid wheel. The rate is slow enough that nothing completes a full turn inside its own lifetime, so it reads as drift rather than spin, and a test pins that against the longest-lived bullet in the game. The renderer slices four textures at startup now instead of thirty-two. check.sh clean, 414 tests, SMOKE PASS (22 assertions), all four diagnostics green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
42568a40ad
commit
4a98cf4b0e
15 files changed
+251
-100
No files matched your search
+16
-10
@@ -19,10 +19,11 @@ extends RefCounted
|
||||
## Generation is still deterministic from (kind, seed, depth), which is what
|
||||
## makes a bad run reproducible from its log line; tests/unit/test_map_gen.gd
|
||||
## pins that.
|
||||
static func build(kind: Protocol.InstanceKind, seed_value: int, depth: int) -> Dictionary:
|
||||
static func build(kind: Protocol.InstanceKind, seed_value: int, depth: int,
|
||||
arena: StringName = &"") -> Dictionary:
|
||||
if kind == Protocol.InstanceKind.LOBBY:
|
||||
return _build_lobby()
|
||||
return generate(seed_value, depth)
|
||||
return generate(seed_value, depth, arena)
|
||||
|
||||
|
||||
static func _build_lobby() -> Dictionary:
|
||||
@@ -71,7 +72,11 @@ static func _build_lobby() -> Dictionary:
|
||||
|
||||
## Result keys: grid, rooms (Array[Rect2i]), spawn (Vector2), boss_pos
|
||||
## (Vector2), boss_room (Rect2i).
|
||||
static func generate(seed_value: int, depth: int) -> Dictionary:
|
||||
## [param arena] names the boss arena to stamp, and therefore which boss the run
|
||||
## ends with. Empty leaves it to the seed, which is what the Proving Grounds
|
||||
## wants and nothing else does -- for a real dungeon the boss is the thing a
|
||||
## player chose at the portal.
|
||||
static func generate(seed_value: int, depth: int, arena: StringName = &"") -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed_value
|
||||
var d := maxi(depth, 1)
|
||||
@@ -86,16 +91,17 @@ static func generate(seed_value: int, depth: int) -> Dictionary:
|
||||
|
||||
# The boss arena is placed first and everything else works around it, so a
|
||||
# generated corridor can never carve through the authored fight.
|
||||
# Which arena -- and therefore which boss -- comes from the SEED, not the
|
||||
# depth. Depth is a dev flag that nothing in play raises, so keying the
|
||||
# arena to it meant the second boss existed and no player could ever reach
|
||||
# it. Every run rolls a fresh seed, so this is a coin flip per dungeon.
|
||||
var arena := &"warden_hall" if posmod(seed_value, 2) == 0 else &"choir_vault"
|
||||
var stamp := Rooms.warden_hall() if arena == &"warden_hall" else Rooms.choir_vault()
|
||||
# Normally the dungeon says which arena it is; only the test harness leaves
|
||||
# it open, and then the seed decides.
|
||||
var chosen := arena
|
||||
if chosen.is_empty():
|
||||
chosen = &"warden_hall" if posmod(seed_value, 2) == 0 else &"choir_vault"
|
||||
var stamp := Rooms.warden_hall() if chosen == &"warden_hall" else Rooms.choir_vault()
|
||||
var bs := Rooms.size_of(stamp)
|
||||
var boss_origin := Vector2i(w - bs.x - 2, (h - bs.y) / 2)
|
||||
var markers := Rooms.stamp(grid, stamp, boss_origin)
|
||||
var boss_room := Rect2i(boss_origin, bs)
|
||||
var arena_id := chosen
|
||||
|
||||
var boss_pos := grid.tile_centre(
|
||||
boss_origin.x + bs.x / 2, boss_origin.y + bs.y / 2)
|
||||
@@ -165,7 +171,7 @@ static func generate(seed_value: int, depth: int) -> Dictionary:
|
||||
# Which boss lives here follows from WHICH arena was stamped. Choosing
|
||||
# it anywhere else would let a dungeon put the Cantor in the Warden's
|
||||
# hall, where its patterns assume geometry that is not there.
|
||||
"boss_id": Rooms.boss_for_arena(arena),
|
||||
"boss_id": Rooms.boss_for_arena(arena_id),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user