⚙️TURBULENCE — Tech Implementation
Spec
⚙️

TURBULENCE — Tech Implementation

Complete Engineering Specification

Unity 6.3 LTS
2-4 Online Co-op
Technical Document
$7.99
Download .md

TURBULENCE — Complete Technical Implementation (MVP)

Unity 6.3 LTS / URP / NGO / Steam / Dissonance

Version 1.1 — Fully Expanded Engineering Spec


0) Read This First

This document is the engineering playbook for building TURBULENCE MVP end-to-end.

It is intentionally implementation-heavy:

  • Exact stack decisions
  • Concrete script/class layout
  • Data models
  • Network sync ownership rules
  • Build order by sprint
  • Known gotchas and fixes

If a decision conflicts with shipping velocity, prefer:

  1. Playable/funny now over perfect architecture
  2. Host-authoritative simplicity over prediction complexity
  3. Data-driven crisis content over adding more systems

1) Final Tech Stack (Current)

1.1 Engine + Rendering

  • Unity 6.3 LTS (6000.3.x)
  • URP
  • .NET profile default for Unity 6

Why:

  • LTS stability for shipping
  • URP gives best perf-per-effort for stylized low-poly
  • Keeps low-end PC target realistic

1.2 Multiplayer + Transport

  • Netcode for GameObjects (NGO)
  • Unity Transport via Steam relay path (Steam lobbies + P2P)
  • Host-authoritative simulation

Why:

  • 2–4 co-op is NGO sweet spot
  • Multiplayer Playmode improves iteration speed
  • Host-authoritative physics is simplest stable model

1.3 Voice

  • Dissonance Voice Chat
  • Positional channels + zone attenuation + intercom override

Why:

  • Proven in friendslop style games
  • Fast setup
  • Low integration risk vs custom voice stack

1.4 Platform Services

  • Facepunch.Steamworks
  • Steam lobbies, invites, rich presence, achievements, cloud save

1.5 Content Data

  • ScriptableObjects for crisis definitions, item defs, flight presets
  • JSON for tunables only when hot-reloading is required

2) Project Setup (Step-by-Step)

2.1 Create Project

  1. Unity Hub → New project
  2. Template: 3D (URP)
  3. Name: Turbulence
  4. Version: Unity 6.3 LTS
  5. Create in Git repo root

2.2 Install Packages

Use Package Manager (Unity Registry unless noted):

  • Netcode for GameObjects
  • Unity Transport
  • Multiplayer Tools
  • Input System
  • Cinemachine
  • Splines (optional for flight path authoring)
  • Addressables (optional for content packaging)
  • Localization (optional)

Asset Store:

  • Dissonance Voice Chat

2.3 Player Settings

  • Active Input Handling: Input System Package (New)
  • Color Space: Linear
  • VSync: Off (in-game option)
  • Target Frame Rate: 120 (cap in settings)

2.4 Physics Settings

  • Fixed Timestep: 0.02
  • Max Allowed Timestep: 0.1
  • Solver Iterations: 8 (start)
  • Solver Velocity Iterations: 2
  • Revisit after perf tests

2.5 Layer Matrix

Create layers:

  • Player
  • PlayerRagdoll
  • Interactable
  • HeldItem
  • Environment
  • PlaneInterior
  • TriggerOnly
  • NPCPassenger
  • VoiceZone

Collision rules:

  • HeldItem ignores PlayerRagdoll small colliders (reduce jitter)
  • TriggerOnly collides with none
  • VoiceZone no collisions

3) Repository Structure

Assets/
  Art/
  Audio/
  VFX/
  Prefabs/
    Core/
    Player/
    Plane/
    Items/
    NPC/
  Scenes/
    Boot.unity
    Terminal.unity
    Flight_BudgetLiner.unity
    Flight_PuddleJumper.unity
    Flight_RedEye.unity
  Data/
    Crisis/
    Flights/
    Items/
    Audio/
  Scripts/
    Core/
      Bootstrap/
      Services/
      Utilities/
    Networking/
    Player/
      Controllers/
      State/
      Interaction/
      Camera/
    Plane/
      Flight/
      Damage/
      Zones/
      Wind/
    Crisis/
      Runtime/
      Definitions/
      Effects/
    Interaction/
      Interactables/
      Tools/
      Carry/
    NPC/
      Passenger/
    UI/
      HUD/
      Menus/
      FlightRecorder/
    Audio/
      Runtime/
      Voice/
    Steam/
    Save/
Packages/
ProjectSettings/

4) High-Level Runtime Architecture

GameBootstrap
  -> ServiceLocator
      -> NetworkService
      -> SteamService
      -> SaveService
      -> AudioService
      -> VoiceService
      -> SceneFlowService

Terminal Scene
  -> LobbyController
  -> FlightSelectionController
  -> CharacterLoadoutController

Flight Scene
  -> FlightOrchestrator (authoritative on host)
      -> PlaneController
      -> CrisisManager
      -> NPCPassengerManager
      -> SpawnManager
      -> LandingEvaluator
      -> FlightRecorder

Core rule:

  • Host owns world truth (plane state, crisis state, item physics)
  • Clients own local input + presentation

5) Network Ownership Rules (Critical)

SystemAuthoritySync Type
Plane transform & flight stateHostNetworkVariables + periodic state RPC
Physics objects (items, cart, luggage)HostNetworkTransform/NetworkRigidbody
Player locomotionOwner → Host validatesNGO Character sync pattern
Player state machineHost authoritative writeNetworkVariables
RagdollHost triggers state, client sim localEvent RPC + root sync only
Crisis lifecycleHost onlyEvent RPC + replicated crisis state
NPC passenger logicHostTransform + anim params replicated
Voice zonesClient-side evaluation + shared configNo network needed for proximity

Hard rule:

  • Clients never directly mutate gameplay-critical physics state.

6) Core Class Map (MVP)

6.1 Core/Bootstrap

  • GameBootstrap (MonoBehaviour)
  • ServiceRegistry
  • GameConfigSO

6.2 Networking

  • NetworkSessionManager (host/client start, disconnect handling)
  • NetworkPrefabCatalog
  • AuthorityDebugOverlay

6.3 Player

  • PlayerNetworkAvatar : NetworkBehaviour
  • PlayerInputRouter
  • PlayerMotor
  • PlayerStateMachine
  • PlayerInteractionController
  • PlayerCarryController
  • PlayerRagdollController
  • PlayerCameraController

6.4 Plane

  • PlaneController : NetworkBehaviour
  • PlaneFlightModel
  • PlaneDamageModel
  • PlaneZoneManager
  • PlaneTurbulenceController
  • WingWindVolume

6.5 Crisis

  • CrisisManager : NetworkBehaviour
  • CrisisRuntimeInstance
  • CrisisDefinitionSO
  • CrisisScheduler
  • CrisisCascadeGraph

6.6 Interaction

  • InteractableBase
  • RepairableInteractable
  • ToolItem
  • GrabbableObject : NetworkBehaviour
  • DoorInteractable
  • SeatInteractable

6.7 NPC

  • PassengerAgent : NetworkBehaviour
  • PassengerBrain
  • PassengerPanicSystem
  • PassengerSeatSystem

6.8 UI

  • HUDController
  • PromptController
  • VoiceIndicatorUI
  • FlightRecorderController

6.9 Audio

  • AudioDirector
  • AlarmRouter
  • PlaneMixController
  • VoiceZoneBridge

6.10 Steam/Save

  • SteamPlatformService
  • AchievementService
  • CloudSaveService
  • ProfileSaveService

7) Data Definitions (ScriptableObjects)

7.1 CrisisDefinitionSO

[CreateAssetMenu]
public class CrisisDefinitionSO : ScriptableObject
{
    public string crisisId;
    public CrisisType type;
    public CrisisSeverity severity;
    public ZoneId[] validZones;
    public ToolType[] requiredTools;
    public float baseFixDuration;
    public float unfixedEscalationSeconds;
    public string[] cascadeToCrisisIds;
    public AnimationCurve severityOverTime;
    public AudioCueSet cues;
    public VfxSet vfx;
}

7.2 FlightPresetSO

[CreateAssetMenu]
public class FlightPresetSO : ScriptableObject
{
    public string flightId;
    public PlaneType planeType;
    public float targetDurationSec;
    public WeatherProfile weather;
    public CrisisPool pool;
    public AnimationCurve crisisDensityCurve;
    public int maxConcurrentCrises;
    public LandingDifficulty landingDifficulty;
}

7.3 ItemDefinitionSO

[CreateAssetMenu]
public class ItemDefinitionSO : ScriptableObject
{
    public string itemId;
    public ToolType toolType;
    public float mass;
    public CarryMode carryMode;
    public float throwVelocityMultiplier;
    public bool consumable;
    public int charges;
}

8) Scene Flow

8.1 Boot

  • Initialize services
  • Init Steam
  • Load profile
  • Go Terminal

8.2 Terminal

  • Host or join lobby
  • Character cosmetics
  • Select flight preset
  • Ready check
  • Load flight scene as network scene

8.3 Flight

  • Spawn players at boarding points
  • Intro/takeoff state
  • Start crisis scheduler
  • End on landing/crash
  • Show flight recorder
  • Return terminal or next flight

9) Player Controller System

9.1 PlayerNetworkAvatar

Responsibilities:

  • Own NetworkVariables for replicated state
  • Handle owner input RPC envelope to host
  • Expose references to subsystems

NetworkVariables (authoritative on host):

  • MoveState
  • ConditionState (Normal/Dazed/etc)
  • HeldItemLeftNetId
  • HeldItemRightNetId
  • IsRagdolled
  • IsUnconscious

9.2 Input Routing

Owner-only input in Update:

  • Move / Look
  • Interact pressed/held
  • Grab / Drop / Throw
  • HoldOn
  • Crouch / Jump

Input packet sent to host at fixed cadence (or command RPC on change).

9.3 Player State Machine

States:

  • Normal
  • Dazed
  • Winded
  • Unconscious
  • SuckedOut

Host transitions only. Client reads NetworkVariable and updates animation/postfx.

Transition sample:

if (impact > dazeThreshold) -> Dazed(3s)
if (impact > knockoutThreshold) -> Unconscious(10s)
if (inSuctionVolume && noMask) -> SuckedOut(15s respawn)

10) Grab/Carry/Throw Implementation

10.1 Detection

  • Center-screen raycast from camera
  • Range 2.5m
  • Hit Interactable or Grabbable layer

10.2 Grabbing

For physics stability in networked host-authoritative setup:

  • Host creates ConfigurableJoint from player hand anchor to object
  • Object switches to HeldItem layer
  • Increase interpolation
  • Optional damped position drive to reduce jitter

Carry modes:

  • One-handed: one anchor
  • Two-handed: dual anchor or center chest anchor + movement speed penalty

10.3 Throw

  • Release joint on host
  • Apply velocity = cameraForward * baseForce * itemMultiplier
  • Clamp magnitude
  • Add angular velocity randomization for comedic tumble

10.4 Network Rules

  • Client requests via ServerRpc
  • Host validates distance/ownership/state
  • Host applies changes
  • Host mirrors to clients via state replication

10.5 Gotchas

  • Jitter if object mass too high vs drive strength
  • Tunneling for thrown items: set CCD on throwable rigidbodies
  • Joint break on turbulence: listen OnJointBreak and force drop cleanup

11) Plane Flight + Tilt Model

11.1 PlaneController

Host-owned source of truth:

  • Pitch, roll, yaw
  • Airspeed
  • Altitude proxy
  • Damage modifiers

Input sources:

  • Pilot controls
  • Autopilot assist
  • Damage-induced drift
  • Turbulence impulses

11.2 Interior Gravity Strategy

Preferred MVP strategy:

  • Keep world gravity standard
  • Rotate plane interior root transform physically
  • Interior rigidbodies childed under interior root
  • Sliding naturally occurs by transform tilt + gravity

Alternative custom gravity optional later.

11.3 Handling Curves

Use curves by damage level:

  • PitchResponseCurve
  • RollResponseCurve
  • StabilityDampingCurve
  • StallRiskCurve

When engine out:

  • Add constant roll bias
  • Reduce max climb
  • Increase control noise

11.4 Autopilot

Simple flawed PID-like stabilizer:

  • Target roll=0, pitch=cruise
  • Random drift every N seconds
  • Random disengage chance under high damage

