53 lines
7.5 KiB
Markdown
53 lines
7.5 KiB
Markdown
# 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.
|