Files
EscapistsReintroduced/docs/ordinary-gate-observer.md

53 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Opt-in ordinary gate action observation
This is a read-only diagnostic for a future private ordinary-input prison test. It is **not enabled or natively qualified by this source change**. It sends no input, selects no action, invokes no action completion and changes no actor, camera, gate, phase or denial text.
`ERR_OrdinaryGateObserver.c` is excluded from Workbench. Its static `Sample(mode, world, controller, actor, nonce, started, index, now)` is designed for the existing ordinary input observer immediately after a validated sample, with the same original process-millisecond start and consecutive sample index. Activation requires `-errOrdinaryGateObserve NONCE`, matching `-errOrdinaryInputObserve NONCE`, `-errOrdinaryScenario core|chat`, local server `RplMode.None`, and a non-console process. No timer or new observation budget is created: at most 450 consecutive samples within the original 45 seconds; one attempted binding per process.
The caller currently stops outside PRISON. That remains unchanged. The new utility accepts PRISON and RADIO_SEARCH, but recording a successful native unlock transition would require a separately reviewed change to the caller’s lifetime/phase checks. An approach-only test can remain entirely in PRISON. Later inventory/menu opening permanently cancels this observer; it does not cancel the main input observer.
## Native API basis
The installed generated `UserAction/BaseInteractionHandlerComponent.c` exposes `GetSelectedAction`; `ExtBaseInteractionHandlerComponent.c` exposes `GetCurrentContext`, `GetControlledEntity` and `GetFilteredActions`. `BaseActionsManagerComponent.c` exposes `GetContext` and `FindAction`. The observer pins the original registered, intact `mode.PrisonEscape().Gate()`, its manager, `SCR_DoorUserAction` context, native door component and exactly one context-owned `ERR_UnlockPrisonAction`. Its native duration must remain four seconds, matching the authored `Prefabs/ERR/PrisonDoor.et`.
Installed `Interactions/SCR_InteractionHandlerComponent.c` stores actual accumulated duration in protected `m_fCurrentProgress`, along with `m_bIsPerforming`, `m_pLastUserAction` and `m_pLastContext`. The new file adds only a read accessor through a modded class; it overrides no callbacks. Progress is returned only when the native selection and context match the pinned action/context, the last action matches, and an ongoing action also has the original last context. Unrelated/stale progress becomes `progressBound=0 progress=0 performing=0`. Calling `BaseUserAction.GetActionProgress(0, 0)` would not retrieve accumulated progress and is deliberately avoided.
`cachedPerformable` reports the existing native filtered-list boolean, not a new permission decision. Vanilla explicitly warns this cache may be stale. Calling successor `CanBePerformed` would update its denial reason, so the observer never does so. A future input helper must treat cached permission as one necessary observation, not authorization by itself.
Generated `UserActionContext.c` explicitly documents `GetOrigin()` as **world space**. `GetRadius`, `BaseDoorComponent.GetNormalizedDoorState` and `GetControlValue` are native getters. Prison center comes from `mode.PrisonPosition()`, gate origin from the pinned entity, and `gateForward` is normalized native `GetTransform()[2]`; no historical coordinates are substituted. These measurements do not prove obstacle clearance or safe walking.
## Record contract
All records carry `run=NONCE sample=INDEX real=PROCESS_MS`. The first valid sample emits:
```text
ERR GATE OBSERVE: run=N sample=I real=T BASIS registered-prison-lock-native-interaction
ERR GATE OBSERVE: run=N sample=I real=T SAMPLE phase=P selected=B context=B cachedPerformable=B progressBound=B performing=B progress=F duration=F pressed=F
ERR GATE OBSERVE: run=N sample=I real=T GEOMETRY body=<x,y,z> context=<x,y,z> radius=F doorState=F doorControl=F prison=<x,y,z> gate=<x,y,z> gateForward=<x,y,z>
```
SAMPLE and GEOMETRY repeat together. `pressed` is native `PerformAction` input value. `selected` identifies the exact registered lock action; `context` identifies its exact native context. The BASIS records source-enforced identity pinning, not a synthetic entity identifier. A consumer must join records with the same nonce/index/time and independently constrain geometry and input. Selection, progress or phase alone does not demonstrate physical escape.
Loss of the original world, mode, controller, roster life/account, conscious on-foot actor, gate/action/context/component identity, menu-free state, explicit opt-in, sample/clock continuity or finite native geometry emits one terminal record:
```text
ERR GATE OBSERVE: run=N sample=I real=T CANCELLED reason
```
The observer never adopts a replacement and never revives. Destroying the gate cancels this **intact-lock** diagnostic; the separate destroyed-gate progression fixture covers that path.
## Verification
`python tests/test_ordinary_gate_observer.py` executes the actual Enforce method bodies as C++ against fake native boundaries. It covers real progress-field attribution, stale selection/context rejection, cached-array mismatch, absent/ambiguous original registration, account/body/controller/registry replacement, menu/vehicle/phase loss, changes during sampling, explicit scope, original time/index limits, finite geometry and permanent cancellation. It is policy coverage, not native compilation, ordinary input, multiplayer or a completed escape proof. The next native check must confirm this installed handler’s selection/progress and geometry at runtime before any gate input is enabled.
## First native observation
[Ordinary Chat184437](evidence/game-2026-09-19-184437-gate-geometry/runner.json.txt) compiles and emits59 gate samples before inventory opening cancels the gate observer. Independent validation passes41 joined READY-through-last observations with zero static-geometry drift. Native context is `(6092.77,213.226,7154)`, radius2, gate `(6090,211.344,7154)`, prison center `(6090,211.444,7160)`, duration4. The lock is not selected at the distant spawn; no hold was sent. Same-API gate/input position agrees within1cm; the camera observer's transform translation trails GetOrigin by1–3cm while walking, bounded to5cm in the join. No broader route/action proof is inferred.
## Optional native action-choice diagnosis
`--observe-prison-choices` requires the existing gate/input observers and forwards the same nonce as `errOrdinaryGateChoices`. With input testing it additionally requires the explicit approach mode. The diagnostic calls only native identity/selected-action/filtered-cache getters; it logs at most eight ordered classes/IDs and cached permissions, under a separate `ERR GATE CHOICE` prefix. Normal SAMPLE/GEOMETRY and default observers remain unchanged. `ordinary_gate_choices.py` joins optional records to raw input/camera/gate samples by nonce, index and native clock.
The [194928 diagnosis](evidence/gate-choice-review-2026-09-19-194928/review.json) parses353 native records. Samples304–353 show exactly disabled `ERR_PrisonDoorAction` ID1 first, followed by permitted `ERR_UnlockPrisonAction` ID0; the selected action is the disabled door. The exact gate context and lock permission are present. The original alignment runner remains failed because it never selected the lock; no wheel/F input was sent. Future selection must pin the original same-run IDs rather than treat these per-instance native numbers as universal constants. Changing action visibility, duration or interaction geometry to force this test is unnecessary.