12) Turbulence System

12.1 Turbulence Events

Data-driven profiles:

  • Light
  • Medium
  • Severe

Each event defines:

  • Duration
  • Impulse frequency
  • Impulse magnitude range
  • Camera shake profile
  • Alarm cues

12.2 Application

Host applies:

  • Plane impulse modifiers
  • Random localized force pulses to loose rigidbodies
  • Condition checks for players not holding on

Players:

  • If IsHoldingOn: no ragdoll
  • Else: apply ragdoll trigger if impulse threshold exceeded

12.3 Scheduler

Tied to flight phase + weather profile

  • Phase 1: low chance
  • Phase 3+: rising frequency
  • Severe only in critical windows

13) Ragdoll System

13.1 Architecture

  • Normal character uses animated rig + capsule controller
  • On ragdoll: disable motor, enable physics rig
  • Host sets state flag + sends ragdoll event
  • Clients run local ragdoll simulation

13.2 Sync Model

  • Root/hip transform periodically synced
  • Limb bones not synced (cosmetic)
  • Recovery snaps with blend-to-animation from root

13.3 Recovery

  • Timer based or assist interaction
  • Re-enable capsule after clearance check
  • Short invulnerability window (0.5s) to prevent ragdoll loops

14) Crisis System (Full)

14.1 CrisisManager State Machine

Idle -> Warmup -> Active -> Critical -> Landing -> Complete

14.2 Runtime Instance

Each active crisis has:

  • instanceId
  • definitionRef
  • zone
  • startTime
  • currentSeverity
  • fixProgress
  • isResolved

14.3 Scheduler Algorithm

Every tick window (e.g. 1s host):

  1. Evaluate target concurrent crises from density curve
  2. If active < target, spawn weighted from valid pool
  3. Weights modified by:
    • Avoid duplicates in same zone
    • Boost cascades that make narrative sense
    • Respect cooldowns

14.4 Cascade Graph

If crisis unresolved beyond threshold:

  • Trigger linked crisis IDs
  • Optionally increase global panic index

Global panic index affects:

  • Passenger behavior
  • Alarm intensity
  • New crisis severity floor

14.5 Resolution

Repairable crisis:

  • Player with required tool enters hold interaction
  • Progress ticks while uninterrupted
  • On completion host marks resolved, fires cues, score event

Interruptions:

  • Turbulence pulse
  • Player released interaction
  • Tool out of charge

15) Interactable Framework

15.1 Base

public abstract class InteractableBase : NetworkBehaviour
{
    public abstract bool CanInteract(PlayerNetworkAvatar player);
    public abstract void BeginInteract(PlayerNetworkAvatar player);
    public abstract void TickInteract(PlayerNetworkAvatar player, float dt);
    public abstract void EndInteract(PlayerNetworkAvatar player, bool cancelled);
}

15.2 RepairableInteractable

Adds:

  • Required tool list
  • Progress network state
  • Duration
  • Fail conditions
  • VFX/audio hooks

15.3 Prompt Integration

Player raycast -> current target interactable -> prompt text from interactable metadata


16) Plane Damage Model

16.1 Systems

  • EngineLeft
  • EngineRight
  • Hydraulics
  • Electrical
  • LandingGear
  • FuselageIntegrity
  • DoorSeals

Each has:

  • Health [0..100]
  • Degradation rate under active crisis
  • Threshold events (warn/critical/fail)

16.2 Effects by Failure

  • Engine fail -> thrust loss + roll bias
  • Hydraulics fail -> reduced control authority
  • Electrical fail -> blackout/intercom down
  • Gear fail -> belly landing scoring penalties
  • Fuselage low -> depressurization risk

16.3 Visual Damage

  • Mesh variant toggles
  • Material damage masks
  • Decals
  • Particle anchors per system

17) Zone System

Zone IDs:

  • Cockpit
  • FirstClass
  • Economy
  • CargoHold
  • Wing

Zone volumes drive:

  • Voice attenuation rules
  • Crisis validity
  • Music/ambience sends
  • Spawn points and navigation constraints

Implementation:

  • PlaneZoneVolume trigger component registers enter/exit per player
  • Zone membership cached in PlayerNetworkAvatar

18) Voice + Intercom

18.1 Proximity

Dissonance positional voice with custom attenuation curves per zone pairing.

18.2 Intercom

Repairable electrical-linked toggle:

  • Off: normal positional only
  • On: add global low-volume bus for all players + local remains
  • If electrical fails: intercom drops hard

18.3 UX

HUD icon indicates:

  • Mic active
  • Intercom active
  • Current zone

19) NPC Passenger System

19.1 States

  • Seated
  • Alerted
  • Panicking
  • MovingToExit
  • Incapacitated

19.2 Panic Propagation

Events add panic score:

  • Nearby explosion/fire
  • Severe turbulence
  • Door breach

If score threshold exceeded -> Panicking. Nearby panicked NPCs increase neighbors over time.

19.3 Seat Logic

Each passenger has assigned seat anchor. When stable, return to seat. When panic, temporary wander/exit behavior.

Network: host sim only, clients receive transform/anim params.


20) Landing Evaluation

20.1 Inputs to Evaluator

  • Vertical speed at touchdown
  • Horizontal speed
  • Roll/pitch angle
  • Gear state
  • Active critical failures
  • Passenger casualties

20.2 Outcome Bands

  • Clean-ish landing
  • Rough landing
  • Catastrophic landing
  • Crash

20.3 Scoring Components

  • Plane integrity remaining
  • Crises resolved count
  • Passenger survival
  • Time efficiency
  • Team incident penalties

21) Flight Recorder (Post-Round)

21.1 Event Logging

Host logs compact events:

  • RepairStart/Complete
  • CrisisSpawn/Resolve/Fail
  • RagdollStart/End
  • UnconsciousStart/End
  • PilotControlTime
  • DamageCaused

Data structure:

public struct FlightEvent
{
    public FlightEventType Type;
    public ulong ActorClientId;
    public float Time;
    public int A;
    public int B;
}

21.2 Award Pass

At round end host computes award winners and sends immutable summary to clients.


22) UI Architecture

22.1 HUD

  • Interaction prompt
  • Tool/charge indicator
  • Voice indicators
  • Minimal crisis hint text (optional accessibility mode)

22.2 Menus

  • Terminal scene menu
  • Flight end summary
  • Settings (graphics/audio/controls/voice)

22.3 Responsive Rules

  • Prevent any horizontal overflow
  • All markdown tables wrapped in own scroll containers

23) Save/Progression

Profile save payload:

[Serializable]
public class PlayerProfileSave
{
    public int Version;
    public string PlayerId;
    public List<string> UnlockedCosmetics;
    public List<string> UnlockedFlights;
    public Dictionary<string,int> StatCounters;
    public SettingsData Settings;
}

Store local + Steam Cloud sync. Version migration function required.


24) Steam Integration Flow

24.1 Host Flow

  1. Init Steam API
  2. Create Steam lobby
  3. Set metadata (flight id, max players, privacy)
  4. Start NGO host
  5. Publish join code/presence

24.2 Join Flow

  1. Accept invite or browse friend lobby
  2. Connect to lobby
  3. Start NGO client with transport settings
  4. On network spawn, load player profile cosmetics

24.3 Rich Presence

Update presence keys:

  • state: In Flight / In Terminal
  • flight: Budget Liner
  • party_size

25) Audio Mix Plan

Buses:

  • Master
  • SFX
  • Dialogue/PA
  • VoiceChat
  • Alarms
  • Ambience
  • Music

Dynamic ducking:

  • Severe alarms duck ambience by -6dB
  • Intercom announcements duck SFX by -3dB
  • Voice chat priority above ambience

26) Performance Plan

26.1 Physics

  • Sleep thresholds tuned per object class
  • Disable unnecessary rigidbodies in far zones
  • Use compound colliders, avoid mesh colliders on dynamic objects
  • Cap active debris count (pool old debris)

26.2 Rendering

  • GPU instancing for repeated cabin props
  • LOD groups for passengers/items
  • Baked lighting where possible, limited dynamic lights

26.3 Networking

  • Lower update rate on low-priority objects
  • Interest management by zone (future optimization)
  • Avoid per-frame RPC spam; prefer batched state

27) Sprint-by-Sprint Build Order (Executable)

Sprint 1 (Week 1)

  • Project setup, packages, input, scene boot
  • Network host/client connect test
  • Basic player locomotion networked

Exit criteria:

  • 2 clients can join and move in same scene

Sprint 2 (Week 2)

  • Plane interior graybox
  • Grab/carry/drop baseline
  • Basic plane tilt manually controlled

Exit criteria:

  • Funny sliding objects in tilted cabin with 2 players

Sprint 3 (Week 3)

  • Flight controls (yoke/throttle)
  • PlaneController host-authoritative
  • Turbulence event prototype

Exit criteria:

  • Start-to-end 2-minute flight simulation sandbox

Sprint 4 (Week 4)

  • CrisisManager v1
  • 3 crisis types (engine fire, electrical short, fuselage crack)
  • Repair interactions with tool requirements

Exit criteria:

  • One full playable loop (takeoff->crisis->landing)

Sprint 5 (Week 5)

  • Ragdoll system + unconscious state
  • Hold-on mechanic
  • Door breach suction prototype

Exit criteria:

  • Turbulence meaningfully disrupts repair flow

Sprint 6 (Week 6)

  • Voice integration (Dissonance)
  • Zone attenuation
  • Intercom repairable system

Exit criteria:

  • Cockpit/cargo communication difference works

Sprint 7 (Week 7)

  • NPC passenger manager baseline
  • Panic propagation
  • Passenger-related scoring hooks

Exit criteria:

  • Passengers visibly react and affect outcomes

Sprint 8 (Week 8)

  • Landing evaluator
  • Round end + Flight Recorder awards
  • Save profile skeleton

Exit criteria:

  • End-to-end vertical slice with post-round summary

Sprint 9–10

  • Content expansion to MVP crisis set
  • Puddle Jumper + Budget Liner production pass
  • Art polish phase 1

Sprint 11–12

  • Red Eye variant
  • Audio/VFX full pass
  • Optimization + QA harness

Sprint 13–14

  • Steam achievements/cloud/rich presence
  • Balancing and tuning
  • Bug burn-down

Sprint 15–16

  • Playtest weekend cycles
  • Store assets/trailer
  • Release candidate + launch patch prep

28) Key Code Patterns (Concrete)

28.1 Host-only mutation guard

if (!IsServer) return;

Use in all gameplay mutation methods.

28.2 Client request pattern

[ServerRpc(RequireOwnership = false)]
private void RequestInteractServerRpc(ulong interactableNetId)
{
    if (!ValidateRequest(interactableNetId)) return;
    ApplyInteraction(interactableNetId);
}

28.3 NetworkVariable with server write

public NetworkVariable<PlayerCondition> Condition =
    new(writePerm: NetworkVariableWritePermission.Server);

28.4 Crisis spawn weighted selection

var candidates = pool.Where(c => c.CanSpawn(context)).ToList();
float total = candidates.Sum(c => c.GetWeight(context));
float r = Random.value * total;
foreach (var c in candidates)
{
    r -= c.GetWeight(context);
    if (r <= 0f) return c;
}

29) QA Test Matrix (MVP)

29.1 Network

  • Host disconnect mid-flight
  • Client reconnect behavior (optional)
  • High ping simulation (100/200/300ms)
  • Packet loss simulation 5/10%

29.2 Physics

  • 4 players all carrying heavy items in aisle
  • Severe turbulence with 50 loose objects
  • Wing repairs during storm impulses

29.3 Crisis

  • Simultaneous 5 active crises
  • Cascade loops do not deadlock
  • Repair interruption edge cases

29.4 Voice

  • Zone transitions while speaking
  • Intercom on/off while talking
  • Mute/deafen behavior

29.5 Save/Progression

  • Version upgrade migration
  • Cloud sync conflict resolution

30) Build, Release, and Ops

30.1 Build Profiles

  • Dev build (logs on)
  • QA build (perf HUD)
  • Release build (stripped logs)

30.2 SteamPipe

  • AppID setup
  • Depot split optional (content/binaries)
  • Branches: internal, playtest, public

30.3 Crash/Telemetry

Minimal MVP telemetry:

  • Flight completion rate
  • Crash reason histogram
  • Average active crises
  • Top fail moments

