Boss gate: the exit gate fails closed; it opens only for a boss that spawned and then died

ZonePack.BossSpawned records the spawn, since a dead boss leaves no entity. RunGoal.ExitOpen requires it, so an exit zone
without a boss stays sealed, and ZoneGenerator logs that configuration by field and code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-07 06:41:33 +02:00
commit c8d40eaf74
5 changed files with 59 additions and 2 deletions

View file

@ -11,9 +11,10 @@ public static class RunGoal
public static bool BossAlive(Zone zone) => Boss(zone) != null;
// The exit gate is open in the galaxy's exit zone once that zone holds no living boss.
// The exit gate fails closed: it is open only in the galaxy's exit zone, once a boss was spawned there and no living
// boss remains. An exit zone that never got a boss stays sealed, so a missing boss can never read as a dead one.
public static bool ExitOpen(Zone zone) =>
zone.Galaxy?.Exit != null && zone.GalaxyZone == zone.Galaxy.Exit && !BossAlive(zone);
zone.Galaxy?.Exit != null && zone.GalaxyZone == zone.Galaxy.Exit && zone.Pack.BossSpawned && !BossAlive(zone);
// Where the exit gate sits: at the adjacency wormholes' radius (the zone's radius times the ratio), in the middle of
// the widest angular gap between them, so it shares a point with none. A zone with no neighbours gets the +x axis.

View file

@ -182,6 +182,7 @@ public class Zone
Orbits = Orbits.Keys.Select(key => new CultRecordRef<OrbitData>(key)).ToList(),
Planets = Planets.Keys.Select(key => new CultRecordRef<BodyData>(key)).ToList(),
Time = _time,
BossSpawned = Pack.BossSpawned,
// Cut 2 (docs/mining-cut.md): drop entries that carry no live information -- healed (no damage) and
// not currently broken -- so a save never accumulates wear rows for chunks nobody has touched since
// they last respawned.

View file

@ -43,6 +43,11 @@ public class ZonePack
// default instance.
[JsonProperty("chunkWear"), Key(6)]
public List<ChunkWearPack> ChunkWear;
// True once this zone's boss has been spawned. The dead boss leaves no entity, so this is the one fact that tells a
// slain boss from one that never existed; RunGoal.ExitOpen reads it. Written only by ZoneGenerator, carried by PackZone.
[JsonProperty("bossSpawned"), Key(7)]
public bool BossSpawned;
}
// Cut 2 (docs/mining-cut.md): one packed wear entry for one chunk. "Field" names the body that owns the chunk

View file

@ -349,8 +349,12 @@ public static class ZoneGenerator
}
boss.Boss = true;
pack.Entities.Add(boss);
pack.BossSpawned = true;
}
if (galaxyZone == galaxy.Exit && !pack.BossSpawned)
itemManager.Log("Galaxy.Exit has no boss: Galaxy.BossZones does not map a boss faction to the exit zone, or its boss could not be generated (ZoneGenerator exit-without-boss). The exit gate stays sealed.");
if (!ambient) return pack;
var enemyCount = (int)(random.NextFloat() * factionPresence * 2) + baseStationCount;