EB-1: machines can die - structures get HP, Husks raze them, wounded base persists

Structures (Turret/Wall/Pylon) reuse the combat spine: authoring bakes Health(GhostField)+DamageEvent buffer+a Destructible tag (no HitRadius -> no friendly projectile fire; no EffectiveCharacterStats -> clamp-to-0). HealthApplyDamageSystem destroys a Destructible at 0 (occupancy auto-frees). EnemyAISystem fortress-targets the weighted-nearest of players+structures via the shared EnemyAIMath.PickWeightedNearest (StructureAggroWeight TuningConfig knob, <1 prefers structures, squared factor; snapshot above the early-return so an undefended base is razed). Persistence v3: per-structure HP threaded through 5 sites (SaveData/PendingStructure/scan-guarded/BaseRestore same-ECB born-correct/WorldLauncher via SaveApply.ToPending); SaveService floor-gate [2,3] loads old saves. Loss feedback: proximity-gated StructureFeedbackSystem; CombatFeedbackSystem suppressed for structures. Pre-code review caught the DamageEvent-buffer crash blocker + 8 majors; post-code review clean. See DR-032.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:53:34 -07:00
parent 35d33f12c1
commit 73cfe2943d
21 changed files with 426 additions and 39 deletions
@@ -120,11 +120,7 @@ namespace ProjectM.Client
var se = em.CreateEntity();
var sbuf = em.AddBuffer<PendingStructure>(se);
foreach (var s in data.Structures)
sbuf.Add(new PendingStructure
{
Type = s.Type, CellX = s.CellX, CellZ = s.CellZ, Direction = s.Direction,
RemainingTicks = s.RemainingTicks, ConveyorResId = s.ConveyorResId, ConveyorCount = s.ConveyorCount,
});
sbuf.Add(SaveApply.ToPending(s)); // EB-1: pure mapping (unit-tested, incl. the wounded HP)
var iobuf = em.AddBuffer<PendingStructureIo>(se);
if (data.StructureIo != null)
foreach (var io in data.StructureIo)