Use to tune difficulty and pacing.


31) Definition of Done (MVP)

MVP is done when all are true:

  1. 2–4 players can host/join and complete a flight loop end-to-end
  2. At least 10 crisis types working with cascades
  3. Turbulence + ragdoll + hold-on is stable and funny
  4. One production-quality plane (Budget Liner) + one simpler variant (Puddle Jumper)
  5. Flight Recorder awards and progression persist
  6. No major desync bugs in 30-minute 4-player session
  7. 60 FPS on min spec target in average gameplay

32) Known Hard Problems (and practical answers)

Problem: networked ragdolls look different

Answer: acceptable by design. Sync root only. Prioritize responsiveness.

Problem: physics jitter while carrying

Answer: lower object mass, increase joint damping, tune break force by item class.

Problem: too many objects tank perf

Answer: debris pooling + cap active loose items + sleep aggressively.

Problem: players idle in 4-player sessions

Answer: increase concurrent crises + zone-separated tasks + role prompts.

Problem: voice chaos unintelligible

Answer: add optional push-to-talk + per-zone attenuation tuning + intercom clarity EQ.


33) Immediate Next Actions (today)

  1. Lock Unity version to 6.3 LTS in README + team agreement
  2. Create Boot + Terminal + Flight graybox scenes
  3. Implement network session bootstrap (host/join)
  4. Build Player locomotion + interact raycast
  5. Build GrabbableObject + carry joint prototype
  6. Build Plane tilt sandbox + loose object test
  7. Run first 2-player fun check

If this first sandbox is funny within 48 hours, continue. If not, pivot before content production.


34) Appendix — Minimal Interfaces

public interface IRepairable
{
    bool CanRepair(PlayerNetworkAvatar player, ToolType tool);
    void BeginRepair(PlayerNetworkAvatar player);
    void TickRepair(PlayerNetworkAvatar player, float dt);
    void EndRepair(PlayerNetworkAvatar player, bool cancelled);
}

public interface ICrisisRuntime
{
    string Id { get; }
    ZoneId Zone { get; }
    CrisisSeverity Severity { get; }
    void OnSpawn();
    void Tick(float dt);
    void Resolve();
    void FailEscalate();
}

public interface IPlaneSubsystem
{
    float Health { get; }
    bool IsCritical { get; }
    void ApplyDamage(float amount);
    void Repair(float amount);
}

Final Note

This is intentionally engineered for shipping velocity. If a system decision increases complexity without increasing clip density or round-to-round fun, cut it.

The game wins when four players can’t stop yelling and laughing inside a failing airplane.


35) System Implementation Playbooks (Expanded, Task-Level)

This section is the execution-grade expansion of Sections 8–26.
Every playbook includes:

  1. Numbered implementation tasks
  2. Acceptance criteria
  3. Required integration tests (mapped to QA IDs in Section 40)

35.1 Network Session Bootstrap + Lobby/Scene Travel Playbook

Goal: deterministic, debuggable host/client startup and travel flow from Terminal -> Flight -> Summary -> Terminal.

Tasks

  1. Implement NetworkSessionManager with explicit states:
    • Offline, SteamInitializing, LobbyHosting, LobbyJoining, TransportConnecting, Connected, InFlight, Disconnecting, Error.
  2. Add a single transition gate method (TryTransition) that enforces legal transitions and logs rejected transitions.
  3. Integrate Facepunch lobby creation/join; persist LobbyId and lobby metadata snapshot in memory.
  4. Create NGO start wrappers:
    • StartHostSession()
    • StartClientSession(connectPayload)
    • ShutdownSession(reason)
  5. Build network scene travel wrapper around NGO scene manager with timeout watchdog (30s default).
  6. Add disconnect reason taxonomy:
    • TransportTimeout, Kicked, HostEnded, SteamAuthFailed, SceneSyncFailed, VersionMismatch, Unknown.
  7. Show deterministic user-facing error dialog per reason and return to Terminal safely.
  8. Add replay-safe run ID (FlightRunId) generated on host and replicated to all clients for logging correlation.

Acceptance Criteria

  • Host can create lobby and transition to flight with 1–3 clients repeatedly without frozen sessions.
  • Failed joins always return player to recoverable Terminal state within 10 seconds of failure.
  • Scene sync timeout never soft-locks UI.
  • All players in a flight share same FlightRunId value in logs.

Integration Tests

  • NET-001, NET-002, NET-003, NET-004, OPS-003

35.2 Player Locomotion + Condition State Playbook

Goal: responsive local input with host-authoritative state outcomes (daze/unconscious/sucked-out).

Tasks

  1. Split owner-local input collection from host simulation:
    • Client: input sampling, camera, animation previsualization.
    • Host: authoritative movement/state validation.
  2. Implement input command struct with monotonic tick and client sequence number.
  3. Add command buffering and staleness rejection on host (drop commands older than maxCommandAgeMs).
  4. Implement PlayerStateMachine transitions only on host; replicate with NetworkVariable<PlayerCondition>.
  5. Add safe position correction policy:
    • hard snap if error > 2.0m
    • soft correction if error between 0.25m–2.0m.
  6. Implement hold-on modifier that reduces knockdown chance and movement speed.
  7. Add condition recovery timers and assist-based revives.
  8. Emit player-state telemetry events to Flight Recorder and debug overlay.

Acceptance Criteria

  • Player does not enter invalid state combinations (e.g., Unconscious + Interacting + Carrying).
  • 200ms simulated RTT remains playable with no state desync lasting > 2 seconds.
  • Host corrections do not cause animation spam or camera jitter loops.

Integration Tests

  • PLY-001, PLY-002, PLY-003, NET-006, CRS-006

35.3 Interaction / Repair / Carry / Throw Playbook

Goal: stable interaction stack under turbulence and multiplayer concurrency.

Tasks

  1. Implement InteractableBase contract plus standardized interaction token (InteractionSessionId).
  2. Build interaction request validation:
    • in range
    • LOS valid
    • player condition allows interaction
    • required tool present.
  3. Add server-authoritative progress loop for repair interactables.
  4. Implement cancellation reasons enum:
    • PlayerReleased, OutOfRange, ImpulseInterrupted, ToolInvalid, TargetInvalidated.
  5. Implement PlayerCarryController with ConfigurableJoint tuning profiles by weight class.
  6. Add carry ownership handoff logic when item is dropped, thrown, or forcibly detached.
  7. Implement throw impulses with deterministic random seed from host tick + item net id.
  8. Add anti-spam cooldown on interact requests (100ms per target).
  9. Add full prompt context string generation from interactable + tool + condition.

Acceptance Criteria

  • Two players attempting same repair target resolves to one active interaction session.
  • Forced cancel under severe turbulence always clears progress lock and UI prompt within 250ms.
  • Thrown items are replicated consistently and never duplicate.

Integration Tests

  • INT-001, INT-002, INT-003, PHY-004, CRS-004

35.4 Plane Flight Model + Pilot Control Playbook

Goal: host-owned flight model that is tunable, chaos-friendly, and sync-safe.

Tasks

  1. Implement PlaneFlightModel pure simulation module with deterministic input struct.
  2. Drive model from host FixedUpdate with fixed timestep accumulator.
  3. Add control channels:
    • pitch input
    • roll input
    • yaw trim
    • throttle
    • autopilot target.
  4. Apply subsystem damage modifiers in ordered pipeline:
    1. pilot/autopilot
    2. stability damping
    3. subsystem penalties
    4. turbulence impulses.
  5. Replicate compressed plane state snapshot to clients at configured cadence.
  6. Implement client interpolation/extrapolation for non-owner view.
  7. Add cockpit interaction binding to mutate host pilot input only.
  8. Add safety clamps for impossible values and NaN protection fallback.

Acceptance Criteria

  • Host and client planes remain visually aligned (<0.5m position, <3° orientation average divergence at 150ms RTT).
  • Loss of one engine always produces expected roll bias and reduced climb.
  • Flight model never emits NaN/Inf during 30-minute soak.

Integration Tests

  • PLN-001, PLN-002, NET-005, OPS-002

35.5 Turbulence + Impulse Delivery Playbook

Goal: turbulence causes readable chaos without uncontrollable unfairness.

Tasks

  1. Implement turbulence profile assets (Light, Medium, Severe, Catastrophic for debug).
  2. Build host scheduler using flight phase + weather + panic index multipliers.
  3. Emit impulse events with seeded RNG for reproducibility (runSeed + hostTick).
  4. Apply impulse channels separately:
    • plane global impulse
    • loose rigidbody pulse
    • camera shake cue
    • knockdown check.
  5. Add min-gap and max-clump constraints to avoid impossible spam.
  6. Add hold-on immunity window and grace period after recovery.
  7. Replicate turbulence state summary to clients for synchronized audiovisuals.
  8. Log every severe impulse to Flight Recorder event stream.

Acceptance Criteria

  • Severe turbulence statistically causes higher knockdown rate than medium, but hold-on reduces knockdowns by configured target percent.
  • No impulse event can chain ragdoll/unconscious loops infinitely.
  • Turbulence visuals and audio stay in phase across clients (<150ms perceived drift).

Integration Tests

  • TRB-001, TRB-002, TRB-003, PLY-004, AUD-003

35.6 Ragdoll / Recovery / Unconscious Playbook

Goal: ragdoll is funny, recoverable, and networking-safe.

Tasks

  1. Build ragdoll rig activation/deactivation utility with strict one-way transitions.
  2. Host triggers ragdoll state; clients simulate locally.
  3. Replicate root transform and state timestamps (start tick, recover tick).
  4. Implement recovery blending pipeline:
    • freeze ragdoll root
    • align anim root
    • blend over recoverBlendDuration.
  5. Add unconscious timeout + teammate assist revive interaction.
  6. Add anti-loop guard (cooldown after recovering before next ragdoll trigger).
  7. Ensure carried items are dropped safely before ragdoll activation.

Acceptance Criteria

  • Player can always recover from ragdoll/unconscious unless explicitly dead/sucked-out.
  • Recovery never spawns player inside collision geometry.
  • Networked observers see coherent recovery timing.

Integration Tests

  • RAG-001, RAG-002, RAG-003, INT-005

35.7 Crisis Spawn / Cascade / Resolve Playbook

Goal: robust crisis orchestration that scales difficulty and creates cooperative task pressure.

Tasks

  1. Implement CrisisManager authoritative state machine with explicit phase gates.
  2. Build weighted crisis selection with duplicate suppression and cooldown enforcement.
  3. Instantiate CrisisRuntimeInstance with immutable instanceId, start tick, origin cause.
  4. Implement unresolved escalation timers and cascade triggers.
  5. Enforce max concurrent crisis cap and per-zone cap.
  6. Add resolution hooks:
    • repair success
    • auto-resolve (if designed)
    • hard-fail.
  7. Publish crisis events to HUD, audio director, NPC panic, damage model.
  8. Add watchdog for stuck crises (if no progress for X sec, emit debug warning).

Acceptance Criteria

  • No crisis remains in “half-created” state after spawn failure.
  • Cascades respect cooldowns and cannot infinitely recurse.
  • Resolution updates all dependent systems in same host tick.

Integration Tests

  • CRS-001, CRS-002, CRS-003, CRS-005, NPC-004

35.8 Plane Damage + Subsystem Failures Playbook

Goal: subsystem health drives gameplay consequences and landing outcomes.

Tasks

  1. Implement PlaneSubsystemState for each subsystem with health, thresholds, and flags.
  2. Apply damage from active crises and turbulence spikes.
  3. Add threshold event dispatch (Warn, Critical, Failed, Recovered).
  4. Route threshold outcomes into plane handling modifiers + audiovisual effects.
  5. Implement repair interactions that restore subsystem health with diminishing returns.
  6. Add irrecoverable fail states for selected severe scenarios.
  7. Persist end-of-flight subsystem final states in recorder summary.

Acceptance Criteria

  • Subsystem thresholds trigger exactly once per crossing direction.
  • Repaired systems remove penalties within one simulation tick.
  • Landing evaluator reads consistent subsystem data at touchdown.

Integration Tests

  • DMG-001, DMG-002, PLN-003, LND-002

35.9 NPC Passenger + Panic Network Playbook

Goal: host-driven NPC behavior that creates pressure and readable chaos.

