4.5 KiB
Retaining the native mortar activity outcome
The 11-19-15 refresh fixture produced eight fresh physical commits and eight actual native projectiles under the original order, with three accepted radio updates and 19 original shells remaining. The HQ journal then reported activity state 0 after the eighth shot, and the order eventually failed on its unchanged 120-second timeout. That run is not a qualified full-salvo completion.
Installed vanilla source explains why polling the current action state alone is insufficient:
scripts/Game/AI/Behavior/SCR_AIStaticArtilleryActivity.c,OnChildBehaviorArtilleryFired: the native child event increments the activity's shot count and callsComplete()when its requested count is reached.scripts/Game/AI/Behavior/SCR_AIStaticArtilleryBehavior.c,Internal_OnArtilleryFiredand the shooting-logic node: the native behavior increments its counter and invokes that child event. The successor still permits this notification only after a confirmed physical projectile.scripts/Game/AI/Behavior/SCR_AIAction.c,OnComplete: callsOnActionCompleted()and then invokes the publicm_OnActionCompletedinvoker withthis.OnFailhas the corresponding failure invoker. However,OnActionDeselected()unconditionally sets the current state toEVALUATED.scripts/Game/generated/AI/EAIActionState.c:EVALUATEDis value zero. The installed defender spawner subscribes to this same completion invoker usingOnMoveFinished(SCR_AIActionBase action), confirming the callback argument type.
The failed trace is consistent with completion followed by deselection before HQ's next poll. It did not itself record the native completion event, so that precise historical sequence remains an inference. The subsequent 112900 rerun records the actual native completion event. The fix follows the event contract regardless of the intervening state transitions.
ERR_MortarBattery now subscribes to the exact newly constructed native activity's completion and failure invokers before adding the action. The listener captures its order ID and records only the first terminal event for that exact current activity/order while the battery and command remain active. It logs MORTAR ACTIVITY EVENT with the native terminal event, current state, captured order, physical order/total counts and commit flag. It neither cancels from inside the callback nor increments counts, creates another action, or calls native completion itself.
On the next HQ update, the stored terminal event takes precedence over a later polled EVALUATED state. If no matching event exists, the existing polled terminal-state checks and 120-second timeout remain. Cancellation detaches both listeners before its own cleanup calls Activity.Complete(), so administrative/timeout/round cleanup cannot be recorded as natural success. A late callback from a canceled or replaced activity/order is ignored. The same detach/reset applies when a new order installs an activity.
tests/test_mortar_activity_completion.py executes the actual subscription, callback ownership, terminal selection, cancellation and update bodies with invoker adapters. It covers completion/failure followed by deselection, exact-order and activity mismatches, stopped/closed command, listener removal before cleanup, first-terminal precedence, polling fallback and timeout despite eight physical shots when no native event occurred. A partial native completion leaves the physical counts partial; the fixture must still reject it. tests/test_mortar_refresh.py continues to run the actual scheduler/cancel/projectile control flow with the activity listener boundary delegated to the dedicated test.
No firing-loop gate, freshness rule, shell count, fixture acceptance condition or deadline changed. Native compilation and a new clean full-salvo run must establish the outcome; source-local adapter tests are not engine or multiplayer evidence.
Native event rerun
The 112900 refresh run passes with 55 assertions and no captured errors. The exact activity emits its completion event after physical projectile eight; HQ closes the order as native COMPLETED, before the unchanged 120-second timeout. The fixture verifies 19 surviving original shells and a further 25 seconds without replay. This confirms the event-based handoff for the observed run. The earlier 111915 timeout failure remains retained; real multiplayer and interrupted native callbacks remain separate.