Sandbox target dummy: planted EnemyTag ghost + DevSandbox respawn spawner

GymSub bakes no WaveDirector (why sandbox enemies never spawned); the
sandbox's combat target is now a TargetDummyTag'd enemy ghost all five
EnemyAISystem passes exclude (planted; knockback stamps inert), spawned
at player+3m with 400 HP by an editor-only scene-gated server system,
dying through the normal Dying path and respawning 1.5s after the corpse.
EnemyTag-reuse audited: sandbox-only by design (cleared-checks would
count it elsewhere).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 00:13:47 -07:00
parent cd634a9df5
commit 2710d680b4
5 changed files with 108 additions and 5 deletions
@@ -120,14 +120,14 @@ namespace ProjectM.Server
if (sweep)
{
foreach (var depenXform in SystemAPI.Query<RefRW<LocalTransform>>()
.WithAll<EnemyTag>().WithNone<Dying, BossState>())
.WithAll<EnemyTag>().WithNone<Dying, BossState>().WithNone<TargetDummyTag>())
depenXform.ValueRW.Position = EnemyMoveUtil.Depenetrate(in physics, depenXform.ValueRO.Position, SweepRadius, envFilter);
}
foreach (var (xform, stats, cooldown, knockback, windup, region) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemyStats>, RefRW<EnemyAttackCooldown>,
RefRW<KnockbackState>, RefRW<AttackWindup>, RefRO<RegionTag>>()
.WithAll<EnemyTag>().WithNone<LungeState, SpitterState, Dying>())
.WithAll<EnemyTag>().WithNone<LungeState, SpitterState, Dying>().WithNone<TargetDummyTag>())
{
float3 pos = xform.ValueRO.Position;
byte huskRegion = region.ValueRO.Region;
@@ -398,7 +398,7 @@ namespace ProjectM.Server
foreach (var (xform, stats, knockback, windup, spitter, region) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemyStats>, RefRW<KnockbackState>,
RefRW<AttackWindup>, RefRW<SpitterState>, RefRO<RegionTag>>()
.WithAll<EnemyTag, SpitterState>().WithNone<LungeState, Dying>())
.WithAll<EnemyTag, SpitterState>().WithNone<LungeState, Dying>().WithNone<TargetDummyTag>())
{
float3 pos = xform.ValueRO.Position;
byte sRegion = region.ValueRO.Region;
@@ -503,7 +503,7 @@ namespace ProjectM.Server
// Charger whose bit is currently DISABLED is still visited (Entities default-excludes disabled enableables).
foreach (var (lunge, isLunging) in
SystemAPI.Query<RefRO<LungeState>, EnabledRefRW<IsLunging>>()
.WithAll<EnemyTag>().WithPresent<IsLunging>().WithNone<Dying>())
.WithAll<EnemyTag>().WithPresent<IsLunging>().WithNone<Dying>().WithNone<TargetDummyTag>())
{
isLunging.ValueRW = lunge.ValueRO.UntilTick != 0u; // lunging iff a committed lunge is live this tick
}
@@ -591,7 +591,7 @@ namespace ProjectM.Server
float nudgeStep = UnstickNudgeSpeed * dt;
foreach (var (nxform, nstats, nav, nregion, nent) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemyStats>, RefRW<EnemyNavState>, RefRO<RegionTag>>()
.WithAll<EnemyTag>().WithNone<SpitterState, BossState, Dying>().WithEntityAccess())
.WithAll<EnemyTag>().WithNone<SpitterState, BossState, Dying>().WithNone<TargetDummyTag>().WithEntityAccess())
{
float3 npos = nxform.ValueRO.Position;
byte nRegion = nregion.ValueRO.Region;