Tasks

  1. Implement PassengerAgent host-only AI tick and replicated movement state.
  2. Build panic score model with event contributions and decay.
  3. Add neighbor propagation by radius and LOS weighting.
  4. Add seat assignment/return logic with conflict resolution.
  5. Integrate panic index into global crisis pacing.
  6. Add incapacitation from severe turbulence and assist interactions.
  7. Replicate minimal NPC state payload (position, anim state, panic tier).

Acceptance Criteria

  • NPC state transitions are deterministic for same event stream and seed.
  • Panic propagation remains bounded (never exceeds configured cap).
  • Passenger casualty and panic outcomes influence landing score.

Integration Tests

  • NPC-001, NPC-002, NPC-003, CRS-007, LND-004

35.10 Voice + Intercom + Zone Attenuation Playbook

Goal: intelligible voice comms under noisy co-op chaos.

Tasks

  1. Configure Dissonance positional channels and room/zone attenuation matrix.
  2. Implement VoiceZoneBridge that maps current zone to attenuation preset.
  3. Build intercom repairable system tied to electrical subsystem health.
  4. Add intercom mix routing and ducking sidechain rules.
  5. Add UI indicators for speaking/intercom active/zone name.
  6. Implement failover when voice service unavailable (text prompt warning).
  7. Add settings for push-to-talk/VOX sensitivity/mute/deafen.

Acceptance Criteria

  • Zone transitions smoothly adjust voice gain without popping.
  • Intercom failure under electrical outage is immediate and recoverable after repair.
  • Players can still communicate with default fallback channel if positional mode fails.

Integration Tests

  • VOC-001, VOC-002, VOC-003, AUD-004

35.11 Landing Evaluator + Flight Recorder Playbook

Goal: trustworthy round result generation and post-flight attribution.

Tasks

  1. Implement LandingEvaluator deterministic score snapshot at touchdown.
  2. Build score component normalization and weighted aggregation.
  3. Implement FlightRecorder event ring buffer + final immutable summary blob.
  4. Add award pipeline (MVP medals) with deterministic tie-break rules.
  5. Replicate final result packet to clients with hash verification.
  6. Persist run summary into save profile (recent runs, stats counters).
  7. Add replay export (JSON) for QA/debug builds.

Acceptance Criteria

  • Host and client display identical final score and awards for same run.
  • No post-touchdown events can modify locked landing result.
  • Flight recorder export includes all required canonical fields.

Integration Tests

  • LND-001, LND-003, REC-001, REC-002, SAV-003

35.12 Save/Profile/Steam Cloud Playbook

Goal: resilient progression saves with migration and conflict handling.

Tasks

  1. Implement profile serialization with versioned schema.
  2. Add migration pipeline from old versions to current.
  3. Save locally first, then mirror to Steam Cloud.
  4. Implement conflict policy:
    • newest timestamp wins for settings
    • merge-additive for unlocks/stats
    • emit warning if incompatible.
  5. Add checksum/hash field for corruption detection.
  6. Implement autosave triggers at safe points (end of run, settings apply, unlock grant).
  7. Add backup copy rotation (profile.bak1, profile.bak2).

Acceptance Criteria

  • Corrupt save auto-recovers from backup when possible.
  • Migration from previous schema version never hard-crashes startup.
  • Cloud conflict resolution path is deterministic and logged.

Integration Tests

  • SAV-001, SAV-002, SAV-004, OPS-004

35.13 UI/HUD/Accessibility Playbook

Goal: clear information hierarchy under stress without clutter.

Tasks

  1. Implement HUD presenter architecture with event-driven state updates.
  2. Build interaction prompt stack with prioritization and cooldown.
  3. Add crisis panel compact mode + accessibility expanded mode.
  4. Add colorblind-safe status icons and non-color indicators.
  5. Add subtitle support for critical alarms/intercom cues.
  6. Add UI scalability presets (80%, 100%, 120%, 140%).
  7. Add fail-safe fallback: if HUD fails, minimal text overlay displays critical states.

Acceptance Criteria

  • Critical interaction prompts remain visible/readable at all supported resolutions.
  • Accessibility mode shows equivalent gameplay-critical information.
  • HUD state never blocks player input when menu hidden.

Integration Tests

  • UI-001, UI-002, UI-003, ACC-001

35.14 Build/Release/Ops Playbook

Goal: reproducible builds and operational confidence for launch.

Tasks

  1. Define CI build pipeline (lint -> type check -> build -> artifact publish).
  2. Add content checksum manifest generation per build.
  3. Add branch promotion workflow (internal -> playtest -> public).
  4. Integrate crash symbol upload and basic telemetry endpoint checks.
  5. Add release candidate checklist gate in CI.
  6. Implement emergency config override file for live balancing tweaks.
  7. Document rollback drill and assign release roles.

Acceptance Criteria

  • Release candidate build reproducible from tagged commit.
  • Hotfix/rollback branch can be produced within 30 minutes.
  • Build artifacts contain version metadata and commit hash.

Integration Tests

  • OPS-001, OPS-002, OPS-005, REL-001

36) Expanded Architecture: Cadence, Recovery, Repro, Migration, Trust Boundaries

36.1 Network Packet + Update Cadence Strategy (Per Subsystem)

SubsystemAuthorityTick/Send CadenceReliabilityPayload StrategyNotes
Player input commandsClient -> Host30 Hz command stream (delta)Unreliable SequencedCompact bit-packed input + seqHost drops stale/out-of-order
Player authoritative stateHost -> All20 Hz snapshot + event deltasMixed (snapshot unreliable, state change reliable)Position/velocity/condition packedReliable used only for state transitions
Plane flight stateHost -> All20 Hz snapshot, 50 Hz host simUnreliable Sequenced + periodic reliable keyframe (1 Hz)Quantized transform, speed, subsystem flagsKeyframe prevents long drift
Crisis lifecycle eventsHost -> AllEvent-drivenReliableSpawn/resolve/fail packets + IDsMust never be dropped
Crisis progress barsHost -> All10 HzUnreliablePercent + interacting player idLoss tolerated, next update corrects
Loose item transformsHost -> Interested clients15 Hz default; 25 Hz when recently thrownUnreliable SequencedPriority bucketsSleep state suppresses packets
NPC transforms/stateHost -> All10 Hz, LOD to 5 Hz distantUnreliable SequencedPosition + panic tier + anim flagsLow priority subsystem
Voice state metadataClient local + Dissonance10 Hz internalInternalSpeaking + channel + zoneNot gameplay-authoritative
HUD alertsHost -> ClientsEvent-drivenReliableAlert id + severity + TTLDeterministic queue order
Recorder summaryHost -> ClientsEnd-of-round onceReliableImmutable summary blob + hashClient validates hash
Save sync ackClient local + cloudOn save pointsReliable (service-level)Version + checksumRetries with backoff

Budget Targets

  • Total gameplay bandwidth target: <= 40 KB/s per client average, spikes <= 80 KB/s.
  • Max reliable gameplay packet bursts: <= 20 packets/sec to prevent head-of-line stalls.
  • Serialization CPU budget: <= 1.5 ms/frame host, <= 1.0 ms/frame client.

36.2 Failure Handling + Recovery Flows

36.2.1 Transport Drop / Host Timeout

  1. Detect no heartbeat for disconnectTimeoutMs.
  2. Freeze gameplay input locally and show "Reconnecting..." overlay.
  3. Attempt soft reconnect for softReconnectWindowSec (if enabled).
  4. If reconnect fails, transition to Terminal with reason and cached run summary if available.
  5. Persist local diagnostics bundle (run-id, last-tick, error-code).

36.2.2 Scene Sync Failure

  1. Watchdog starts on scene load event.
  2. If one or more clients stall past timeout:
    • host sends retry sync command once.
    • if still failed, host offers vote/force-remove stalled client (MVP: force remove after grace).
  3. Remaining players continue session with updated player count difficulty scaling.

36.2.3 Crisis Runtime Corruption Guard

  1. Validate every spawned crisis against schema and zone validity.
  2. If invalid runtime detected:
    • quarantine crisis instance
    • emit critical log + telemetry marker
    • reduce target concurrency by one for remainder of run.
  3. Continue run without hard crash.

36.2.4 Save Write Failure

  1. On local save failure, retry up to 3 times with exponential backoff.
  2. If still failing, write emergency temp save and notify user once.
  3. Queue cloud sync for next successful boot.

36.2.5 Voice Service Failure

  1. Detect Dissonance init or runtime failure.
  2. Switch to fallback mode: UI text warnings + ping wheel suggestion.
  3. Keep gameplay unaffected; do not block match start.

36.3 Deterministic-ish Reproducibility Strategy (Bug Capture)

Full deterministic lockstep is out of scope; goal is reproducible-enough captures for triage.

Core Strategy

  • Host generates RunSeed on match start.
  • Every stochastic system consumes RNG from named streams:
    • rng_crisis, rng_turbulence, rng_npc, rng_awards.
  • Record deterministic anchors each second:
    • host tick
    • plane state hash
    • active crisis list hash
    • subsystem health hash.
  • Log canonical event stream (ordered by host tick + event ordinal).

Debug Capture Artifact (.turbrepro)

  • Header:
    • build version
    • git commit hash
    • run id
    • run seed
    • player roster
  • Timeline:
    • keyframe snapshot every 5s
    • event deltas between keyframes
  • Integrity:
    • rolling hash every 1s to detect divergence.

Repro Workflow

  1. QA reproduces issue and exports .turbrepro.
  2. Engineer loads repro in internal replay scene.
  3. Replay runs host simulation from seed + events.
  4. Divergence detector reports first tick mismatch with subsystem tags.

Known Limits

  • Physics and ragdoll not strictly deterministic across hardware.
  • Replay intent is diagnosis and approximate step-through, not perfect cinematic reconstruction.

36.4 Host Migration Strategy (Deferred by Design)

Why Deferred for MVP

  • NGO + host-authoritative physics + heavy runtime state (crises, loose objects, NPCs) makes robust migration expensive.
  • Migration risks outweigh MVP benefit for short-session co-op where host reliability is acceptable.
  • Team priority is content density + stable normal sessions.

MVP Behavior

  • If host disconnects: run terminates gracefully, partial results summarized if possible.
  • Show clear messaging: "Host disconnected — flight aborted."
  • Option to return party to Terminal and re-host quickly.

Post-MVP Migration Design (Planned)

  1. Preselect standby host candidate by ping/stability score.
  2. Host periodically serializes migration snapshot (low rate, e.g., every 10s).
  3. On host loss, clients elect standby and rehydrate snapshot.
  4. Resume run with migration marker and optional score penalty.

Deferred Exit Criteria (for future milestone)

  • Snapshot serialization <= 200KB for typical run.
  • Resume time <= 15 seconds.
  • No duplicated crisis instances after resume.

36.5 Anti-Cheat + Trust Boundaries (Host-Authoritative Co-op)

Threat model assumes friendly co-op, but basic abuse prevention required.

Trust Boundaries

  • Trusted: host simulation results (within session), server-authored crisis/damage states.
  • Semi-trusted: client input intents (must be validated).
  • Untrusted: client-reported positions, repair completion, item ownership mutations.

Required Validation Rules

  1. Interaction requests validated for range, LOS, state, and cooldown.
  2. Repair progress computed only on host; clients can never submit progress amount.
  3. Throw force clamped host-side regardless of client camera vector.
  4. Tool ownership and charges validated host-side each tick of interaction.
  5. Movement anti-teleport check with max distance delta per command.
  6. Rate-limit all ServerRpc(RequireOwnership=false) endpoints.

Abuse Handling

  • Soft violations: request dropped + warning log.
  • Repeated violations threshold: auto-kick (host option).
  • Telemetry flag for suspicious behavior for later analysis.

Not in MVP

  • Kernel anti-cheat, encrypted replay signing, authoritative dedicated servers.

36.6 Observability and Runtime Diagnostics

  • On-screen debug overlay toggles:
    • net RTT/jitter/loss
    • active crises
    • panic index
    • subsystem health
    • sim tick drift.
  • Structured logs with tags: NET, CRISIS, PLANE, NPC, SAVE, VOICE.
  • Crash bundle includes last 256 gameplay events and last 10 errors.

37) Concrete C# Skeletons (Major Runtime Classes)

These are implementation skeletons, not drop-in production code.

37.1 NetworkSessionManager.cs

using System;
using System.Collections;
using Unity.Netcode;
using UnityEngine;

