Files
EscapistsReintroduced/docs/casualty-loading.md
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

12 KiB

Ground casualty loading into extraction transport

This independently authored implementation adds a five-second Load casualty into extraction helicopter action only to the successor Prisoner prefab, using its existing Chest and LowerTorso interaction contexts. It keeps inherited vanilla medical interactions. The service and owner bridge compile natively, and the five-second action and inherited medical actions have been inspected in the native fixture. Ground support, completed interaction and multiplayer qualification are still pending; the earlier successful child-bench mechanism experiment alone does not qualify this complete interaction. Ground exit placement still fails its separate native assertion, so this implementation deliberately supplies no unload action or claim.

The server accepts only distinct, connected participants whose current controller, connection and used-life roster record all match their current bodies through the canonical server-owned ERR_PlayerRoster.CurrentActor check. The bound roster identity must be non-null; this also preserves the existing offline Workbench identity path. A reserved disconnected body is deliberately ineligible. The rescuer must be conscious and on foot; the living target must be natively incapacitated, on the ground, not entering/exiting a vehicle and observed settled for at least five seconds. Settlement is sampled through the round tick with a maximum two-second observation gap; it does not infer that unobserved sub-tick movement was impossible. Recovery, falling, movement, stale observation or identity changes reset the observation window.

Both actors must be within 12 metres of the registered extraction aircraft and within three metres of one another. The mission must be active in EXTRACTION. The intact helicopter needs actual simulation ground contact or the conservative sleeping, engine-off three-wheel support check described in casualty-ground-support.md, an upright vertical component at least 0.85, linear velocity at most 0.5 m/s and angular velocity at most 0.1 rad/s. Native body interaction contexts provide initial line-of-sight presentation. A new shared native raycast revalidates rescuer-to-casualty sight on the server at request and commit, then again on the owner immediately before entry. It accepts an unobstructed segment or contact with the intended casualty and its attachments. This first version does not supply carrying/dragging animations, arbitrary vehicle loading, or a player unloading action.

Only actual child transport benches of the registered Mi8 are selectable. The service rejects its root navigator CARGO slot, patient slots, pilot/turret slots, occupied, inaccessible, locked or reserved slots. The exact slot must belong to the actual aircraft's hierarchy and the installed Mi8 seat-part prefab. Native seat classes and health settings remain unchanged. A native reservation plus service tickets prevents duplicate requests for the same caller, casualty or bench. Reservation replication/contention still needs remote tests.

Ownership and commit boundary

A stock MoveInVehicle request may retry for missing replicated vehicle entities. This service does not invoke that method or queue movement retries. It uses a successor-specific controller handshake:

  1. Action completion passes live server checks and reserves a bench. The target's controller receives a prepare request identifying the actual mode instance and ticket. Preparation cannot move anyone.
  2. The target owner validates its current body/phase, starts a three-second local System.GetTickCount lease, and sends ready. The server accepts ready only from the ticket's exact controller/body and revalidates identity, current physical state, settled duration, reservation and a five-second preparation deadline.
  3. The server commits one command with exact mode, caller, casualty, aircraft, seat-owner replication IDs and the slot ID. This is the final authorization boundary. The owner consumes the unexpired lease before executing, rechecks matching mode/body/aircraft, native incapacitation, ranges, grounded craft and the real child seat, then calls native GetInVehicle once. Missing entities and native refusals fail once; no delayed retry can later acquire a seat. Duplicate or expired commits cannot execute.
  4. The server never trusts either a positive or negative client acknowledgement as permission to resolve or release a seat. It observes actual target-seat occupancy and aircraft membership, with an eight-second post-commit budget. The local lease bounds how long a delayed commit may start movement without requiring clocks synchronized between machines. Before commit, identity/physical changes cancel the request. After commit, caller or casualty connection changes alone do not cancel the authorized move or release its reservation: the immutable original body/aircraft/seat are observed until completion or expiry. Death of a still-existing target body retains the committed reservation through physical occupancy or expiry, because the owner can still have pre-death replicated state. Occupancy by a dead target resolves as failure. Loss of the physical aircraft/body or terminal/explicit Stop ends observation without compensating teleports or health changes.

An already committed native movement is not rolled back if conditions subsequently change. A false resolution means loading was not confirmed by this service, not that a committed native command was magically revoked. Terminal/identity changes are also rechecked on the owner; replication ordering under real latency remains a required multiplayer test. The service removes only a reservation still owned by the same surviving target reference, never another player's reservation. Native reservation cleanup after entity deletion also requires explicit runtime verification.

