Phase 1 B3: enemies die with a corpse window instead of popping out of existence
Server: HealthApplyDamageSystem marks EnemyTag Dying{UntilTick} (TickUtil.
NonZero, ~54 ticks) on the lethal 0-crossing instead of instant destroy,
zeroes every live cue (replicated AttackWindup, LungeState + IsLunging bit,
KnockbackState), destroys on expiry; plain-world tests keep instant destroy
(no NetworkTime) so the suite's assertions stay meaningful.
Every consumer now ignores corpses (all confirmed entity-count/unfiltered by
the design review): EnemyAISystem all 4 passes, BossAISystem brain + summon
cap, RoomEnemyDirector room-clear + MaxAlive fit, WaveSystem cap + cleared,
CyclePhaseSystem breach wipe + DefendCleared, ThreatDirector timeout cull,
TurretFire targeting, CoreDamage drain, ProjectileDamage snapshot (corpses
are not shields), AbilityFire auto-aim candidates, debug cull + telemetry.
Wipe passes skip Dying to avoid cross-ECB double-destroys.
Client: EnemyAnimationDriveSystem finally drives the controller's IsDead
param (Health.Current<=0 is the replicated death read; corpse locomotion
zeroed); CombatFeedbackSystem moves the kill CRUNCH to the 0-crossing (the
prune-edge timing would land it ~1 s late) - the prune keeps a small
dissolve puff for corpses and the legacy full read for alive-vanish culls.
456/456 EditMode.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,8 +82,9 @@ namespace ProjectM.Simulation
|
||||
foreach (var (tx, th, te) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<Health>>().WithAny<TrainingDummyTag, EnemyTag>().WithEntityAccess())
|
||||
{
|
||||
if (th.ValueRO.Current <= 0f) continue; // B3: corpses are neither aim magnets nor cleave targets
|
||||
candidatePositions.Add(tx.ValueRO.Position);
|
||||
if (th.ValueRO.Current > 0f) { coneTargets.Add(te); coneTargetPos.Add(tx.ValueRO.Position); }
|
||||
coneTargets.Add(te); coneTargetPos.Add(tx.ValueRO.Position);
|
||||
}
|
||||
}
|
||||
var candidates = candidatePositions.AsArray();
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Phase 1 (B3): the enemy corpse window. Stamped ONCE by <c>HealthApplyDamageSystem</c> on the lethal
|
||||
/// 0-crossing (server-only, never replicated — clients derive the death read from the replicated
|
||||
/// <c>Health.Current<=0</c>); the entity is destroyed when ServerTick reaches <see cref="UntilTick"/>.
|
||||
/// While present the enemy is a CORPSE: every actor pass (AI move/windup/contact, boss kit), every
|
||||
/// LIVING count (room clear, HUD Remaining, MaxAlive fit, summon cap, siege cleared) and every target
|
||||
/// snapshot (projectiles, melee, turrets, auto-aim, aggro) must skip it — a corpse neither acts, counts,
|
||||
/// nor soaks hits (design review wf_fd177263: all counts were entity-count based, all snapshots unfiltered).
|
||||
/// </summary>
|
||||
public struct Dying : IComponentData
|
||||
{
|
||||
/// <summary>Server tick the corpse despawns (via <c>TickUtil.NonZero</c>; compared via <c>NetworkTick</c>).</summary>
|
||||
public uint UntilTick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b794b215be5185499725afc93d9d78c
|
||||
Reference in New Issue
Block a user