public enum SessionState
{
    Offline,
    SteamInitializing,
    LobbyHosting,
    LobbyJoining,
    TransportConnecting,
    Connected,
    InFlight,
    Disconnecting,
    Error
}

public enum DisconnectReasonCode
{
    None,
    TransportTimeout,
    HostEnded,
    SteamAuthFailed,
    SceneSyncFailed,
    VersionMismatch,
    Kicked,
    Unknown
}

public sealed class NetworkSessionManager : MonoBehaviour
{
    [Header("Timeouts")]
    [SerializeField] private float connectTimeoutSec = 20f;
    [SerializeField] private float sceneSyncTimeoutSec = 30f;

    public SessionState CurrentState { get; private set; } = SessionState.Offline;
    public string CurrentRunId { get; private set; } = string.Empty;
    public ulong CurrentLobbyId { get; private set; }

    public event Action<SessionState, SessionState>? OnStateChanged;
    public event Action<DisconnectReasonCode>? OnDisconnected;

    public bool IsHost => NetworkManager.Singleton != null && NetworkManager.Singleton.IsHost;
    public bool IsClient => NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient;

    public bool TryTransition(SessionState next, string reason)
    {
        if (!IsLegalTransition(CurrentState, next))
        {
            Debug.LogWarning($"[NET] Illegal transition {CurrentState} -> {next}. reason={reason}");
            return false;
        }

        var prev = CurrentState;
        CurrentState = next;
        Debug.Log($"[NET] Session transition {prev} -> {next}. reason={reason}");
        OnStateChanged?.Invoke(prev, next);
        return true;
    }

    public void BeginHostFlow()
    {
        if (!TryTransition(SessionState.SteamInitializing, "HostFlowStart")) return;
        StartCoroutine(CoHostFlow());
    }

    public void BeginJoinFlow(ulong lobbyId)
    {
        if (!TryTransition(SessionState.SteamInitializing, "JoinFlowStart")) return;
        CurrentLobbyId = lobbyId;
        StartCoroutine(CoJoinFlow(lobbyId));
    }

    public void ShutdownSession(DisconnectReasonCode reason)
    {
        if (!TryTransition(SessionState.Disconnecting, reason.ToString())) return;

        if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsClient || NetworkManager.Singleton.IsServer))
            NetworkManager.Singleton.Shutdown();

        OnDisconnected?.Invoke(reason);
        TryTransition(SessionState.Offline, "ShutdownComplete");
    }

    public void SetRunId(string runId)
    {
        CurrentRunId = runId;
    }

    private IEnumerator CoHostFlow()
    {
        // TODO: Steam init + lobby create + NGO host start
        yield return null;

        TryTransition(SessionState.Connected, "HostConnected");
    }

    private IEnumerator CoJoinFlow(ulong lobbyId)
    {
        // TODO: Steam join + NGO client start
        var timeout = Time.time + connectTimeoutSec;
        while (Time.time < timeout)
        {
            if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsConnectedClient)
            {
                TryTransition(SessionState.Connected, "JoinConnected");
                yield break;
            }
            yield return null;
        }

        FailSession(DisconnectReasonCode.TransportTimeout, "Join timeout");
    }

    private void FailSession(DisconnectReasonCode code, string msg)
    {
        Debug.LogError($"[NET] Session failure code={code} msg={msg}");
        TryTransition(SessionState.Error, msg);
        OnDisconnected?.Invoke(code);
    }

    private static bool IsLegalTransition(SessionState current, SessionState next)
    {
        if (current == next) return true;
        return (current, next) switch
        {
            (SessionState.Offline, SessionState.SteamInitializing) => true,
            (SessionState.SteamInitializing, SessionState.LobbyHosting) => true,
            (SessionState.SteamInitializing, SessionState.LobbyJoining) => true,
            (SessionState.LobbyHosting, SessionState.TransportConnecting) => true,
            (SessionState.LobbyJoining, SessionState.TransportConnecting) => true,
            (SessionState.TransportConnecting, SessionState.Connected) => true,
            (SessionState.Connected, SessionState.InFlight) => true,
            (_, SessionState.Disconnecting) => true,
            (SessionState.Disconnecting, SessionState.Offline) => true,
            (_, SessionState.Error) => true,
            _ => false
        };
    }
}

37.2 FlightOrchestrator.cs

using System;
using Unity.Netcode;
using UnityEngine;

public enum FlightPhase
{
    Boarding,
    Takeoff,
    Cruise,
    CrisisPeak,
    Approach,
    Landing,
    Ended
}

public sealed class FlightOrchestrator : NetworkBehaviour
{
    [SerializeField] private PlaneController planeController = default!;
    [SerializeField] private CrisisManager crisisManager = default!;
    [SerializeField] private NPCPassengerManager passengerManager = default!;
    [SerializeField] private LandingEvaluator landingEvaluator = default!;
    [SerializeField] private FlightRecorder recorder = default!;