The mode adapter owns Tick/Stop/roster subscriptions and replicates only the current extraction-aircraft reference for client action presentation. Roster events run immediate cleanup. While tickets remain pending, one service-owned 250 ms callback observes only those tickets, so the full eight-second committed reservation expires even after the last player disconnects and the ordinary mission tick returns early. This callback never issues another owner command, refreshes a lease or samples settlement. It is removed when the last ticket resolves and on Stop. PREPARING roster callbacks are no-ops and cannot permanently stop the service. Terminal/explicit Stop latches before notifying subscribers, releases owned tickets and removes subscriptions. No new persistent settings, public entity-selection RPC, automatic spawning or original Escapists resource is introduced.

Integration and checks

New production files:

  • addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingService.c
  • addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingOwner.c
  • addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingGameMode.c
  • addons/core/Scripts/Game/ERR/00_Core/ERR_LoadCasualtyAction.c

The successor Prisoner prefab owns the action and tools/check.py includes the casualty policy regression. The action reuses the existing Prisoner resource identity; new nested action/UI component IDs are successor-authored.

Offline checks execute production timing/range/aircraft-motion policy bodies and the complete repository suite. Native work must inspect the real spawned action and inherited medicine actions, show strict rejection for nonparticipants/old bodies/wrong phase, then exercise genuine two-player authorization and physical movement. Test cancellation during action progress, recovery during progress, duplicate callers, last-seat contention, native refusal, loss of replication visibility, delayed/reordered/expired handshake, disconnect/reconnect, aircraft loss and terminal transition. Confirm native seat reservation cleanup and actual single occupancy. Do not count synthetic fixture roster identities as proof of remote controller ownership.

Bounded local service fixture

ERR_NativeCasualtyServiceProbe.c is excluded outside WORKBENCH. Opt-in casualty-service uses Core steps 1400/1402 and a 45-second fixture deadline. It moves the actual local controlled player beside the real helicopter and causes native incapacitation once; the ordinary owner-controller handshake and target roster checks remain real. Only a separately spawned physical helper receives a synthetic CurrentLife adapter. Before enabling that adapter, the fixture explicitly checks real-target membership and strict rejection of the helper, including in a valid physical extraction state. This is not a second authenticated network player.

The fixture deliberately advances legal mission phases to reach extraction; it does not claim a completed radio objective. It checks a fresh settling window, native external bench reservations, a physical successor wall blocking sight, the real action's completion boundary, duplicate requests, actual player occupancy, reservation release, preserved target identity and idempotent Stop. It does not simulate holding action input, remote packet timing or Stop during an in-flight command. It retains inherited medical-action presence. On completion the normal test reset ends the round; cleanup deletes only the helper/obstacle and releases fixture-owned reservations, with no forced recovery, seated-player teleport, body deletion or unrelated health restoration.

The WORKBENCH mode override routes owner callbacks to the helper adapter only during this explicit case. Core smoke and the hidden runner expose the opt-in casualty-service case. 075514 completes this local fixture with 87 passing assertions and zero engine or script/VM errors. The production one-shot initial placement lets the original aircraft settle naturally; separate raised, tilted and moving aircraft are rejected. A single actual native damage request incapacitates the controlled player, then the real action completion boundary and owner-controller handshake load that living body into the selected child bench exactly once. External reservations and physical obstruction reject requests; successful loading releases the reservation, retains player identity and survives idempotent Stop without another completion.

The real local controller takes the server/local fast path; this does not exercise remote Owner/Server RPC transit. Only the helper membership is adapted, and action completion is invoked directly rather than holding input for five seconds. The fixture requests its own reset after loading, without a complete radio-to-extraction playthrough. Real two-player input, latency/reordering, disconnect/death while pending, last-seat contention and entity-removal cleanup remain separate requirements. Earlier contact and damage-fixture failures are preserved in the validation log.

The separate stock removal fixture passes in 080058: the helicopter's inherited door action removes a test-owned casualty from a real bench, leaving it living and incapacitated beside the authored exit with the seat vacant and accessible. This supplements loading with a local native removal path. It is not the production service's real-player/remote handshake, held action input, or a new extraction-result calculation.

The source-local regression in test_casualty_reservation_death.py also executes the actual pending-observation and callback ownership methods without ordinary round ticks: it retains a committed seat just before eight seconds, resolves it once at expiry, cancels the callback, and checks duplicate scheduling, uncommitted expiry, Stop and reentrant request scheduling. This is deterministic scheduling evidence, not a native network timing claim.

Native regression 102037 passes 101 assertions with zero captured errors after the pending-maintenance fix: real owner handshake, physical incapacitated-player loading exactly once, reservation release and idempotent Stop remain correct. The established fixture still uses an explicit helper-only membership adapter; it is not a real empty-server/disconnect or multiplayer session.