31 lines
6.8 KiB
Markdown
31 lines
6.8 KiB
Markdown
# Same-order mortar observation updates
|
|
|
|
An active physical battery can now receive a newer observation for its existing target through HQ. HQ must first have received the observer's actual uplink; the mortar system reads `SupportContacts`, selects a strictly newer observation with a different positive message ID, and sends a separate `MORTAR UPDATE` downlink. The existing radio route must finish its actual carrier/hop checks before the battery can use the snapshot. The existing report ledger claims each update once, so an attempted update cannot later authorize another response using the same observation.
|
|
|
|
Each update captures the exact gun, group and order ID. Only one update may be pending per battery. HQ checks for new updates every five seconds within its existing tick; pending routes advance each tick. There are no new timers, native activities, spawns or ammunition allocations. The initial order ID, initial source/radio IDs, original order deadline, shot count and eight-round limit do not change. The report retains its original observation time through both radio legs. The 60-second freshness bound and 120-second order timeout remain unchanged.
|
|
|
|
Delivery can have three outcomes:
|
|
|
|
- With no command for the next shell yet, the battery accepts the snapshot and the normal native aim node must solve it before firing.
|
|
- With a native aim command already in progress, the exact original solver recomputes the new report using the existing ballistic and friendly/settlement checks. It accepts the new report only if charge, gun position, impact, barrel target and flight time remain equivalent to the **original** command. The original geometry remains stored; successive small changes cannot accumulate through comparisons with previously accepted approximations. A changed solution cancels future fire instead of claiming that a different barrel command was executed.
|
|
- With a shell already committed to native inventory transfer, delivery waits. That shell retains its original report, geometry, airspace reservation and projectile attribution. Only after its native projectile callback clears the commit can the update be adopted for another shell.
|
|
|
|
A failed downlink, absent route for a selected newer contact, expired update or changed physical ownership stops further loads. If a projectile is already committed, the rejection remains latched until that projectile is observed; it does not clear its commit flag or relabel it. A transaction belonging to an older order is discarded without cancelling the newer order. Round stop and normal cancellation clear the pending update. Receiving no newer observation never retimestamps the old one: normal expiry still cancels fire.
|
|
|
|
`AimedAt` now records the original native barrel command time. `AimValidatedAt` records the latest successful equivalent ballistic check, including checks against a newly delivered observation. Only a real aim command sets both together. Revalidation and report adoption update only `AimValidatedAt`; projectile observation and cancellation clear both. Both native mortar fixture commit hooks check the original command timestamp and independently require validation within ten seconds. Their existing `aimAge` diagnostic now remains the age of the original command.
|
|
|
|
The journal's normal `source`/`radio` prefix continues to identify the **initial order**. `UPDATE QUEUED`, `UPDATE RECEIVED`, and subsequent aim messages identify the newer accepted observation and update radio IDs. A refresh fixture must inspect those accepted report IDs and original observation timestamps; it must not mistake the initial prefix for the latest target report.
|
|
|
|
`tests/test_mortar_refresh.py` executes the production constructor, ownership, eligibility, ledger-claim call, delivery advancement and aim-adoption methods against controlled radio/ballistic boundary results. It covers duplicate/older/future/wrong-target reports, pending and failed routes, lost/replaced gun or group, stale order, deferred commit, immutable deadline/count/timestamps, non-equivalent geometry, missing solver and cumulative tolerance drift. Existing aim-renewal and native-evidence policy tests cover the two timestamp meanings. These are source-policy tests, not native radio, aiming or multiplayer evidence.
|
|
|
|
Native compilation is observed in the 11:09:55 expiry run; a real repeated-report delivery fixture remains pending. That run failed the expiry-evidence classification described below, so it does not qualify report refresh or the complete regression. The earlier immutable-report full-salvo attempt at `logs_2026-09-19_10-54-41` correctly produced two real projectiles before the original report expired. It is a partial-cancellation observation, not a completed salvo and not evidence for this new path. A future refresh fixture must retain the original expiry case separately, use explicitly disclosed new observations with distinct IDs/times through real uplink/downlink, and verify one unchanged order/activity, finite physical stock, fresh commit authorization and natural completion. No full-salvo, moving-target re-aim or multiplayer claim follows from this implementation alone.
|
|
|
|
|
|
## Supply refusal classification after expiry
|
|
|
|
The 11:09:55 native expiry run recorded three real projectiles. The third shell was committed while the original observation was 59.653 seconds old and emitted after expiry; an already authorized native transfer may finish. At 61.009 seconds the next supply check correctly refused authorization, but ShootingLogic replaced its expired-report reason with the generic physical-ammunition failure. The expiry fixture therefore rejected the closure instead of crediting a proven expiry. That failed run remains failed evidence.
|
|
|
|
ShootingLogic now captures one current timestamp, calls the existing `SupplyState(now)` first, and retains its PENDING/FAILED behavior. Only after FAILED does `ERR_MortarSystem.SupplyFailure` classify a missing/expired delivered report through `Latest`/`ContactFailure`; a still-fresh report retains the existing physical-ammunition reason. It then calls the unchanged deferred `Withhold` path. This changes the diagnostic classification, not authorization, inventory contents, native counters, cancellation timing or report freshness. Existing accepted transfers are still polled before the authorization check, remain reserved when unresolved and are never freed/retried by the helper.
|
|
|
|
`tests/test_mortar_transactions.py` executes the actual helper, delivered-report lookup and contact-reason bodies for missing, expired, future/invalid and fresh reports, including the exact 60-second boundary. Integration checks retain supply polling and PENDING handling before failure classification. Existing actual Poll-body tests continue to cover ambiguous callbacks, invalidated/late completions and unresolved reservation retention. This narrow correction has source-regression coverage; its native expiry rerun is pending.
|