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
@@ -0,0 +1,47 @@
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// EB-1 — static live-tunable knobs for <see cref="StructureFeedbackSystem"/> (structure damage chips +
/// destruction bursts). A presentation-only bridge (mirrors <c>WorldFeelConfig</c>); reset on play-enter via
/// <see cref="RuntimeInitializeOnLoadMethod"/> so poked values never leak across fast-enter-playmode sessions.
/// Read only on the main thread by the managed feedback system, never from Burst.
/// </summary>
public static class StructureFeelConfig
{
public static bool Enabled = true;
/// <summary>A despawn farther than this from the local player does NOT fire a death burst — so the
/// base->expedition RegionRelevancy despawn (all base structures drop at once) stays silent.</summary>
public static float ProximityRange = 45f;
public static int ChipBurstCount = 8;
public static int DeathBurstCount = 40;
public static float ChipSfxVolume = 0.25f;
public static float DeathSfxVolume = 0.6f;
// A LOUD, low-frequency punch is reserved for a structure DEATH only; per-chip feedback is camera-silent so
// a wave of hits never sustains a nauseating shake (AddShake clamps cumulatively, PunchFov takes a max).
public static float DeathFovKick = 5.5f;
public static float DeathShake = 0.35f;
public static Color DamageTint = new Color(2.4f, 1.4f, 0.4f); // amber HDR spark on a hit
public static Color DeathTint = new Color(3.0f, 0.7f, 0.25f); // red-orange HDR loss burst
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetDefaults()
{
Enabled = true;
ProximityRange = 45f;
ChipBurstCount = 8;
DeathBurstCount = 40;
ChipSfxVolume = 0.25f;
DeathSfxVolume = 0.6f;
DeathFovKick = 5.5f;
DeathShake = 0.35f;
DamageTint = new Color(2.4f, 1.4f, 0.4f);
DeathTint = new Color(3.0f, 0.7f, 0.25f);
}
}
}