    public NetworkVariable<FlightPhase> Phase =
        new(FlightPhase.Boarding, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    public NetworkVariable<float> FlightTimeSec =
        new(0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    private bool _landingLocked;

    public override void OnNetworkSpawn()
    {
        if (IsServer)
        {
            InitializeHostRun();
        }
    }

    private void FixedUpdate()
    {
        if (!IsServer) return;

        FlightTimeSec.Value += Time.fixedDeltaTime;

        planeController.TickSimulation(Time.fixedDeltaTime, Phase.Value);
        crisisManager.TickSimulation(Time.fixedDeltaTime, Phase.Value);
        passengerManager.TickSimulation(Time.fixedDeltaTime, crisisManager.GlobalPanicIndex);

        EvaluatePhaseTransitions();
    }

    [ServerRpc(RequireOwnership = false)]
    public void RequestEndFlightServerRpc(ServerRpcParams rpcParams = default)
    {
        if (Phase.Value == FlightPhase.Ended) return;
        ForceEnterLanding("ManualEndRequest", rpcParams.Receive.SenderClientId);
    }

    [Server]
    private void InitializeHostRun()
    {
        recorder.BeginRun();
        crisisManager.InitializeRun();
        passengerManager.InitializeRun();
        Phase.Value = FlightPhase.Boarding;
    }

    [Server]
    private void EvaluatePhaseTransitions()
    {
        // TODO: Replace thresholds with FlightPresetSO curves
        if (FlightTimeSec.Value > 20f && Phase.Value == FlightPhase.Boarding) Phase.Value = FlightPhase.Takeoff;
        if (FlightTimeSec.Value > 60f && Phase.Value == FlightPhase.Takeoff) Phase.Value = FlightPhase.Cruise;

        if (crisisManager.GlobalPanicIndex > 0.75f && Phase.Value == FlightPhase.Cruise)
            Phase.Value = FlightPhase.CrisisPeak;

        if (FlightTimeSec.Value > 300f && Phase.Value is FlightPhase.Cruise or FlightPhase.CrisisPeak)
            Phase.Value = FlightPhase.Approach;

        if (FlightTimeSec.Value > 360f && Phase.Value == FlightPhase.Approach)
            ForceEnterLanding("TimedApproach", OwnerClientId);
    }

    [Server]
    private void ForceEnterLanding(string reason, ulong actorClientId)
    {
        if (_landingLocked) return;
        _landingLocked = true;

        Phase.Value = FlightPhase.Landing;

        var result = landingEvaluator.Evaluate(planeController, crisisManager, passengerManager, FlightTimeSec.Value);
        recorder.LockAndFinalize(result, reason, actorClientId);

        PublishLandingResultClientRpc(result.ToNetDto(), reason);
        Phase.Value = FlightPhase.Ended;
    }

    [ClientRpc]
    private void PublishLandingResultClientRpc(LandingResultNetDto dto, string reason)
    {
        // TODO: UI summary routing
    }
}

37.3 PlaneController.cs

using Unity.Netcode;
using UnityEngine;

public sealed class PlaneController : NetworkBehaviour
{
    [Header("Simulation")]
    [SerializeField] private PlaneFlightModel flightModel = default!;
    [SerializeField] private float snapshotSendRateHz = 20f;

    [Header("Runtime State")]
    public NetworkVariable<PlaneStateNet> NetState =
        new(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    public PlaneSubsystemCollection Subsystems { get; } = new();

    private float _snapshotTimer;
    private PilotInputState _pilotInput;
    private TurbulenceImpulse _pendingImpulse;

    public override void OnNetworkSpawn()
    {
        if (IsServer)
        {
            InitializeSubsystems();
        }
    }

    [Server]
    public void TickSimulation(float dt, FlightPhase phase)
    {
        var modifiers = Subsystems.BuildHandlingModifiers();
        var simInput = new PlaneSimInput(_pilotInput, _pendingImpulse, modifiers, phase);

        flightModel.Step(dt, simInput);
        _pendingImpulse = default;

        _snapshotTimer += dt;
        if (_snapshotTimer >= (1f / snapshotSendRateHz))
        {
            _snapshotTimer = 0f;
            NetState.Value = PlaneStateNet.FromModel(flightModel, Subsystems);
        }
    }

    [ServerRpc(RequireOwnership = false)]
    public void SubmitPilotInputServerRpc(PilotInputNetDto dto, ServerRpcParams rpcParams = default)
    {
        if (!ValidatePilotInput(dto, rpcParams.Receive.SenderClientId)) return;
        _pilotInput = dto.ToRuntime();
    }

    [Server]
    public void ApplyTurbulenceImpulse(TurbulenceImpulse impulse)
    {
        _pendingImpulse = _pendingImpulse.Combine(impulse);
    }

    [Server]
    public void ApplySubsystemDamage(PlaneSubsystemType type, float amount, string source)
    {
        Subsystems.ApplyDamage(type, amount, source);
    }

    private void InitializeSubsystems()
    {
        Subsystems.InitializeDefaults();
    }

    private bool ValidatePilotInput(PilotInputNetDto dto, ulong sender)
    {
        if (dto.Tick <= 0) return false;
        if (Mathf.Abs(dto.Pitch) > 1.01f || Mathf.Abs(dto.Roll) > 1.01f) return false;
        return true;
    }
}

37.4 CrisisManager.cs

using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

public sealed class CrisisManager : NetworkBehaviour
{
    [SerializeField] private FlightPresetSO preset = default!;
    [SerializeField] private CrisisDefinitionSO[] crisisCatalog = Array.Empty<CrisisDefinitionSO>();

    public readonly NetworkList<CrisisNetState> ActiveCrisisStates = new();

    public float GlobalPanicIndex { get; private set; }

    private readonly Dictionary<int, CrisisRuntimeInstance> _instances = new();
    private int _nextInstanceId = 1;
    private float _spawnAccumulator;

    [Server]
    public void InitializeRun()
    {
        GlobalPanicIndex = 0f;
        _instances.Clear();
        ActiveCrisisStates.Clear();
        _spawnAccumulator = 0f;
    }

    [Server]
    public void TickSimulation(float dt, FlightPhase phase)
    {
        _spawnAccumulator += dt;

        if (_spawnAccumulator >= 1f)
        {
            _spawnAccumulator = 0f;
            TrySpawnByBudget(phase);
        }

        TickInstances(dt);
        RebuildNetState();
        RecomputeGlobalPanic();
    }

    [Server]
    public bool TryBeginRepair(int instanceId, ulong playerId)
    {
        if (!_instances.TryGetValue(instanceId, out var inst)) return false;
        return inst.TryBeginRepair(playerId);
    }

    [Server]
    public void TickRepair(int instanceId, ulong playerId, float dt)
    {
        if (!_instances.TryGetValue(instanceId, out var inst)) return;

        inst.TickRepair(playerId, dt);
        if (inst.IsResolved)
        {
            OnCrisisResolved(inst);
        }
    }

    [Server]
    public void CancelRepair(int instanceId, ulong playerId, RepairCancelReason reason)
    {
        if (_instances.TryGetValue(instanceId, out var inst))
            inst.CancelRepair(playerId, reason);
    }

    [Server]
    private void TrySpawnByBudget(FlightPhase phase)
    {
        int targetConcurrent = ComputeTargetConcurrency(phase);
        if (_instances.Count >= targetConcurrent) return;

        var candidate = SelectWeightedCandidate(phase);
        if (candidate == null) return;

        SpawnInstance(candidate);
    }

    [Server]
    private void SpawnInstance(CrisisDefinitionSO def)
    {
        var id = _nextInstanceId++;
        var zone = SelectZoneFor(def);

        var inst = new CrisisRuntimeInstance(id, def, zone, NetworkManager.Singleton.ServerTime.TimeAsFloat);
        inst.OnSpawn();
        _instances[id] = inst;

        CrisisSpawnedClientRpc(inst.ToSpawnDto());
    }

    [ClientRpc]
    private void CrisisSpawnedClientRpc(CrisisSpawnDto dto)
    {
        // UI/A/V hooks only on clients
    }

    [Server]
    private void TickInstances(float dt)
    {
        foreach (var kv in _instances)
        {
            kv.Value.Tick(dt);
        }

        // TODO: handle fail/escalate pass in separate iteration
    }

    [Server]
    private void OnCrisisResolved(CrisisRuntimeInstance inst)
    {
        _instances.Remove(inst.InstanceId);
        CrisisResolvedClientRpc(inst.InstanceId);
    }

    [ClientRpc]
    private void CrisisResolvedClientRpc(int instanceId)
    {
        // UI remove state
    }

    [Server]
    private void RebuildNetState()
    {
        ActiveCrisisStates.Clear();
        foreach (var inst in _instances.Values)
            ActiveCrisisStates.Add(inst.ToNetState());
    }

    [Server]
    private void RecomputeGlobalPanic()
    {
        float panic = 0f;
        foreach (var inst in _instances.Values)
            panic += inst.GetPanicContribution();

        GlobalPanicIndex = Mathf.Clamp01(panic / Mathf.Max(1, preset.maxConcurrentCrises));
    }

    private int ComputeTargetConcurrency(FlightPhase phase)
    {
        float t = NetworkManager.Singleton.ServerTime.TimeAsFloat / Mathf.Max(1f, preset.targetDurationSec);
        float curve = preset.crisisDensityCurve.Evaluate(Mathf.Clamp01(t));
        return Mathf.Clamp(Mathf.RoundToInt(curve * preset.maxConcurrentCrises), 1, preset.maxConcurrentCrises);
    }

    private CrisisDefinitionSO? SelectWeightedCandidate(FlightPhase phase)
    {
        // TODO: weighted pick with cooldown/zone constraints
        if (crisisCatalog.Length == 0) return null;
        return crisisCatalog[UnityEngine.Random.Range(0, crisisCatalog.Length)];
    }

    private ZoneId SelectZoneFor(CrisisDefinitionSO def)
    {
        if (def.validZones == null || def.validZones.Length == 0) return ZoneId.Economy;
        return def.validZones[UnityEngine.Random.Range(0, def.validZones.Length)];
    }
}

37.5 PlayerNetworkAvatar.cs

using Unity.Netcode;
using UnityEngine;

public enum PlayerCondition
{
    Normal,
    Dazed,
    Winded,
    Unconscious,
    SuckedOut
}

public sealed class PlayerNetworkAvatar : NetworkBehaviour
{
    [SerializeField] private CharacterController motor = default!;
    [SerializeField] private PlayerInputRouter inputRouter = default!;
    [SerializeField] private PlayerCarryController carryController = default!;
    [SerializeField] private PlayerRagdollController ragdollController = default!;

    public NetworkVariable<PlayerCondition> Condition =
        new(PlayerCondition.Normal, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    public NetworkVariable<bool> IsHoldingOn =
        new(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    public NetworkVariable<ulong> HeldItemNetId =
        new(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    private PlayerInputCommand _latestInput;

    public override void OnNetworkSpawn()
    {
        if (IsOwner)
            inputRouter.EnableInput();

        Condition.OnValueChanged += OnConditionChanged;
    }

    private void Update()
    {
        if (!IsOwner) return;
        var cmd = inputRouter.BuildCommand(Time.frameCount);
        SubmitInputServerRpc(cmd.ToNetDto());
    }

    [ServerRpc]
    private void SubmitInputServerRpc(PlayerInputCommandDto dto, ServerRpcParams rpcParams = default)
    {
        if (!ValidateInput(dto, rpcParams.Receive.SenderClientId)) return;
        _latestInput = dto.ToRuntime();
    }

    [Server]
    public void TickAuthoritative(float dt)
    {
        if (Condition.Value is PlayerCondition.Unconscious or PlayerCondition.SuckedOut)
            return;

        ApplyMovement(_latestInput, dt);
        ProcessConditionTransitions();
    }

    [Server]
    public void ApplyImpact(float impulseMagnitude)
    {
        if (IsHoldingOn.Value) return;

        if (impulseMagnitude > 12f)
        {
            EnterCondition(PlayerCondition.Unconscious);
            ragdollController.ServerEnterRagdoll();
        }
        else if (impulseMagnitude > 7f)
        {
            EnterCondition(PlayerCondition.Dazed);
            ragdollController.ServerEnterRagdoll();
        }
    }

    [Server]
    public bool TrySetHolding(bool active)
    {
        if (Condition.Value != PlayerCondition.Normal) return false;
        IsHoldingOn.Value = active;
        return true;
    }

    [Server]
    private void EnterCondition(PlayerCondition next)
    {
        Condition.Value = next;
    }

    private void OnConditionChanged(PlayerCondition prev, PlayerCondition next)
    {
        // local animation/postfx hooks
    }

    private bool ValidateInput(PlayerInputCommandDto dto, ulong sender)
    {
        if (sender != OwnerClientId) return false;
        if (dto.Sequence <= 0) return false;
        return true;
    }

    private void ApplyMovement(PlayerInputCommand cmd, float dt)
    {
        // TODO: host-side movement resolution
    }

    private void ProcessConditionTransitions()
    {
        // TODO: timer-based recoveries
    }
}

37.6 RepairableInteractable.cs

using Unity.Netcode;
using UnityEngine;

public abstract class RepairableInteractable : InteractableBase
{
    [SerializeField] protected ToolType[] validTools = default!;
    [SerializeField] protected float repairDurationSec = 4f;
    [SerializeField] protected float interruptionPenaltySec = 0.5f;

    public NetworkVariable<float> ProgressNormalized =
        new(0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    public NetworkVariable<ulong> ActiveRepairer =
        new(ulong.MaxValue, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);

    private float _progressSec;

    public override bool CanInteract(PlayerNetworkAvatar player)
    {
        if (!IsServer) return false;
        if (player == null) return false;
        if (ActiveRepairer.Value != ulong.MaxValue && ActiveRepairer.Value != player.OwnerClientId)
            return false;

        return IsToolValid(player);
    }

    public override void BeginInteract(PlayerNetworkAvatar player)
    {
        if (!IsServer) return;
        if (!CanInteract(player)) return;

        ActiveRepairer.Value = player.OwnerClientId;
        OnRepairBegin(player);
    }

    public override void TickInteract(PlayerNetworkAvatar player, float dt)
    {
        if (!IsServer) return;
        if (ActiveRepairer.Value != player.OwnerClientId) return;

        _progressSec += dt;
        ProgressNormalized.Value = Mathf.Clamp01(_progressSec / repairDurationSec);

        if (ProgressNormalized.Value >= 1f)
        {
            CompleteRepair(player);
        }
    }

    public override void EndInteract(PlayerNetworkAvatar player, bool cancelled)
    {
        if (!IsServer) return;
        if (ActiveRepairer.Value != player.OwnerClientId) return;

        if (cancelled)
        {
            _progressSec = Mathf.Max(0f, _progressSec - interruptionPenaltySec);
            ProgressNormalized.Value = Mathf.Clamp01(_progressSec / repairDurationSec);
        }

        ActiveRepairer.Value = ulong.MaxValue;
        OnRepairEnd(player, cancelled);
    }

    [Server]
    protected virtual void CompleteRepair(PlayerNetworkAvatar player)
    {
        ActiveRepairer.Value = ulong.MaxValue;
        ProgressNormalized.Value = 1f;
        OnRepairComplete(player);
    }

    protected abstract bool IsToolValid(PlayerNetworkAvatar player);
    protected abstract void OnRepairBegin(PlayerNetworkAvatar player);
    protected abstract void OnRepairEnd(PlayerNetworkAvatar player, bool cancelled);
    protected abstract void OnRepairComplete(PlayerNetworkAvatar player);
}

37.7 FlightRecorder.cs

using System;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public struct FlightEventRecord
{
    public int Tick;
    public FlightEventType Type;
    public ulong ActorClientId;
    public int A;
    public int B;
    public float F;
}

public sealed class FlightRecorder : MonoBehaviour
{
    [SerializeField] private int ringBufferCapacity = 8192;

    private readonly List<FlightEventRecord> _events = new();
    private readonly List<FlightSnapshotHash> _hashTimeline = new();

    public bool IsLocked { get; private set; }
    public string RunId { get; private set; } = string.Empty;

    public void BeginRun()
    {
        IsLocked = false;
        _events.Clear();
        _hashTimeline.Clear();
        RunId = Guid.NewGuid().ToString("N");
    }

    public void RecordEvent(FlightEventType type, ulong actor, int a = 0, int b = 0, float f = 0f)
    {
        if (IsLocked) return;

        if (_events.Count >= ringBufferCapacity)
            _events.RemoveAt(0);

        _events.Add(new FlightEventRecord
        {
            Tick = Time.frameCount,
            Type = type,
            ActorClientId = actor,
            A = a,
            B = b,
            F = f
        });
    }

    public void RecordHashSample(FlightSnapshotHash hash)
    {
        if (IsLocked) return;
        _hashTimeline.Add(hash);
    }

    public void LockAndFinalize(LandingEvaluationResult result, string reason, ulong actor)
    {
        if (IsLocked) return;
        IsLocked = true;

        RecordEvent(FlightEventType.LandingFinalized, actor, (int)result.OutcomeBand, 0, result.TotalScore);
        // TODO: Build immutable summary blob + checksum
    }

    public FlightRecorderExport BuildExport()
    {
        return new FlightRecorderExport
        {
            RunId = RunId,
            Events = _events.ToArray(),
            Hashes = _hashTimeline.ToArray()
        };
    }
}

37.8 DeterministicCaptureService.cs

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

public sealed class DeterministicCaptureService
{
    private readonly SHA256 _sha = SHA256.Create();
    private readonly StringBuilder _buffer = new(2048);

    public int RunSeed { get; private set; }
    public string RunId { get; private set; } = string.Empty;

    public void Begin(string runId, int runSeed)
    {
        RunId = runId;
        RunSeed = runSeed;
        _buffer.Clear();
    }

    public string BuildSecondHash(int hostTick, PlaneStateNet plane, CrisisNetState[] crises)
    {
        _buffer.Clear();
        _buffer.Append(RunId).Append('|')
               .Append(hostTick).Append('|')
               .Append(plane.PositionXQ).Append('|')
               .Append(plane.RollQ).Append('|')
               .Append(crises.Length);

        foreach (var c in crises)
            _buffer.Append('|').Append(c.InstanceId).Append(':').Append((int)c.Severity).Append(':').Append(c.Zone);

        var bytes = Encoding.UTF8.GetBytes(_buffer.ToString());
        var hash = _sha.ComputeHash(bytes);
        return Convert.ToHexString(hash);
    }

    public void WriteReproArtifact(string path, FlightRecorderExport export, string[] secondHashes)
    {
        var payload = JsonUtility.ToJson(new TurbReproPayload
        {
            RunId = RunId,
            RunSeed = RunSeed,
            Export = export,
            SecondHashes = secondHashes
        }, true);

        File.WriteAllText(path, payload, Encoding.UTF8);
    }

    [Serializable]
    private struct TurbReproPayload
    {
        public string RunId;
        public int RunSeed;
        public FlightRecorderExport Export;
        public string[] SecondHashes;
    }
}

38) Sequence Diagrams (Markdown Text)

38.1 Join Flow (Invite -> Lobby -> Flight Scene)

sequenceDiagram
    participant Host
    participant Steam
    participant Client
    participant Net as NetworkSessionManager
    participant NGO
    participant Scene as SceneFlowService

    Host->>Steam: CreateLobby(maxPlayers, metadata)
    Steam-->>Host: LobbyCreated(lobbyId)
    Host->>NGO: StartHost(transportConfig)
    Host->>Steam: SetRichPresence(InTerminal)

    Client->>Steam: AcceptInvite(lobbyId)
    Steam-->>Client: LobbyJoinSuccess
    Client->>Net: BeginJoinFlow(lobbyId)
    Net->>NGO: StartClient(connectPayload)
    NGO-->>Host: ClientConnected(clientId)
    Host->>Client: SendRunId + Version + FlightPreset

    Host->>Scene: LoadNetworkScene(Flight_X)
    Scene-->>NGO: SceneEventProgress
    NGO-->>Client: SceneSynchronized

    Host->>Client: Spawn PlayerNetworkAvatar
    Client-->>Host: ReadyAck
    Host->>All: BeginBoardingPhase

38.2 Interact / Repair Flow

sequenceDiagram
    participant P as PlayerClient
    participant H as Host
    participant I as RepairableInteractable
    participant C as CrisisManager
    participant UI as HUD

    P->>H: RequestInteractServerRpc(targetNetId, toolId)
    H->>I: Validate(CanInteract, distance, tool)
    alt valid
        H->>I: BeginInteract(player)
        H->>UI: Replicate Progress(0%)
        loop while holding + valid
            P->>H: InteractHoldTick(seq)
            H->>I: TickInteract(dt)
            I-->>H: ProgressUpdated
            H->>UI: Replicate Progress(n%)
        end
        alt progress reaches 100%
            I->>C: ResolveLinkedCrisis(instanceId)
            C->>H: Update panic + score events
            H->>UI: Show RepairComplete
        else interrupted
            H->>I: EndInteract(cancelled=true, reason)
            H->>UI: Show Interrupted
        end
    else invalid
        H->>P: RejectInteraction(reason)
    end

38.3 Crisis Spawn -> Cascade -> Resolve

sequenceDiagram
    participant FM as FlightOrchestrator
    participant CM as CrisisManager
    participant DM as PlaneDamageModel
    participant NPC as PassengerPanicSystem
    participant HUD

    FM->>CM: TickSimulation(dt, phase)
    CM->>CM: EvaluateSpawnBudget()
    CM->>CM: Spawn Crisis A (instanceId=42)
    CM->>HUD: CrisisSpawned(A)
    CM->>DM: ApplySubsystemDamageRate(A)
    CM->>NPC: AddPanic(A.severity)

    loop unresolved timer
        CM->>CM: IncrementEscalationClock
    end

    alt escalation threshold reached
        CM->>CM: Spawn Cascaded Crisis B
        CM->>HUD: CrisisSpawned(B)
        CM->>NPC: PanicSpike
    end

    Note over CM: Player repairs A and B
    CM->>CM: MarkResolved(A)
    CM->>CM: MarkResolved(B)
    CM->>DM: RemoveDamageRates
    CM->>NPC: ReducePanicDecay
    CM->>HUD: CrisisResolved events

38.4 Landing Result + Recorder Lock

sequenceDiagram
    participant FO as FlightOrchestrator(host)
    participant LE as LandingEvaluator
    participant FR as FlightRecorder
    participant Save as ProfileSaveService
    participant Client

    FO->>LE: Evaluate(plane, crises, passengers, finalTouchdown)
    LE-->>FO: LandingEvaluationResult
    FO->>FR: LockAndFinalize(result, reason, actor)
    FR->>FR: Build immutable summary + checksum
    FO->>Client: PublishLandingResultClientRpc(summary)

    Client-->>FO: ResultAck
    FO->>Save: PersistRunSummary(summary)
    Save-->>FO: SaveOk
    FO->>Client: OpenFlightRecorderScreen

39) Full Delivery Schedule (Weeks 1–16)

Daily checkpoints are explicit for Weeks 1–4. Weeks 5–16 are weekly milestone plans with deliverables and exit gates.

Week 1 (Foundation + Multiplayer Skeleton) — Daily Checkpoints

Day 1 (Mon)

  • Lock Unity/editor/toolchain versions.
  • Create Boot, Terminal, Flight graybox scenes.
  • Add package dependencies and verify deterministic import on teammate machine.
  • Checkpoint: clean launch to Boot + Terminal.

Day 2 (Tue)

  • Implement GameBootstrap, ServiceRegistry, minimal config asset pipeline.
  • Add logging conventions and FlightRunId generation utility.
  • Checkpoint: services initialize in deterministic order.

Day 3 (Wed)

  • Integrate Facepunch Steam init stubs + local fallback mode.
  • Implement NetworkSessionManager state machine skeleton.
  • Checkpoint: host/client process can transition Offline->Connected in local loopback.

Day 4 (Thu)

  • Add NGO networked scene load flow.
  • Spawn PlayerNetworkAvatar with owner identification UI labels.
  • Checkpoint: two instances load flight scene and see each other.

Day 5 (Fri)

  • Add CI next build gate + basic Unity script compile check doc.
  • Smoke test: 10 start/join/disconnect cycles.
  • Checkpoint: no unrecoverable stuck session states.

Week 2 (Player + Interaction + Carry) — Daily Checkpoints

Day 1 (Mon)

  • Implement input router and owner-only input capture.
  • Add host-authoritative movement skeleton.
  • Checkpoint: movement + look replicate.

Day 2 (Tue)

  • Build interact raycast + prompt MVP.
  • Add InteractableBase runtime registry.
  • Checkpoint: prompt appears for interactables.

Day 3 (Wed)

  • Implement grabbable pickup/drop host validation.
  • Add carry anchor + joint tuning profile v1.
  • Checkpoint: one item can be carried by one player with replication.

Day 4 (Thu)

  • Implement throw pipeline and anti-exploit clamps.
  • Add carry ownership release edge handling.
  • Checkpoint: thrown item behavior deterministic enough under 100ms RTT.

Day 5 (Fri)

  • Add first integration tests for interaction/carry.
  • Team playtest focused on “funny physics density”.
  • Checkpoint: ship/no-ship review for current carry feel.

Week 3 (Plane + Turbulence Core) — Daily Checkpoints

Day 1 (Mon)

  • Implement PlaneFlightModel step function + pilot input ingestion.
  • Hook cockpit control input to host.
  • Checkpoint: host can steer plane orientation.

Day 2 (Tue)

  • Build interior tilt root setup and physics sanity pass.
  • Add loose object spawn set for test.
  • Checkpoint: tilt causes expected sliding.

Day 3 (Wed)

  • Implement turbulence profile assets + scheduler skeleton.
  • Add impulse distribution to plane and loose objects.
  • Checkpoint: light/medium/severe profiles visibly distinct.

Day 4 (Thu)

  • Integrate knockdown threshold checks and hold-on modifier.
  • Add minimal camera shake + alarm cues.
  • Checkpoint: severe turbulence causes controlled chaos.

Day 5 (Fri)

  • Playtest stress run with 4 players, 30 objects.
  • Capture first perf/bandwidth baselines.
  • Checkpoint: retain >=50 FPS on min target in stress slice.

Week 4 (Crisis + Repair Loop v1) — Daily Checkpoints

Day 1 (Mon)

  • Implement CrisisDefinitionSO authoring workflow.
  • Add CrisisManager spawn budget + runtime instance tracking.
  • Checkpoint: crises spawn in valid zones.

Day 2 (Tue)

  • Implement repairable interactables and tool validation.
  • Connect repair completion to crisis resolution.
  • Checkpoint: one crisis end-to-end repairable.

Day 3 (Wed)

  • Add escalation timers + cascade spawn logic.
  • Add global panic index output.
  • Checkpoint: unresolved crisis can trigger cascade once.

Day 4 (Thu)

  • Integrate crisis effects with plane damage model.
  • Add HUD crisis panel + warning cues.
  • Checkpoint: player sees actionable crisis info.

Day 5 (Fri)

  • Vertical slice review: boarding -> turbulence -> crisis repair -> landing placeholder.
  • Bug scrub + risk review.
  • Checkpoint: go/no-go decision for content scaling.

Week 5

  • Ragdoll/unconscious full pass, assist revive interactions, anti-loop guards.
  • Exit gate: RAG-001..003 pass, no unrecoverable ragdoll lockouts.

Week 6

  • Voice integration (Dissonance), zone attenuation matrix, intercom repair link.
  • Exit gate: cross-zone voice intelligibility accepted by team playtest.

Week 7

  • NPC passengers v1, seat logic, panic propagation, scoring hooks.
  • Exit gate: passengers influence gameplay and score in measurable way.

Week 8

  • Landing evaluator and recorder summary v1, post-flight UI.
  • Exit gate: deterministic scoreboard identical across clients.

Week 9

  • Content expansion pass: additional crises, tool variants, zone-specific events.
  • Exit gate: minimum 10 distinct crises with valid repair loops.

Week 10

  • Plane variant pass (Budget Liner polish, Puddle Jumper balance).
  • Exit gate: both variants playable with unique handling curves.

Week 11

  • Red Eye variant foundation + long-flight pacing adjustments.
  • Exit gate: pacing curve validated in 20+ minute sessions.

Week 12

  • Audio/VFX production pass + optimization budget lock.
  • Exit gate: CPU/GPU/network targets met in representative runs.

Week 13

  • Progression/achievements/cloud save stabilization, migration tests.
  • Exit gate: SAV matrix green on clean and dirty data paths.

Week 14

  • Balance and tuning freeze candidates; telemetry-driven parameter pass.
  • Exit gate: win/fail distribution in target range during playtests.

Week 15

  • Release candidate prep, marketing capture build, final bug triage.
  • Exit gate: RC0 cut, severity-1 bug count = 0.

Week 16

  • Launch readiness, rollback drills, day-0 patch prep.
  • Exit gate: production checklist (Section 42) complete and signed.

40) Expanded QA Matrix (IDs, Steps, Pass/Fail Criteria)

40.1 Legend

  • Priority: P0 (ship blocker), P1 (high), P2 (medium)
  • Type: Functional, Network, Performance, Soak, Regression

40.2 Test Cases

IDPriorityTypeScenarioSteps (condensed)Pass CriteriaFail Criteria
NET-001P0NetworkHost create/join baselineHost creates lobby, 3 clients join, load flightAll 4 spawn within timeout; no errorsAny client fails to spawn or hard-locks
NET-002P0NetworkMid-load client timeoutSimulate one client scene stallStalled client removed, others continueWhole session deadlocks
NET-003P0NetworkHost disconnect handlingKill host process mid-flightClients return to Terminal with clear reasonClients stuck in broken scene
NET-004P1NetworkVersion mismatch rejectJoin with mismatched build metadataClient rejected gracefully with messageClient crashes or partial join
NET-005P1NetworkPlane snapshot drift200ms RTT simulation for 10 minAvg drift under limits from 35.4Visible persistent divergence
NET-006P1NetworkInput staleness dropInject delayed command packetsStale commands ignored, no rubber-band loopsState corruption from stale inputs
PLY-001P0FunctionalState exclusivityForce knockdown during interactionInvalid combinations never persistPlayer in contradictory states
PLY-002P1FunctionalHold-on mitigationCompare knockdowns with/without holdHold-on reduces knockdown rate per tuning targetNo meaningful mitigation
PLY-003P1RegressionRecovery timingTrigger daze/unconscious repeatedlyRecovery times match tuning ±10%Recovery inconsistent or blocked
PLY-004P1FunctionalTurbulence impact propagationSevere impulses across 4 playersCondition outcomes correspond to thresholdsRandom/unbounded outcomes
INT-001P0FunctionalRepair validationAttempt repair w/o required toolHost rejects with clear reasonRepair proceeds incorrectly
INT-002P0NetworkConcurrent interaction contentionTwo players interact same targetOne active repairer onlyDual-progress corruption
INT-003P1RegressionInterrupted repair cleanupCancel via impulse/out-of-rangeProgress and session unlock cleanlySoft-locked interactable
INT-005P1FunctionalRagdoll carry dropCarry item, force ragdollItem dropped safely, no orphan ownershipItem duplicated/lost ownership
PHY-004P1PerformanceThrow stress100 throws over 2 minNo catastrophic jitter/explosionsFrequent NaN or sim blowups
PLN-001P0FunctionalFlight control channelPilot inputs pitch/roll/throttlePlane responds and replicates correctlyControl ignored/desynced
PLN-002P1FunctionalEngine failure handlingDrop engine health below fail thresholdRoll bias + climb penalty appliedNo handling change
PLN-003P1RegressionSubsystem repair recoveryRepair failed subsystemPenalty removed within one tickPenalty persists incorrectly
TRB-001P0FunctionalProfile differentiationRun light/medium/severe sessionsDistinct impulse stats per profileProfiles feel identical
TRB-002P1RegressionImpulse spam guardForce high panic/weatherMax-clump constraints honoredBack-to-back unfair spikes
TRB-003P1NetworkA/V sync under turbulenceMulti-client severe eventAudio/visual cues aligned <150msNoticeable cue desync
RAG-001P0FunctionalRagdoll recover pathTrigger ragdoll 20x100% recover without stuck stateAny permanent ragdoll lock
RAG-002P1RegressionRecovery collision safetyRecover near walls/seatsNo clipping/embedding on recoverPlayer stuck inside geometry
RAG-003P1NetworkObserver coherenceObserve remote recoveriesRemote states transition correctlyObserver sees invalid transitions
CRS-001P0FunctionalCrisis spawn validitySpawn over full flightAll crises in valid zonesInvalid zone crisis appears
CRS-002P0FunctionalCascade boundednessForce unresolved crisesCascades occur, no infinite recursionUnbounded spawn loop
CRS-003P1RegressionResolve propagationResolve crisis with dependenciesPanic/damage/UI update same tickPartial updates or stale states
CRS-004P1FunctionalRepair interruption cause trackingInterrupt repairs via all causesCorrect cancel reason emittedWrong/no reason logging
CRS-005P1SoakLong-run stability45 min run with high densityNo crisis manager exceptionsRuntime crashes/memory leaks
CRS-006P1NetworkCondition & crisis couplingUnconscious during repairRepair pauses/cancels correctlyProgress continues incorrectly
CRS-007P2FunctionalPanic couplingResolve major crisesNPC panic decays per designPanic stays pegged high
DMG-001P0FunctionalThreshold eventsSweep subsystem health 100->0->100Warn/Critical/Fail/Recover fire once each crossingDuplicate or missing threshold events
DMG-002P1RegressionIrrecoverable fail pathsTrigger designated irrecoverable systemBehavior matches design, logged clearlySystem accidentally repairable
NPC-001P1FunctionalSeat return behaviorDisturb passengers then stabilizeNPCs return to seatsPerpetual wandering
NPC-002P1FunctionalPanic spread radiusTrigger local panic clusterSpread follows radius/LOS constraintsWhole cabin instantly panics
NPC-003P2NetworkNPC LOD cadenceObserve distant zone NPCsLower send rate no severe jitterTeleporting/erratic movement
NPC-004P1IntegrationCrisis->NPC couplingSpawn severe crisis in economyPanic increase measurable and boundedNo panic reaction
VOC-001P0FunctionalZone attenuationSpeak while crossing zonesSmooth gain transitionHarsh pops or silence gaps
VOC-002P1FunctionalIntercom outageToggle intercom then electrical failIntercom drops immediately; resumes after repairIntercom stuck state
VOC-003P2RegressionDevice hot-swapChange mic device mid-sessionVoice resumes after reinitPermanent mute without notice
AUD-003P2IntegrationTurbulence mix responseTrigger severe turbulenceAlarm/ambience ducking values respectedMix clipping or no ducking
AUD-004P2IntegrationVoice priorityDense ambience + speakingVoice remains intelligible target SNRVoice buried by ambience
LND-001P0FunctionalDeterministic landing scoreRepeat fixed scenario 5xSame score/grade every runScore drift across runs
LND-002P1IntegrationDamage contributionLand with varying subsystem healthScore penalties map to subsystem statusDamage ignored
LND-003P0NetworkResult replicationEnd run with 4 clientsAll clients show identical final summary hashAny mismatch
LND-004P1IntegrationPassenger outcome weightingSimulate casualties/panic levelsScore shifts per tuning weightsPassenger outcomes ignored
REC-001P0FunctionalRecorder lock immutabilityEmit events post-lock attemptNo new events after lockMutable results after lock
REC-002P1RegressionExport completenessExport .turbrepro buildContains required header/timeline/hash fieldsMissing canonical fields
SAV-001P0FunctionalBasic save/loadComplete run, reboot gameStats/unlocks persist correctlyData loss
SAV-002P1RegressionSchema migrationLoad legacy version saveMigrates without crash/data corruptionBoot failure or broken profile
SAV-003P1IntegrationResult->save handoffComplete landing then autosaveSummary persisted once with checksumDuplicate or missing summary
SAV-004P1FunctionalCorrupt save recoveryTamper with save fileBackup recovery path engagesFatal startup failure
UI-001P1FunctionalPrompt priorityMultiple nearby interactablesHighest-priority actionable prompt shownWrong/no prompt
UI-002P2AccessibilityHigh-scale HUD readabilitySet 140% UI scaleNo clipped critical textClipping or off-screen elements
UI-003P2RegressionHUD fallback modeSimulate HUD presenter exceptionMinimal fallback overlay appearsBlank critical UI
ACC-001P2AccessibilityNon-color status cuesEnable colorblind modeAll critical states represented by icon/textColor-only critical info
OPS-001P0OpsCI reproducible buildBuild from clean clone and tagged commitArtifact hash stable within expected assetsNon-reproducible output
OPS-002P1Performance30-min 4-player soakRun representative matchNo memory leak >10%, no fatal errorsCrash or runaway memory
OPS-003P1Network/OpsSession retry behaviorRepeated host/join cycles x50No progressive degradationReliability degrades over cycles
OPS-004P1OpsSave failure fallbackForce disk full/permission errorGraceful warning + backup attemptHard crash/data loss
OPS-005P0OpsRollback drillExecute rollback plan dry-runPrior build restored and validated in SLARollback exceeds SLA / fails
REL-001P0ReleaseRC checklist sign-offValidate Section 42 checklistAll P0 blockers closed + signaturesUnsigned gates / open blockers

41) Tuning Defaults (Initial Numeric Values)

These are starting defaults for balancing and QA baselines. Update via tuning changelog each sprint.

41.1 Turbulence Defaults

ParameterLightMediumSevere
Event duration (sec)8–1410–1812–22
Impulse interval (sec)1.8–3.01.0–1.80.45–1.1
Plane impulse magnitude (m/s² equivalent)0.8–1.61.8–3.63.8–7.0
Loose object impulse (N)25–6060–140140–320
Knockdown threshold impulse8.07.26.5
Unconscious threshold impulse13.012.010.8
Min gap between severe events (sec)n/an/a18

41.2 Carry / Weight / Throw Defaults

Item ClassMass (kg)Carry ModeMove Speed MultiplierThrow Force Multiplier
Small tool1.2One-hand0.981.15
Medium luggage6.5One-hand0.880.85
Heavy suitcase12.0Two-hand0.740.62
Service cart module22.0Two-hand/drag0.560.30
Passenger assist body (downed)68.0Two-player carry (future)0.450.0

Additional carry tunables:

  • Joint spring: 3200 (small), 4600 (medium), 6200 (heavy)
  • Joint damper: 180, 240, 320
  • Break force: 250, 420, 700

41.3 Repair Defaults

Crisis TypeBase Repair Time (sec)Required ToolInterruption Penalty (sec)Max Concurrent Repairers
Electrical short4.0InsulatedKit0.61
Engine fire suppression6.5Extinguisher0.81
Hydraulic leak7.0WrenchKit1.01
Door seal breach5.5SealantGun0.71
Avionics reboot8.5CircuitPad1.21
Landing gear manual release10.0CrankTool1.41

41.4 NPC Panic Defaults

ParameterDefault
Panic baseline decay per sec0.035
Panic gain: nearby severe turbulence+0.22
Panic gain: explosion/fire in zone+0.35
Panic gain: door breach+0.40
Neighbor propagation radius3.5m
Neighbor propagation per sec (from panicking NPC)+0.06
Panic threshold (Alerted -> Panicking)0.62
Panic threshold (Panicking -> MovingToExit)0.82
Calm threshold (Panicking -> Alerted)0.38
Global panic index clamp[0.0, 1.0]

41.5 Landing Score Weights (Initial)

ComponentWeight
Touchdown quality (vertical/horizontal speed)0.30
Plane subsystem integrity0.25
Crisis resolution rate0.20
Passenger survival + panic outcome0.20
Time efficiency0.05

Outcome bands:

  • >= 85: Clean-ish Landing
  • 70–84: Rough Landing
  • 50–69: Catastrophic Landing
  • < 50: Crash

41.6 Network / Timing Tunables

ParameterDefault
Host simulation fixed tick50 Hz
Player command send rate30 Hz
Plane state snapshot rate20 Hz
Crisis progress update rate10 Hz
Item transform send rate15 Hz (25 Hz active throw window)
Client command max age300 ms
Disconnect timeout10 sec
Scene sync timeout30 sec
Reliable keyframe interval1 sec

42) Production Readiness Checklist + Launch Rollback Plan

42.1 Go/No-Go Checklist (Must Be Signed)

A) Technical Stability

