Reconcile radio-delivered body and status casualties by original roster identity

This commit is contained in:
2026-09-20 03:09:16 +02:00
parent a2aab17171
commit 9b6596eb3e
44 changed files with 6042 additions and 86 deletions
@@ -0,0 +1,234 @@
#ifdef WORKBENCH
// Controlled scene setup only: reuse two already admitted formations, relocate
// and kill one original non-radio member. Production LOS, snapshots and radio
// delivery must establish knowledge; this is not organic patrol-discovery proof.
class ERR_NativeHQBodySetup
{
ref ERR_HQUnit VictimUnit;
ref ERR_HQUnit ObserverUnit;
IEntity Actor;
string SourceId;
int InitialSlot;
int InitialStrength;
float SavedNextStatus;
int StatusMessageId;
float StatusObservedAt = -1;
vector StatusPosition;
}
modded class ERR_HQ
{
protected bool ERR_BodyFixtureHealthy(ERR_HQUnit unit)
{
if (!unit || !unit.Eligible(m_Mode) || !unit.StatusSampler.Bound() || !unit.Radio || unit.Uplink
|| unit.PendingReport || unit.PendingStatus || unit.PendingSupplyStatus || unit.PendingSweep) return false;
ERR_HQRoute route = m_Network.Route(unit.Radio);
if (!route || route.Nodes.IsEmpty()) return false;
for (int i = 0; i < route.Nodes.Count(); i++)
if (!route.Nodes[i] || !route.Nodes[i].Valid(i == route.Nodes.Count() - 1)) return false;
return true;
}
ERR_NativeHQBodySetup ERR_BodyFixturePrepare(float now)
{
if (!ERR_NativeHQBodyProbe.Enabled()) return null;
foreach (ERR_HQUnit observer : m_Units)
{
if (!observer.HomeGuard || !ERR_BodyFixtureHealthy(observer)) continue;
bool originalObserver;
for (int observerSlot = 0; observerSlot < observer.StatusSampler.InitialStrength(); observerSlot++)
if (observer.StatusSampler.InitialMember(observerSlot) == observer.Radio
&& observer.StatusSampler.CanAttribute(observerSlot, observer.Radio)) originalObserver = true;
if (!originalObserver) continue;
foreach (ERR_HQUnit victim : m_Units)
{
if (victim == observer || victim.StatusSampler.InitialStrength() < 2 || !ERR_BodyFixtureHealthy(victim))
continue;
string sourceId = victim.StatusSampler.InitialSourceId();
ERR_HQWatchEntry status = m_Watch.Find(sourceId);
if (status && status.ConfirmedLosses != 0) continue;
for (int slot = 0; slot < victim.StatusSampler.InitialStrength(); slot++)
{
IEntity actor = victim.StatusSampler.InitialMember(slot);
if (!actor || actor == victim.Radio || !ERR_Actors.CanFight(actor)
|| !victim.StatusSampler.CanAttribute(slot, actor) || CompartmentAccessComponent.GetVehicleIn(actor)) continue;
ERR_NativeHQBodySetup fixture = new ERR_NativeHQBodySetup();
fixture.VictimUnit = victim; fixture.ObserverUnit = observer; fixture.Actor = actor;
fixture.SourceId = sourceId; fixture.InitialSlot = slot;
fixture.InitialStrength = victim.StatusSampler.InitialStrength();
fixture.SavedNextStatus = victim.NextStatus;
// Isolate the BODY uplink from the deceased member's own group check-in.
// No report/route is discarded: selection required that group to be idle.
victim.NextStatus = now + 180000;
if (status)
{
fixture.StatusMessageId = status.MessageId; fixture.StatusObservedAt = status.ObservedAt;
fixture.StatusPosition = status.Position;
}
return fixture;
}
}
}
return null;
}
ERR_HQWatchEntry ERR_BodyFixtureWatch(string sourceId)
{
if (!ERR_NativeHQBodyProbe.Enabled()) return null;
return m_Watch.Find(sourceId);
}
bool ERR_BodyFixtureContact(IEntity actor)
{
foreach (ERR_HQReport report : m_Contacts) if (report.Target == actor) return true;
return false;
}
// Invoke the real production observer/queue method. It must do its own native LOS
// and physical route lookup. The fixture does not construct or enqueue a report.
ERR_HQUnit ERR_BodyFixturePending(ERR_NativeHQBodySetup fixture, float now)
{
if (!ERR_NativeHQBodyProbe.Enabled() || !fixture) return null;
ObserveBody(fixture.ObserverUnit, now);
foreach (ERR_HQUnit unit : m_Units)
if (unit.PendingReport && unit.Uplink && unit.PendingReport.Target == fixture.Actor
&& unit.PendingReport.TravelMode == "body") return unit;
return null;
}
}
class ERR_NativeHQBodyProbe
{
protected ERR_GameMode m_Mode;
protected ref ERR_NativeHQBodySetup m_Setup;
protected ref ERR_HQReport m_Report;
protected ref ERR_HQRoute m_Route;
protected ref ERR_HQStatusReport m_Status;
protected ref ERR_HQRoute m_StatusRoute;
protected float m_Deadline;
protected float m_StableUntil;
protected int m_Step;
protected bool m_Failed;
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1"
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "hq-body";
}
void ERR_NativeHQBodyProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: HQ body " + detail); return true; }
m_Failed = true; Print("ERR SMOKE FAIL: HQ body " + detail, LogLevel.ERROR); return false;
}
// Get(target) intentionally exposes SIGHTING only. Inspect the bounded detached
// evaluations to locate this exact BODY report without changing memory semantics.
protected ERR_HQBelief BodyBelief(ERR_HQ hq, IEntity actor, int messageId)
{
for (int i = 0; i < hq.Memory().Count(); i++)
{
ERR_HQBelief belief = hq.Memory().At(i);
if (belief && belief.Kind == ERR_HQMemoryKind.BODY && belief.Target == actor
&& belief.MessageId == messageId) return belief;
}
return null;
}
protected bool NoLoss(ERR_HQ hq)
{
ERR_HQWatchEntry status = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
return !status || status.ConfirmedLosses == 0;
}
int Tick(ERR_HQ hq, float now)
{
if (!Enabled() || !hq || !m_Mode || m_Failed) return -1;
if (m_Deadline == 0) m_Deadline = now + 150000;
if (now > m_Deadline) { Check(false, "bounded native LOS/radio fixture deadline"); return -1; }
if (m_Step == 0)
{
if (!hq.ERR_ProbeSquadsReady()) return 0;
m_Setup = hq.ERR_BodyFixturePrepare(now);
if (!m_Setup) return 0;
if (!Check(m_Setup.VictimUnit.Group != m_Setup.ObserverUnit.Group
&& ERR_Actors.Group(m_Setup.Actor) == m_Setup.VictimUnit.Group,
"two distinct existing physical formations and immutable victim slot selected")) return -1;
// Explicit fixture arrangement. One native corpse, no new actor/group/item.
vector position = ERR_Actors.Ground(m_Setup.ObserverUnit.Radio.GetOrigin() + "0 0 5");
m_Setup.Actor.SetOrigin(position);
SCR_CharacterDamageManagerComponent damage;
damage = SCR_CharacterDamageManagerComponent.Cast(m_Setup.Actor.FindComponent(SCR_CharacterDamageManagerComponent));
if (!Check(damage != null, "original fixture actor has native character damage")) return -1;
damage.EnableDamageHandling(true);
damage.Kill(Instigator.CreateInstigator(m_Setup.Actor));
if (!Check(NoLoss(hq), "local fixture death has not informed HQ")) return -1;
m_Step = 1; return 0;
}
if (m_Step == 1)
{
if (!NoLoss(hq)) { Check(false, "uncaptured delivery preceded pending-payload inspection"); return -1; }
ERR_HQUnit reporter = hq.ERR_BodyFixturePending(m_Setup, now);
if (!reporter) return 0;
m_Report = reporter.PendingReport.Snapshot(); m_Route = reporter.Uplink;
if (!Check(m_Report.MessageId > 0 && m_Report.Source == reporter.Group
&& m_Report.Target == m_Setup.Actor && m_Report.CasualtySourceId == m_Setup.SourceId
&& m_Report.CasualtySlot == m_Setup.InitialSlot && m_Report.CasualtyStrength == m_Setup.InitialStrength,
"actual native LOS produced body snapshot with original formation/slot attribution")) return -1;
if (!Check(m_Route.MessageId == m_Report.MessageId && !m_Route.Nodes.IsEmpty()
&& m_Route.Nodes[0].Carrier == reporter.Radio && ERR_Actors.CanFight(reporter.Radio)
&& !m_Report.HasVelocity && !m_Report.Distress && m_Report.TravelMode == "body",
"living observer formation queued its actual radio route without target-motion privilege")) return -1;
if (!Check(NoLoss(hq), "pending radio payload still gives HQ no casualty knowledge")) return -1;
m_Step = 2; return 0;
}
if (m_Step == 2)
{
if (m_Route.Failed) { Check(false, "actual body uplink failed before delivery"); return -1; }
if (m_Route.Cursor < m_Route.Nodes.Count() || m_Route.Sending)
{
if (!NoLoss(hq)) { Check(false, "in-transit body report created early knowledge"); return -1; }
return 0;
}
ERR_HQWatchEntry status = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
ERR_HQBelief belief = BodyBelief(hq, m_Setup.Actor, m_Report.MessageId);
if (!Check(status && status.ConfirmedLosses == 1 && !status.Closed,
"completed native radio uplink records exactly one original casualty")) return -1;
if (!Check(status.MessageId == m_Setup.StatusMessageId && status.ObservedAt == m_Setup.StatusObservedAt
&& status.Position == m_Setup.StatusPosition,
"body delivery preserves the formation's independent status chronology and position")) return -1;
if (!Check(belief && belief.Kind == ERR_HQMemoryKind.BODY && belief.MessageId == m_Report.MessageId
&& !belief.HasVelocity && !hq.ERR_BodyFixtureContact(m_Setup.Actor),
"delivered body is uncertain investigation memory without a firing contact")) return -1;
// Resume a real surviving caller's own cumulative status for overlap proof.
m_Setup.VictimUnit.NextStatus = now;
m_Step = 3; return 0;
}
if (m_Step == 3)
{
ERR_HQUnit victim = m_Setup.VictimUnit;
if (!victim.PendingStatus || !victim.Uplink) return 0;
m_Status = victim.PendingStatus; m_StatusRoute = victim.Uplink;
if (!Check(m_Status.SourceId == m_Setup.SourceId && m_Status.ConfirmedLosses == 1
&& m_Status.DeathSlots.Count() == 1 && m_Status.DeathSlots[0] == m_Setup.InitialSlot,
"real surviving caller independently reports the same original casualty slot")) return -1;
m_Step = 4; return 0;
}
if (m_Step == 4)
{
if (m_StatusRoute.Failed) { Check(false, "actual overlapping status uplink failed"); return -1; }
if (m_StatusRoute.Cursor < m_StatusRoute.Nodes.Count() || m_StatusRoute.Sending) return 0;
ERR_HQWatchEntry status = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
if (!Check(status && status.MessageId == m_Status.MessageId && status.ConfirmedLosses == 1
&& status.Count == m_Setup.InitialStrength - 1,
"delivered cumulative status overlaps body evidence without double-counting")) return -1;
m_StableUntil = now + 20000; m_Step = 5; return 0;
}
ERR_HQWatchEntry stable = hq.ERR_BodyFixtureWatch(m_Setup.SourceId);
if (!stable || stable.ConfirmedLosses != 1 || hq.ERR_BodyFixtureContact(m_Setup.Actor))
{ Check(false, "confirmed corpse lost idempotence or became a firing contact"); return -1; }
if (now < m_StableUntil) return 0;
Check(true, "confirmed corpse remains one loss with no firing contact after twenty further seconds");
return 1;
}
void Stop()
{
if (m_Setup && m_Setup.VictimUnit && m_Step < 3)
m_Setup.VictimUnit.NextStatus = m_Setup.SavedNextStatus;
}
void ~ERR_NativeHQBodyProbe() { Stop(); }
}
#endif
+25 -2
View File
@@ -17,6 +17,8 @@ modded class ERR_GameMode
protected bool m_ERRTestRadioNetwork;
protected bool m_ERRTestHQResponse;
protected bool m_ERRTestHQMemory;
protected bool m_ERRTestHQBody;
protected ref ERR_NativeHQBodyProbe m_ERRHQBodyProbe;
protected bool m_ERRTestConvoy;
protected ref ERR_NativeConvoyProbe m_ERRConvoyProbe;
protected ref ERR_NativeHQMemoryProbe m_ERRHQMemoryProbe;
@@ -59,6 +61,7 @@ modded class ERR_GameMode
m_ERRTestRadioNetwork = outcome == "radio-network";
m_ERRTestHQResponse = outcome == "hq-response";
m_ERRTestHQMemory = outcome == "hq-memory";
m_ERRTestHQBody = outcome == "hq-body";
m_ERRTestConvoy = outcome == "convoy";
m_ERRTestMortar = outcome == "mortar-inventory";
m_ERRTestMapOwnership = outcome == "map-ownership";
@@ -72,10 +75,10 @@ modded class ERR_GameMode
m_ERRTestMortarSalvo = outcome == "mortar-salvo" || outcome == "mortar-refresh";
m_ERRTestMortarTransfer = outcome == "mortar-transfer";
m_ERRTestPatrolHQ = outcome == "patrol-hq";
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar || m_ERRTestHQMemory || m_ERRTestConvoy;
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar || m_ERRTestHQMemory || m_ERRTestHQBody || m_ERRTestConvoy;
m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 150000;
if (m_ERRTestConvoy) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 540000;
if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;
if (m_ERRTestHQMemory || m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;
if (m_ERRTestAircraftControl || m_ERRTestAircraftRoute || m_ERRTestAircraftField) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestMortarCourier) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 450000;
if (m_ERRTestMortarTransfer) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
@@ -514,6 +517,13 @@ modded class ERR_GameMode
m_ERRSmokeStep = 106;
return;
}
if (m_ERRTestHQBody)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRHQBodyProbe = new ERR_NativeHQBodyProbe(this);
m_ERRSmokeStep = 107;
return;
}
if (m_ERRTestHQMemory)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
@@ -694,6 +704,19 @@ modded class ERR_GameMode
damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
m_ERRSmokeWait = now + 4000;
}
else if (m_ERRSmokeStep == 107)
{
int result = m_ERRHQBodyProbe.Tick(ERR_HQ.Cast(Command()), now);
if (result == 0) return;
m_ERRHQBodyProbe.Stop();
if (!ERR_Check(result == 1, "native visual body discovery, delivered casualty and status overlap")) return;
m_ERRSmokeStep = 9;
SCR_CharacterDamageManagerComponent damage;
damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent));
damage.EnableDamageHandling(true);
damage.Kill(Instigator.CreateInstigator(m_ERRSmokePlayer));
m_ERRSmokeWait = now + 4000;
}
else if (m_ERRSmokeStep == 106)
{
int result = m_ERRConvoyProbe.Tick(ERR_HQ.Cast(Command()), now);