Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

138 lines
14 KiB
Diff

--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c
@@ -349,6 +349,17 @@
protected void ERR_OnObserverMonitorStarted() {}
protected void ERR_UpdateObserverClient()
{
+ if (!GetGame()) return;
+ if (this != GetGame().GetPlayerController())
+ {
+ // A replaced controller may retain its timer until native destruction.
+ // Release only its presentation; never restore its old body camera.
+ GetGame().GetCallqueue().Remove(ERR_UpdateObserverClient);
+ m_ERRObserverMonitor = false;
+ m_ERRRecoveryBody = null; m_ERRRecoveryDeadline = -1;
+ ERR_CloseObserverCamera();
+ return;
+ }
ERR_GameMode mode = ERR_GameMode.Get();
ERR_UpdateRecoveryCamera(mode);
bool observe = mode && ERR_ObserverRules.View(m_ERRObserverReason, mode.Active(), ERR_Actors.Alive(GetControlledEntity()), ERR_Actors.CanFight(GetControlledEntity()));
--- a/tests/test_hint_delivery.py
+++ b/tests/test_hint_delivery.py
@@ -3,16 +3,16 @@
from pathlib import Path
import os
GUIDE=Path(os.environ.get('ERR_GUIDE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerGuide.c'))
-OBSERVER=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c'
+OBSERVER=Path(os.environ.get('ERR_OBSERVER_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c'))
def cpp(text):
- text=text.replace('protected ', '').replace('override ', '').replace('super.OnControlledEntityChanged(from, to);', 'nativeChanges++;')
+ text=text.replace(' = null;', ' = nullptr;').replace('protected ', '').replace('override ', '').replace('super.OnControlledEntityChanged(from, to);', 'nativeChanges++;')
for name in ('ERR_GameMode','ERR_Phase','ERR_ObserverReason','ERR_Actors','ERR_ObjectiveText','SCR_HintManagerComponent','ERR_ObserverGuidance','ERR_ObserverRules','Resource','ETransformMode','SCR_ManualCamera','SCR_RespawnSystemComponent','RplId'):
text=text.replace(name+'.',name+'::')
for typename in ('ERR_GameMode','SCR_HintManagerComponent','Resource','SCR_RespawnSystemComponent','CameraManager'):
text=text.replace(typename+' ',typename+'* ')
for variable in ('mode','hints','resource','respawn','manager'):
text=text.replace(variable+'.',variable+'->')
- text=text.replace('GetCallqueue().','GetCallqueue()->').replace('Remove(ERR_UpdateGuide)','Remove(this)').replace('CallLater(ERR_UpdateGuide, 1000, true)','CallLater(this, 1000, true)')
+ text=text.replace('GetCallqueue().','GetCallqueue()->').replace('Remove(ERR_UpdateGuide)','Remove(this)').replace('Remove(ERR_UpdateObserverClient)','Remove(this)').replace('CallLater(ERR_UpdateGuide, 1000, true)','CallLater(this, 1000, true)')
text=text.replace('GetGame().','GetGame()->').replace('EntitySpawnParams params = new EntitySpawnParams();','EntitySpawnParams params;')
return text
methods=cpp(body(GUIDE,'protected void ERR_StartGuide(')).replace('ERR_StartGuide()', 'StartActual()')+'\n'+cpp(body(GUIDE,'override void OnControlledEntityChanged('))+'\n'+cpp(body(GUIDE,'protected void ERR_UpdateGuide('))+'\n'+cpp(body(OBSERVER,'protected void ERR_UpdateObserverClient('))
@@ -53,7 +53,7 @@
Controller(){game.local=this;}
void ERR_UpdateRecoveryCamera(ERR_GameMode*){}
int nativeChanges=0,starts=0;bool m_ERRGuideStarted=false;void ERR_StartGuide(){starts++;StartActual();}
- int m_ERRGuidePhase=-1;string m_ERRGuideMessage;
+ int m_ERRGuidePhase=-1;string m_ERRGuideMessage;bool m_ERRObserverMonitor=false;Actor* m_ERRRecoveryBody=nullptr;float m_ERRRecoveryDeadline=-1;
Actor* controlled=&actor;int m_ERRObserverReason=ERR_ObserverReason::NONE;
SCR_ManualCamera* m_ERRObserverCamera=nullptr;Vec m_ERRObserverAnchor;
int m_ERRObservedId=1;bool m_ERRObserverReady=false;
--- a/tools/headless_workbench.py
+++ b/tools/headless_workbench.py
@@ -33,6 +33,7 @@
p.add_argument('--timeout', type=int, default=360)
p.add_argument('--project', choices=['core', 'chat'], default='chat', help='Diagnostic fixture family (both load the combined mod)')
a = p.parse_args()
+if a.smoke == 'unconscious-camera' and a.project != 'core': p.error('unconscious-camera requires --project core to keep the general smoke fixture inactive')
if a.mine_stock:
if a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 360:
p.error('--mine-stock requires --smoke mounted-ammo --project chat --rounds 1 and timeout at least 360 seconds')
--- a/tools/check.py
+++ b/tools/check.py
@@ -15,6 +15,8 @@
subprocess.run([sys.executable,str(ROOT/'tests/test_prison_ai.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_native_player_death_fixture.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_hq_planner_budget.py')],check=True)
+ subprocess.run([sys.executable,str(ROOT/'tests/test_observer_ownership.py')],check=True)
+ subprocess.run([sys.executable,str(ROOT/'tests/test_native_observer_selector.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_loading.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_lifecycle.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_transfer.py')],check=True)
--- a/docs/current-status.md
+++ b/docs/current-status.md
@@ -1,4 +1,10 @@
# Current development status
+
+**2 October 2026 — spectator callbacks respect local controller ownership:** a retained old controller can no longer restore its old body camera, close the current editor or create a new spectator view after replacement. It stops only its own monitor and releases its owned presentation state. The existing binder keeps watching for a replacement controller and starts its monitor once, retaining normal stop/end cleanup. Independent actual-method tests reproduce the old fault and verify the corrected behavior; all **279 registered regression programs pass**.
+
+The [hidden/muted unconscious-camera run](evidence/native-2026-10-02-observer-ownership/report.json) completes **27 assertions and one round**, preserving ordinary unconsciousness, both spectator views, tracking, pose-gap fallback, healing and repeated knockout. It records zero script/runtime failures and thirteen retained installed-resource errors, with confirmed cleanup and unchanged inputs. The first attempt used the wrong Chat fixture selector and failed because the general smoke also ran; that evidence is retained, and the runner now refuses that selector combination before native setup. This is normal camera integration evidence; native controller replacement and real multiplayer remain unverified. [Review and exact limits](evidence/observer-ownership-2026-10-02/review.json).
+
+Playtest instructions now open the actual combined Core project and point to the latest retained build. Original missions and inherited changes are preserved. The existing 412-file packaged candidate is unchanged and does not yet contain this observer fix. Human play, multiplayer, physical logistics, comparative FPS and clean release remain unfinished.
**2 October 2026 — updated development playtest and objective-hint ownership fix:** the [separate ground-depot/guide handoff](../.build/playtest-2026-10-02-ground-depot-guide/README.md) now includes recent finite camp loading, zero-stock engineer resupply, HQ count optimization and a correction preventing a retained old controller from displaying hints after local ownership changes. It contains **403 authored files, 346 exact PAK payloads and three runtime files**, with matching editable source. [Artifact hashes and strict limits](evidence/package-2026-10-02-ground-depot-guide/handoff.json).
--- a/docs/runtime-testing.md
+++ b/docs/runtime-testing.md
@@ -28,13 +28,13 @@
## Native setup
-Use matching stable Arma Reforger and Arma Reforger Tools installations. Keep original Escapists and its extensions unloaded. Register `addons` as an addon search directory, and open `addons/chat/addon.gproj`. Open `Worlds/ERR_Everon.ent` in the World Editor. `ERR_ValidateWorldPlugin` logs the authored locations' terrain heights; `-errPlay 1` allows that diagnostic plugin to enter play mode when deliberately invoked.
+Use matching stable Arma Reforger and Arma Reforger Tools installations. Keep original Escapists and its extensions unloaded. Register `addons` as an addon search directory, and open `addons/core/addon.gproj`. Open `Worlds/ERR_Everon.ent` in the World Editor. `ERR_ValidateWorldPlugin` logs the authored locations' terrain heights; `-errPlay 1` allows that diagnostic plugin to enter play mode when deliberately invoked.
The command-line conventions follow the [official startup parameter documentation](https://community.bistudio.com/wiki/Arma_Reforger:Startup_Parameters): Workbench uses `-gproj`, `-addonsDir`, `-load` and `-plugin`. Workbench's `-load` is distinct from the game's `-world`. Launching a visible editor is an explicit test step, not part of `tools/check.py`.
## Acceptance sessions
-Use the [player route playtest guide](player-route.md) for the intended normal-input sequence. It is derived from source and is not a record of a completed manual run. Packed Core and five-addon Chat each have artifact-bound ordinary startup evidence; this does not resolve the baseline packaging UI shutdown errors or qualify later source changes. See [current status](current-status.md) and the [packaging control](native-packaging-control.md).
+Use the [player route playtest guide](player-route.md) for the intended normal-input sequence. It is derived from source and is not a record of a completed manual run. The current combined-addon development handoff has artifact-bound ordinary startup evidence; see [the retained playtest](playtest-build.md). Historical Core-only and five-addon results remain separate. Startup evidence does not resolve packaging/resource failures or qualify later source changes. See [current status](current-status.md) and the [packaging control](native-packaging-control.md).
Record the game/Tools versions, repository tree hash, addon list, player count, AI/vehicle counts, settings, map positions and complete logs for every run. Start from an isolated profile.
--- a/docs/single-mod.md
+++ b/docs/single-mod.md
@@ -29,7 +29,7 @@
folders. RHS and WCS remain external dependencies, not copied assets. No Workshop
publication or release qualification is part of this migration.
-The [30 September development candidate](combined-candidate-2026-09-30.md) packages the current combined source; historical five-addon handoffs remain separate.
+The [playtest build guide](playtest-build.md) identifies the latest retained, source-matched development candidate. The [30 September candidate](combined-candidate-2026-09-30.md) and historical five-addon handoffs remain earlier snapshots.
## Source and package checks
--- a/docs/spectator-guidance.md
+++ b/docs/spectator-guidance.md
@@ -15,3 +15,9 @@
`ERR_UnconsciousCameraProbe` tests native incapacitation, unchanged possession, physical camera tracking, loaded input actions including inherited orbit input, toggling to/from head view, a different manual camera taking over before healing, return to the normal camera, and repeated knockout. It uses a physical stand-in for another player and programmatic input-handler calls; it does not prove physical keyboard delivery or dedicated multiplayer target streaming. Native run results are recorded separately under `.local/analysis/spectator-2026-09-27/`.
On 27 September, the isolated native fixture completed 25 assertions without script errors or VM exceptions. It verified both camera views, the loaded controls, a separate manual camera takeover, restoration after healing, and repeated knockout. The runner subsequently hit a transient cleanup-classification failure; separate follow-up confirmed the private runtime idle and authored sources unchanged. Existing external resource errors remain. Raw results and failed diagnostic iterations are preserved in the evidence directory above. Physical keyboard delivery, remote client cycling, and native Game Master/ArmaVision menu closure remain manual multiplayer checks.
+
+### Observer monitor ownership (2 October 2026)
+
+A retained former local controller now stops its observer callback before it can restore its old player camera, close the editor or spawn a spectator camera for the new local session. It clears only its own timer, pending recovery state and owned observer camera. The current controller's timer and view remain intact; the existing 250 ms binder now remains active until stop/end so a replacement controller starts its monitor once. Repeated checks do not create extra timers. Native manual-camera teardown retains its ordinary previous-camera fallback when the deleted camera itself is current.
+
+An actual-method regression independently reproduces the old camera/editor change and passes with the guard. All **279 registered regression programs pass**. The [hidden/muted native fixture](evidence/native-2026-10-02-observer-ownership/report.json) completes **27 assertions and one round** for ordinary unconsciousness, target tracking, both views, a head-pose sampling gap, healing and repeated knockout. It has zero script/runtime failures and thirteen retained installed-resource errors, with unchanged source/external inputs and confirmed cleanup. The initial wrong-selector failure is preserved; `unconscious-camera` now requires `--project core` before runtime setup. Native controller replacement, shared input internals and real multiplayer remain unqualified. This correction does not establish the cause of the previously reported first-person drift. [Review and preservation](evidence/observer-ownership-2026-10-02/review.json).
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c
@@ -465,7 +465,7 @@
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!controller) return;
controller.ERR_StartObserverMonitor();
- GetGame().GetCallqueue().Remove(BindLocalObserver);
+ // Keep watching for a replacement local controller; Start is idempotent.
}
protected void StartScenario()