  • All P0 tests in Section 40 passing on RC build.
  • 30-minute 4-player soak passes 3 consecutive runs.
  • No known crash repro with frequency > 1/20 runs.
  • Save migration tests pass from all supported prior versions.

B) Performance Targets

  • Min spec target sustains 60 FPS average in representative run.
  • 1% low FPS >= 45 on min spec.
  • Host CPU frame budget within acceptable range under peak crises.
  • Network bandwidth within budget (Section 36.1) in 4-player stress.

C) Multiplayer Reliability

  • Join flow success rate >= 98% in controlled network conditions.
  • Host disconnect behavior verified and user messaging clear.
  • No reproducible session deadlock after repeated host/join cycles.

D) Content and UX

  • Minimum crisis count met with valid resolve loops.
  • Recorder summary deterministic and consistent across clients.
  • Accessibility minimums (Section 35.13) validated.
  • Voice/intercom functionality verified with fallback path.

E) Operations

  • CI release pipeline green from tagged commit.
  • Symbols/debug artifacts uploaded and retrievable.
  • Telemetry dashboards available for day-0 monitoring.
  • Rollback drill executed successfully in past 7 days.

F) Sign-Off

  • Engineering Lead
  • QA Lead
  • Production
  • Community/Support (launch communication prepared)

