# Native complete mortar salvo qualification `ERR_NativeMortarSalvoProbe` is a separate, opt-in WORKBENCH case for `errSmoke=1`, `errSmokeOutcome=mortar-salvo`. The earlier immutable-report source compiled and ran in [native-2026-09-19-105441-salvo-expiry-refusal](evidence/native-2026-09-19-105441-salvo-expiry-refusal/report.json): two actual committed projectiles were observed, then the original report expired and the full-salvo case correctly failed. That is partial firing/cancellation evidence, not eight-round completion. The new refresh variant described below and subsequent fixture changes still need their own native run. No acceptance item is marked passed. It waits for the existing authored mortar, the same four conscious crew and their real manpack uplink, then records 27 distinct physical HE inventory entities. It uses the existing bounded terrain survey, with no character or vehicle within the 400 m target square and production settlement/friendly exclusions applied to every configured salvo offset. An inert, fixture-owned vanilla intel folder is the report identity. Its stationary observation is explicitly synthetic; the fixture queues it once on the battery RTO's actual uplink, without injecting an HQ contact or invoking the battery's order methods. Normal HQ reception, scheduling, downlink, native artillery activity and firing remain in control. The test requires every configured round (`ERR_MortarSalvo.ROUNDS`, currently eight) to produce a distinct, nonnull native `OnWeaponFired` projectile. Each event must match the observed production commit boundary for the same source, order and sequential shot. Every commit still requires the original immutable observation within the production 60-second freshness window and an original native aim command with an independently recent equivalent validation (`AimValidatedAt`). No observation is refreshed to make eight rounds fit. Natural activity completion with fewer than eight launches, stale-report withholding, timeout, rejection or explicit Stop all fail this particular case. The separate expiry test remains the correct place to qualify withholding. If the real loading/aiming cycle cannot finish eight fresh commits within 60 seconds, that is an exposed behavior limit, not permission to relax freshness or claim completion. At natural completion the physical crew inventories must contain exactly 19 surviving original shells, with the battery's count agreeing. A replacement entity is never accepted as conserved ammunition. There must be eight native events, eight matching commits, eight production order/total shots and no pending load, route, report or committed shell. This verifies launch and inventory consumption; neither projectile impact nor hit accuracy is inferred. The original delivered report snapshot must remain ineligible for a second request. The fixture leaves the unchanged HQ contact and scheduler running for a further 25 seconds, exceeding the normal 20-second post-dispatch decision interval. Any additional command, load, projectile, shell or order fails. The final crew must still be the original four conscious actors. Cleanup removes only this fixture's report identity and stops its captured battery; it never deletes/recreates crew ammunition or attempts to retract an already committed native projectile. Cleanup is idempotent and must also run on the outer runner's timeout or mission Stop. Source errors remain errors. Initial readiness is bounded by 180 seconds; after queueing, the order is bounded by the production 120-second timeout plus 35 seconds, and the replay window has a 2-second scheduling allowance. This does not qualify actual enemy visual perception, donor-to-gunner resupply (the gunner can carry nine rounds), real impacts, multiplayer, performance or a second distinct report. It adds no production balance changes or resource assets. It reuses the successor's independently authored native evidence rules; all new hooks are gated by the active fixture singleton and preserve their superclass calls. ## Original integration wiring Keep the three new files, then make narrow changes to `addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c`: 1. Add `protected bool m_ERRTestMortarSalvo;` and `protected ref ERR_NativeMortarSalvoProbe m_ERRMortarSalvo;`. 2. In `EOnInit`, set `m_ERRTestMortarSalvo = outcome == "mortar-salvo";`, and give it the same 330000 ms outer deadline as mortar-fire. Existing player-death flags must remain unchanged. Add `if (m_ERRMortarSalvo) m_ERRMortarSalvo.Stop();` to the existing `ERR_StopHQPatrol` cleanup. 3. In the initial single-player PRISON branch after the existing contract/admin checks, mirror the mortar-fire branch's explicit test-only player protection, construct `m_ERRMortarSalvo = new ERR_NativeMortarSalvoProbe(this);`, set `m_ERRSmokeStep = 190;` and return. The probe never changes player health itself. 4. Add the bounded tick branch: ```c if (m_ERRSmokeStep == 190) { int result = m_ERRMortarSalvo.Tick(now); if (result < 0) { ERR_Check(false, "native complete mortar salvo fixture; inspect source diagnostics"); return; } if (result == 1) { m_ERRSmokeStep = 191; Transition(ERR_Phase.DEFEAT); } return; } ``` 5. Add the corresponding phase handler before generic defeat handling. Require `current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 191`, set `m_ERRSmokeDone = true`, remove the tick callback, call `ERR_StopHQPatrol()`, print `ERR SMOKE COMPLETE: native full configured mortar salvo, exact original ammunition and unchanged-report replay refusal; fixture reset requested`, then return. 6. Add `mortar-salvo` to `tools/headless_workbench.py` choices and the existing mortar-fire/expiry/transfer validation set requiring Chat, one round and timeout at least 400. Do not alter other outcomes. Add `test_native_mortar_salvo.py` to `tools/check.py`'s explicit regression list. Proposed native command after integration: `python3 tools/headless_workbench.py --project chat --smoke mortar-salvo --rounds 1 --timeout 400`. Retain all error capture and source manifest qualification. Archive partial/failed results honestly; an expiry pass cannot substitute for this case. Run the focused source check with `python3 tests/test_native_mortar_salvo.py`. It compiles the actual Enforce evidence method bodies as C++ with adapters and tests eight sequential commits/events, incomplete/extra events, changed report/order identity, stale report/aim, duplicate projectiles, missing shells, closed-step commits, early closure and Stop. Native API scheduling, callback ordering, inventory entities and actual launch remain for the real run. ## Separate `mortar-refresh` variant The same bounded fixture also accepts `errSmokeOutcome=mortar-refresh`. The original `mortar-salvo` behavior remains a single immutable observation. The new variant does not extend that observation: it explicitly creates distinct synthetic observations of the same stationary inert target, each with a new journal ID and its actual native creation time. The fixture captures the original physical RTO and queues each report only through that same carrier's existing HQ uplink. It never inserts an HQ contact, calls a production order/refresh method, edits an existing report timestamp or directly replaces `battery.Report`. A small record per observation stores its original ID/time and later the actual accepted snapshot. The fixture observes production `UPDATE QUEUED` and `UPDATE RECEIVED` audit boundaries: the former must expose a nonempty real HQ downlink with its own message ID; the latter must show that exact transaction delivered, attached to the captured gun/group/order, with no committed shell being relabelled. It pins the original native artillery activity and rejects recreation. Initial order delivery is separately observed through the existing real downlink. These hooks only record evidence and flag failures; they never cancel or change the production operation from inside its call. A subsequent observation is eligible only after the previous one was accepted, at least 20 native seconds after its creation, with no refresh transaction pending. Queueing ends after four of eight physical shots or 90 seconds of the original order, whichever comes first. The bounded record list permits at most eight observations. Every queued report must actually have been accepted before natural completion, and at least one physical commit must use a later accepted report. An unclaimed or undelivered final observation cannot be ignored to manufacture a replay pass. If real timing violates these gates, the fixture fails without pausing firing, forgiving the deadline or altering the report lifetime. Each commit binds the accepted recorded report ID and exact immutable observation time, checks the original command time and a validation within ten seconds, and captures that report association. Its subsequent physical projectile must retain that association even if newer observations are in transit. The original order ID/source, exact activity, physical gun/RTO/crew, eight launches, 27-to-19 original-shell inventory conservation, natural closure and all existing safety checks remain mandatory. Report updates do not renew the production 120-second order timeout or the fixture deadline. Completion requires no pending update. During the 25-second replay observation, **every** accepted report remains ineligible for a second order; the latest HQ contact is deliberately left intact. The new variant is still explicit synthetic-observation testing. It does not claim real enemy perception, impact accuracy, multiplayer delivery, moving-target retargeting or a completed native full salvo before a matching engine archive exists. The initial immutable-report expiry archive remains separate evidence and must not be relabelled as a refresh run. Root integration is intentionally outside this fixture change: accept `mortar-refresh` in the existing Chat mortar-salvo branch and runner choices with the same one-round/400-second constraints; identify the outcome accurately in the completion text. The class constructor selects the variant from that outcome. Add `test_native_mortar_refresh.py` to the explicit repository test list. Proposed native invocation after integration: `python3 tools/headless_workbench.py --project chat --smoke mortar-refresh --rounds 1 --timeout 400`. `tests/test_native_mortar_refresh.py` executes the actual observation records, cadence, production-audit observation, commit and closure gates using boundary adapters. It rejects changed identity/time/position/source, early or duplicate acceptance, missing downlinks, activity replacement, committed-shell report updates, unaccepted commits and unclaimed reports at replay. `tests/test_native_mortar_salvo.py` continues to exercise the original immutable variant's actual commit/event/completion gates. These source-local tests are not native qualification. ## Successful native refreshed salvo [112900](evidence/native-2026-09-19-112900-mortar-refresh/report.json) completes with 55 assertions and zero captured errors. The original RTO sends observations M1/M7/M9/M11 using distinct actual observation times. Three updates traverse actual HQ downlinks; all eight fresh commitments and physical projectile callbacks retain one order/activity and consistent accepted report identity. The native activity event closes the order, 19 original shells remain, and a 25-second replay check passes. This uses a deliberately stationary inert target; impact accuracy, ordinary perception and multiplayer are not claimed. The earlier eight-shot timeout failure at 111915 exposed missed native completion polling and is preserved alongside the event-latch correction.