42.2 Launch-Day Monitoring Plan (First 24 Hours)

Core dashboards (5-minute refresh):

  • Crash-free session rate
  • Successful match start rate
  • Average session duration
  • Mid-flight disconnect rate
  • Save failure rate
  • Top 5 error codes

Paging thresholds (initial):

  • Crash-free sessions < 95% for 15 minutes
  • Match start failures > 5% for 15 minutes
  • Save failure > 2% for 10 minutes
  • Severe disconnect spikes > 8% for 15 minutes

42.3 Rollback Decision Matrix

TriggerSeverityAction
Startup crash affecting >10% usersCriticalImmediate rollback to previous stable build
Matchmaking/join failures >15% sustainedCriticalRollback unless hotfix ETA < 2h
Save corruption confirmedCriticalDisable cloud sync path + rollback
Isolated subsystem bug w/ workaroundHighHotfix forward if ETA < 6h
Cosmetic/non-blocking defectMediumTrack for next patch, no rollback

42.4 Rollback Execution Runbook

  1. Declare Incident
    • Incident commander (IC) opens incident channel and timestamps start.
  2. Freeze Releases
    • Stop any new deploy/promotions.
  3. Select Rollback Target
    • Most recent green build with signed checklist.
  4. Execute Steam Branch Repoint
    • Repoint public to rollback build depot manifest.
  5. Verify in Production-like Smoke
    • Host/join, complete one flight, verify save path.
  6. Communicate
    • Publish player-facing status note (short + factual).
  7. Monitor
    • Confirm key metrics recover for at least 30 minutes.
  8. Post-Incident Review
    • Root cause, missed signals, prevention actions.

Rollback SLA target: production rollback complete in <= 30 minutes from decision.


43) Final Engineering Handoff Notes

  • This document now serves as the implementation contract for MVP Turbulence:
    • System architecture and trust boundaries
    • Concrete class skeletons and event flows
    • Task-level playbooks with acceptance + integration tests
    • 16-week delivery and production launch/rollback operations
  • During implementation, treat tuning defaults as live and version them with changelog entries by sprint.
  • Any divergence from host-authoritative rules must be explicitly reviewed for exploit and complexity impact.

Primary handoff rule: If a feature is not mapped to playbook tasks + QA IDs + telemetry visibility, it is not ready for production.