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:
- Playable/funny now over perfect architecture
- Host-authoritative simplicity over prediction complexity
- 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
- Unity Hub → New project
- Template: 3D (URP)
- Name:
Turbulence - Version: Unity 6.3 LTS
- 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)
| System | Authority | Sync Type |
|---|---|---|
| Plane transform & flight state | Host | NetworkVariables + periodic state RPC |
| Physics objects (items, cart, luggage) | Host | NetworkTransform/NetworkRigidbody |
| Player locomotion | Owner → Host validates | NGO Character sync pattern |
| Player state machine | Host authoritative write | NetworkVariables |
| Ragdoll | Host triggers state, client sim local | Event RPC + root sync only |
| Crisis lifecycle | Host only | Event RPC + replicated crisis state |
| NPC passenger logic | Host | Transform + anim params replicated |
| Voice zones | Client-side evaluation + shared config | No 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)ServiceRegistryGameConfigSO
6.2 Networking
NetworkSessionManager(host/client start, disconnect handling)NetworkPrefabCatalogAuthorityDebugOverlay
6.3 Player
PlayerNetworkAvatar : NetworkBehaviourPlayerInputRouterPlayerMotorPlayerStateMachinePlayerInteractionControllerPlayerCarryControllerPlayerRagdollControllerPlayerCameraController
6.4 Plane
PlaneController : NetworkBehaviourPlaneFlightModelPlaneDamageModelPlaneZoneManagerPlaneTurbulenceControllerWingWindVolume
6.5 Crisis
CrisisManager : NetworkBehaviourCrisisRuntimeInstanceCrisisDefinitionSOCrisisSchedulerCrisisCascadeGraph
6.6 Interaction
InteractableBaseRepairableInteractableToolItemGrabbableObject : NetworkBehaviourDoorInteractableSeatInteractable
6.7 NPC
PassengerAgent : NetworkBehaviourPassengerBrainPassengerPanicSystemPassengerSeatSystem
6.8 UI
HUDControllerPromptControllerVoiceIndicatorUIFlightRecorderController
6.9 Audio
AudioDirectorAlarmRouterPlaneMixControllerVoiceZoneBridge
6.10 Steam/Save
SteamPlatformServiceAchievementServiceCloudSaveServiceProfileSaveService
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):
MoveStateConditionState(Normal/Dazed/etc)HeldItemLeftNetIdHeldItemRightNetIdIsRagdolledIsUnconscious
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
OnJointBreakand 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:
PitchResponseCurveRollResponseCurveStabilityDampingCurveStallRiskCurve
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:
instanceIddefinitionRefzonestartTimecurrentSeverityfixProgressisResolved
14.3 Scheduler Algorithm
Every tick window (e.g. 1s host):
- Evaluate target concurrent crises from density curve
- If active < target, spawn weighted from valid pool
- 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:
PlaneZoneVolumetrigger 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
- Init Steam API
- Create Steam lobby
- Set metadata (flight id, max players, privacy)
- Start NGO host
- Publish join code/presence
24.2 Join Flow
- Accept invite or browse friend lobby
- Connect to lobby
- Start NGO client with transport settings
- 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:
- 2–4 players can host/join and complete a flight loop end-to-end
- At least 10 crisis types working with cascades
- Turbulence + ragdoll + hold-on is stable and funny
- One production-quality plane (Budget Liner) + one simpler variant (Puddle Jumper)
- Flight Recorder awards and progression persist
- No major desync bugs in 30-minute 4-player session
- 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)
- Lock Unity version to 6.3 LTS in README + team agreement
- Create Boot + Terminal + Flight graybox scenes
- Implement network session bootstrap (host/join)
- Build Player locomotion + interact raycast
- Build GrabbableObject + carry joint prototype
- Build Plane tilt sandbox + loose object test
- 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:
- Numbered implementation tasks
- Acceptance criteria
- 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
- Implement
NetworkSessionManagerwith explicit states:Offline,SteamInitializing,LobbyHosting,LobbyJoining,TransportConnecting,Connected,InFlight,Disconnecting,Error.
- Add a single transition gate method (
TryTransition) that enforces legal transitions and logs rejected transitions. - Integrate Facepunch lobby creation/join; persist
LobbyIdand lobby metadata snapshot in memory. - Create NGO start wrappers:
StartHostSession()StartClientSession(connectPayload)ShutdownSession(reason)
- Build network scene travel wrapper around NGO scene manager with timeout watchdog (30s default).
- Add disconnect reason taxonomy:
TransportTimeout,Kicked,HostEnded,SteamAuthFailed,SceneSyncFailed,VersionMismatch,Unknown.
- Show deterministic user-facing error dialog per reason and return to Terminal safely.
- 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
FlightRunIdvalue 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
- Split owner-local input collection from host simulation:
- Client: input sampling, camera, animation previsualization.
- Host: authoritative movement/state validation.
- Implement input command struct with monotonic tick and client sequence number.
- Add command buffering and staleness rejection on host (drop commands older than
maxCommandAgeMs). - Implement
PlayerStateMachinetransitions only on host; replicate withNetworkVariable<PlayerCondition>. - Add safe position correction policy:
- hard snap if error > 2.0m
- soft correction if error between 0.25m–2.0m.
- Implement hold-on modifier that reduces knockdown chance and movement speed.
- Add condition recovery timers and assist-based revives.
- 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
- Implement
InteractableBasecontract plus standardized interaction token (InteractionSessionId). - Build interaction request validation:
- in range
- LOS valid
- player condition allows interaction
- required tool present.
- Add server-authoritative progress loop for repair interactables.
- Implement cancellation reasons enum:
PlayerReleased,OutOfRange,ImpulseInterrupted,ToolInvalid,TargetInvalidated.
- Implement
PlayerCarryControllerwithConfigurableJointtuning profiles by weight class. - Add carry ownership handoff logic when item is dropped, thrown, or forcibly detached.
- Implement throw impulses with deterministic random seed from host tick + item net id.
- Add anti-spam cooldown on interact requests (100ms per target).
- 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
- Implement
PlaneFlightModelpure simulation module with deterministic input struct. - Drive model from host FixedUpdate with fixed timestep accumulator.
- Add control channels:
- pitch input
- roll input
- yaw trim
- throttle
- autopilot target.
- Apply subsystem damage modifiers in ordered pipeline:
- pilot/autopilot
- stability damping
- subsystem penalties
- turbulence impulses.
- Replicate compressed plane state snapshot to clients at configured cadence.
- Implement client interpolation/extrapolation for non-owner view.
- Add cockpit interaction binding to mutate host pilot input only.
- 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
- Implement turbulence profile assets (
Light,Medium,Severe,Catastrophicfor debug). - Build host scheduler using flight phase + weather + panic index multipliers.
- Emit impulse events with seeded RNG for reproducibility (
runSeed + hostTick). - Apply impulse channels separately:
- plane global impulse
- loose rigidbody pulse
- camera shake cue
- knockdown check.
- Add min-gap and max-clump constraints to avoid impossible spam.
- Add hold-on immunity window and grace period after recovery.
- Replicate turbulence state summary to clients for synchronized audiovisuals.
- 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
- Build ragdoll rig activation/deactivation utility with strict one-way transitions.
- Host triggers ragdoll state; clients simulate locally.
- Replicate root transform and state timestamps (start tick, recover tick).
- Implement recovery blending pipeline:
- freeze ragdoll root
- align anim root
- blend over
recoverBlendDuration.
- Add unconscious timeout + teammate assist revive interaction.
- Add anti-loop guard (cooldown after recovering before next ragdoll trigger).
- 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
- Implement
CrisisManagerauthoritative state machine with explicit phase gates. - Build weighted crisis selection with duplicate suppression and cooldown enforcement.
- Instantiate
CrisisRuntimeInstancewith immutableinstanceId, start tick, origin cause. - Implement unresolved escalation timers and cascade triggers.
- Enforce max concurrent crisis cap and per-zone cap.
- Add resolution hooks:
- repair success
- auto-resolve (if designed)
- hard-fail.
- Publish crisis events to HUD, audio director, NPC panic, damage model.
- 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
- Implement
PlaneSubsystemStatefor each subsystem with health, thresholds, and flags. - Apply damage from active crises and turbulence spikes.
- Add threshold event dispatch (
Warn,Critical,Failed,Recovered). - Route threshold outcomes into plane handling modifiers + audiovisual effects.
- Implement repair interactions that restore subsystem health with diminishing returns.
- Add irrecoverable fail states for selected severe scenarios.
- 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
- Implement
PassengerAgenthost-only AI tick and replicated movement state. - Build panic score model with event contributions and decay.
- Add neighbor propagation by radius and LOS weighting.
- Add seat assignment/return logic with conflict resolution.
- Integrate panic index into global crisis pacing.
- Add incapacitation from severe turbulence and assist interactions.
- 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
- Configure Dissonance positional channels and room/zone attenuation matrix.
- Implement
VoiceZoneBridgethat maps current zone to attenuation preset. - Build intercom repairable system tied to electrical subsystem health.
- Add intercom mix routing and ducking sidechain rules.
- Add UI indicators for speaking/intercom active/zone name.
- Implement failover when voice service unavailable (text prompt warning).
- 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
- Implement
LandingEvaluatordeterministic score snapshot at touchdown. - Build score component normalization and weighted aggregation.
- Implement
FlightRecorderevent ring buffer + final immutable summary blob. - Add award pipeline (MVP medals) with deterministic tie-break rules.
- Replicate final result packet to clients with hash verification.
- Persist run summary into save profile (recent runs, stats counters).
- 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
- Implement profile serialization with versioned schema.
- Add migration pipeline from old versions to current.
- Save locally first, then mirror to Steam Cloud.
- Implement conflict policy:
- newest timestamp wins for settings
- merge-additive for unlocks/stats
- emit warning if incompatible.
- Add checksum/hash field for corruption detection.
- Implement autosave triggers at safe points (end of run, settings apply, unlock grant).
- 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
- Implement HUD presenter architecture with event-driven state updates.
- Build interaction prompt stack with prioritization and cooldown.
- Add crisis panel compact mode + accessibility expanded mode.
- Add colorblind-safe status icons and non-color indicators.
- Add subtitle support for critical alarms/intercom cues.
- Add UI scalability presets (80%, 100%, 120%, 140%).
- 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
- Define CI build pipeline (lint -> type check -> build -> artifact publish).
- Add content checksum manifest generation per build.
- Add branch promotion workflow (
internal->playtest->public). - Integrate crash symbol upload and basic telemetry endpoint checks.
- Add release candidate checklist gate in CI.
- Implement emergency config override file for live balancing tweaks.
- 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)
| Subsystem | Authority | Tick/Send Cadence | Reliability | Payload Strategy | Notes |
|---|---|---|---|---|---|
| Player input commands | Client -> Host | 30 Hz command stream (delta) | Unreliable Sequenced | Compact bit-packed input + seq | Host drops stale/out-of-order |
| Player authoritative state | Host -> All | 20 Hz snapshot + event deltas | Mixed (snapshot unreliable, state change reliable) | Position/velocity/condition packed | Reliable used only for state transitions |
| Plane flight state | Host -> All | 20 Hz snapshot, 50 Hz host sim | Unreliable Sequenced + periodic reliable keyframe (1 Hz) | Quantized transform, speed, subsystem flags | Keyframe prevents long drift |
| Crisis lifecycle events | Host -> All | Event-driven | Reliable | Spawn/resolve/fail packets + IDs | Must never be dropped |
| Crisis progress bars | Host -> All | 10 Hz | Unreliable | Percent + interacting player id | Loss tolerated, next update corrects |
| Loose item transforms | Host -> Interested clients | 15 Hz default; 25 Hz when recently thrown | Unreliable Sequenced | Priority buckets | Sleep state suppresses packets |
| NPC transforms/state | Host -> All | 10 Hz, LOD to 5 Hz distant | Unreliable Sequenced | Position + panic tier + anim flags | Low priority subsystem |
| Voice state metadata | Client local + Dissonance | 10 Hz internal | Internal | Speaking + channel + zone | Not gameplay-authoritative |
| HUD alerts | Host -> Clients | Event-driven | Reliable | Alert id + severity + TTL | Deterministic queue order |
| Recorder summary | Host -> Clients | End-of-round once | Reliable | Immutable summary blob + hash | Client validates hash |
| Save sync ack | Client local + cloud | On save points | Reliable (service-level) | Version + checksum | Retries 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
- Detect no heartbeat for
disconnectTimeoutMs. - Freeze gameplay input locally and show "Reconnecting..." overlay.
- Attempt soft reconnect for
softReconnectWindowSec(if enabled). - If reconnect fails, transition to Terminal with reason and cached run summary if available.
- Persist local diagnostics bundle (
run-id,last-tick,error-code).
36.2.2 Scene Sync Failure
- Watchdog starts on scene load event.
- 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).
- Remaining players continue session with updated player count difficulty scaling.
36.2.3 Crisis Runtime Corruption Guard
- Validate every spawned crisis against schema and zone validity.
- If invalid runtime detected:
- quarantine crisis instance
- emit critical log + telemetry marker
- reduce target concurrency by one for remainder of run.
- Continue run without hard crash.
36.2.4 Save Write Failure
- On local save failure, retry up to 3 times with exponential backoff.
- If still failing, write emergency temp save and notify user once.
- Queue cloud sync for next successful boot.
36.2.5 Voice Service Failure
- Detect Dissonance init or runtime failure.
- Switch to fallback mode: UI text warnings + ping wheel suggestion.
- 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
RunSeedon 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
- QA reproduces issue and exports
.turbrepro. - Engineer loads repro in internal replay scene.
- Replay runs host simulation from seed + events.
- 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)
- Preselect standby host candidate by ping/stability score.
- Host periodically serializes migration snapshot (low rate, e.g., every 10s).
- On host loss, clients elect standby and rehydrate snapshot.
- 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
- Interaction requests validated for range, LOS, state, and cooldown.
- Repair progress computed only on host; clients can never submit progress amount.
- Throw force clamped host-side regardless of client camera vector.
- Tool ownership and charges validated host-side each tick of interaction.
- Movement anti-teleport check with max distance delta per command.
- 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
FlightRunIdgeneration utility. - Checkpoint: services initialize in deterministic order.
Day 3 (Wed)
- Integrate Facepunch Steam init stubs + local fallback mode.
- Implement
NetworkSessionManagerstate machine skeleton. - Checkpoint: host/client process can transition Offline->Connected in local loopback.
Day 4 (Thu)
- Add NGO networked scene load flow.
- Spawn
PlayerNetworkAvatarwith owner identification UI labels. - Checkpoint: two instances load flight scene and see each other.
Day 5 (Fri)
- Add CI
next buildgate + 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
InteractableBaseruntime 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
PlaneFlightModelstep 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
CrisisDefinitionSOauthoring workflow. - Add
CrisisManagerspawn 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..003pass, 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:
SAVmatrix 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
| ID | Priority | Type | Scenario | Steps (condensed) | Pass Criteria | Fail Criteria |
|---|---|---|---|---|---|---|
| NET-001 | P0 | Network | Host create/join baseline | Host creates lobby, 3 clients join, load flight | All 4 spawn within timeout; no errors | Any client fails to spawn or hard-locks |
| NET-002 | P0 | Network | Mid-load client timeout | Simulate one client scene stall | Stalled client removed, others continue | Whole session deadlocks |
| NET-003 | P0 | Network | Host disconnect handling | Kill host process mid-flight | Clients return to Terminal with clear reason | Clients stuck in broken scene |
| NET-004 | P1 | Network | Version mismatch reject | Join with mismatched build metadata | Client rejected gracefully with message | Client crashes or partial join |
| NET-005 | P1 | Network | Plane snapshot drift | 200ms RTT simulation for 10 min | Avg drift under limits from 35.4 | Visible persistent divergence |
| NET-006 | P1 | Network | Input staleness drop | Inject delayed command packets | Stale commands ignored, no rubber-band loops | State corruption from stale inputs |
| PLY-001 | P0 | Functional | State exclusivity | Force knockdown during interaction | Invalid combinations never persist | Player in contradictory states |
| PLY-002 | P1 | Functional | Hold-on mitigation | Compare knockdowns with/without hold | Hold-on reduces knockdown rate per tuning target | No meaningful mitigation |
| PLY-003 | P1 | Regression | Recovery timing | Trigger daze/unconscious repeatedly | Recovery times match tuning ±10% | Recovery inconsistent or blocked |
| PLY-004 | P1 | Functional | Turbulence impact propagation | Severe impulses across 4 players | Condition outcomes correspond to thresholds | Random/unbounded outcomes |
| INT-001 | P0 | Functional | Repair validation | Attempt repair w/o required tool | Host rejects with clear reason | Repair proceeds incorrectly |
| INT-002 | P0 | Network | Concurrent interaction contention | Two players interact same target | One active repairer only | Dual-progress corruption |
| INT-003 | P1 | Regression | Interrupted repair cleanup | Cancel via impulse/out-of-range | Progress and session unlock cleanly | Soft-locked interactable |
| INT-005 | P1 | Functional | Ragdoll carry drop | Carry item, force ragdoll | Item dropped safely, no orphan ownership | Item duplicated/lost ownership |
| PHY-004 | P1 | Performance | Throw stress | 100 throws over 2 min | No catastrophic jitter/explosions | Frequent NaN or sim blowups |
| PLN-001 | P0 | Functional | Flight control channel | Pilot inputs pitch/roll/throttle | Plane responds and replicates correctly | Control ignored/desynced |
| PLN-002 | P1 | Functional | Engine failure handling | Drop engine health below fail threshold | Roll bias + climb penalty applied | No handling change |
| PLN-003 | P1 | Regression | Subsystem repair recovery | Repair failed subsystem | Penalty removed within one tick | Penalty persists incorrectly |
| TRB-001 | P0 | Functional | Profile differentiation | Run light/medium/severe sessions | Distinct impulse stats per profile | Profiles feel identical |
| TRB-002 | P1 | Regression | Impulse spam guard | Force high panic/weather | Max-clump constraints honored | Back-to-back unfair spikes |
| TRB-003 | P1 | Network | A/V sync under turbulence | Multi-client severe event | Audio/visual cues aligned <150ms | Noticeable cue desync |
| RAG-001 | P0 | Functional | Ragdoll recover path | Trigger ragdoll 20x | 100% recover without stuck state | Any permanent ragdoll lock |
| RAG-002 | P1 | Regression | Recovery collision safety | Recover near walls/seats | No clipping/embedding on recover | Player stuck inside geometry |
| RAG-003 | P1 | Network | Observer coherence | Observe remote recoveries | Remote states transition correctly | Observer sees invalid transitions |
| CRS-001 | P0 | Functional | Crisis spawn validity | Spawn over full flight | All crises in valid zones | Invalid zone crisis appears |
| CRS-002 | P0 | Functional | Cascade boundedness | Force unresolved crises | Cascades occur, no infinite recursion | Unbounded spawn loop |
| CRS-003 | P1 | Regression | Resolve propagation | Resolve crisis with dependencies | Panic/damage/UI update same tick | Partial updates or stale states |
| CRS-004 | P1 | Functional | Repair interruption cause tracking | Interrupt repairs via all causes | Correct cancel reason emitted | Wrong/no reason logging |
| CRS-005 | P1 | Soak | Long-run stability | 45 min run with high density | No crisis manager exceptions | Runtime crashes/memory leaks |
| CRS-006 | P1 | Network | Condition & crisis coupling | Unconscious during repair | Repair pauses/cancels correctly | Progress continues incorrectly |
| CRS-007 | P2 | Functional | Panic coupling | Resolve major crises | NPC panic decays per design | Panic stays pegged high |
| DMG-001 | P0 | Functional | Threshold events | Sweep subsystem health 100->0->100 | Warn/Critical/Fail/Recover fire once each crossing | Duplicate or missing threshold events |
| DMG-002 | P1 | Regression | Irrecoverable fail paths | Trigger designated irrecoverable system | Behavior matches design, logged clearly | System accidentally repairable |
| NPC-001 | P1 | Functional | Seat return behavior | Disturb passengers then stabilize | NPCs return to seats | Perpetual wandering |
| NPC-002 | P1 | Functional | Panic spread radius | Trigger local panic cluster | Spread follows radius/LOS constraints | Whole cabin instantly panics |
| NPC-003 | P2 | Network | NPC LOD cadence | Observe distant zone NPCs | Lower send rate no severe jitter | Teleporting/erratic movement |
| NPC-004 | P1 | Integration | Crisis->NPC coupling | Spawn severe crisis in economy | Panic increase measurable and bounded | No panic reaction |
| VOC-001 | P0 | Functional | Zone attenuation | Speak while crossing zones | Smooth gain transition | Harsh pops or silence gaps |
| VOC-002 | P1 | Functional | Intercom outage | Toggle intercom then electrical fail | Intercom drops immediately; resumes after repair | Intercom stuck state |
| VOC-003 | P2 | Regression | Device hot-swap | Change mic device mid-session | Voice resumes after reinit | Permanent mute without notice |
| AUD-003 | P2 | Integration | Turbulence mix response | Trigger severe turbulence | Alarm/ambience ducking values respected | Mix clipping or no ducking |
| AUD-004 | P2 | Integration | Voice priority | Dense ambience + speaking | Voice remains intelligible target SNR | Voice buried by ambience |
| LND-001 | P0 | Functional | Deterministic landing score | Repeat fixed scenario 5x | Same score/grade every run | Score drift across runs |
| LND-002 | P1 | Integration | Damage contribution | Land with varying subsystem health | Score penalties map to subsystem status | Damage ignored |
| LND-003 | P0 | Network | Result replication | End run with 4 clients | All clients show identical final summary hash | Any mismatch |
| LND-004 | P1 | Integration | Passenger outcome weighting | Simulate casualties/panic levels | Score shifts per tuning weights | Passenger outcomes ignored |
| REC-001 | P0 | Functional | Recorder lock immutability | Emit events post-lock attempt | No new events after lock | Mutable results after lock |
| REC-002 | P1 | Regression | Export completeness | Export .turbrepro build | Contains required header/timeline/hash fields | Missing canonical fields |
| SAV-001 | P0 | Functional | Basic save/load | Complete run, reboot game | Stats/unlocks persist correctly | Data loss |
| SAV-002 | P1 | Regression | Schema migration | Load legacy version save | Migrates without crash/data corruption | Boot failure or broken profile |
| SAV-003 | P1 | Integration | Result->save handoff | Complete landing then autosave | Summary persisted once with checksum | Duplicate or missing summary |
| SAV-004 | P1 | Functional | Corrupt save recovery | Tamper with save file | Backup recovery path engages | Fatal startup failure |
| UI-001 | P1 | Functional | Prompt priority | Multiple nearby interactables | Highest-priority actionable prompt shown | Wrong/no prompt |
| UI-002 | P2 | Accessibility | High-scale HUD readability | Set 140% UI scale | No clipped critical text | Clipping or off-screen elements |
| UI-003 | P2 | Regression | HUD fallback mode | Simulate HUD presenter exception | Minimal fallback overlay appears | Blank critical UI |
| ACC-001 | P2 | Accessibility | Non-color status cues | Enable colorblind mode | All critical states represented by icon/text | Color-only critical info |
| OPS-001 | P0 | Ops | CI reproducible build | Build from clean clone and tagged commit | Artifact hash stable within expected assets | Non-reproducible output |
| OPS-002 | P1 | Performance | 30-min 4-player soak | Run representative match | No memory leak >10%, no fatal errors | Crash or runaway memory |
| OPS-003 | P1 | Network/Ops | Session retry behavior | Repeated host/join cycles x50 | No progressive degradation | Reliability degrades over cycles |
| OPS-004 | P1 | Ops | Save failure fallback | Force disk full/permission error | Graceful warning + backup attempt | Hard crash/data loss |
| OPS-005 | P0 | Ops | Rollback drill | Execute rollback plan dry-run | Prior build restored and validated in SLA | Rollback exceeds SLA / fails |
| REL-001 | P0 | Release | RC checklist sign-off | Validate Section 42 checklist | All P0 blockers closed + signatures | Unsigned 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
| Parameter | Light | Medium | Severe |
|---|---|---|---|
| Event duration (sec) | 8–14 | 10–18 | 12–22 |
| Impulse interval (sec) | 1.8–3.0 | 1.0–1.8 | 0.45–1.1 |
| Plane impulse magnitude (m/s² equivalent) | 0.8–1.6 | 1.8–3.6 | 3.8–7.0 |
| Loose object impulse (N) | 25–60 | 60–140 | 140–320 |
| Knockdown threshold impulse | 8.0 | 7.2 | 6.5 |
| Unconscious threshold impulse | 13.0 | 12.0 | 10.8 |
| Min gap between severe events (sec) | n/a | n/a | 18 |
41.2 Carry / Weight / Throw Defaults
| Item Class | Mass (kg) | Carry Mode | Move Speed Multiplier | Throw Force Multiplier |
|---|---|---|---|---|
| Small tool | 1.2 | One-hand | 0.98 | 1.15 |
| Medium luggage | 6.5 | One-hand | 0.88 | 0.85 |
| Heavy suitcase | 12.0 | Two-hand | 0.74 | 0.62 |
| Service cart module | 22.0 | Two-hand/drag | 0.56 | 0.30 |
| Passenger assist body (downed) | 68.0 | Two-player carry (future) | 0.45 | 0.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 Type | Base Repair Time (sec) | Required Tool | Interruption Penalty (sec) | Max Concurrent Repairers |
|---|---|---|---|---|
| Electrical short | 4.0 | InsulatedKit | 0.6 | 1 |
| Engine fire suppression | 6.5 | Extinguisher | 0.8 | 1 |
| Hydraulic leak | 7.0 | WrenchKit | 1.0 | 1 |
| Door seal breach | 5.5 | SealantGun | 0.7 | 1 |
| Avionics reboot | 8.5 | CircuitPad | 1.2 | 1 |
| Landing gear manual release | 10.0 | CrankTool | 1.4 | 1 |
41.4 NPC Panic Defaults
| Parameter | Default |
|---|---|
| Panic baseline decay per sec | 0.035 |
| Panic gain: nearby severe turbulence | +0.22 |
| Panic gain: explosion/fire in zone | +0.35 |
| Panic gain: door breach | +0.40 |
| Neighbor propagation radius | 3.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)
| Component | Weight |
|---|---|
| Touchdown quality (vertical/horizontal speed) | 0.30 |
| Plane subsystem integrity | 0.25 |
| Crisis resolution rate | 0.20 |
| Passenger survival + panic outcome | 0.20 |
| Time efficiency | 0.05 |
Outcome bands:
>= 85: Clean-ish Landing70–84: Rough Landing50–69: Catastrophic Landing< 50: Crash
41.6 Network / Timing Tunables
| Parameter | Default |
|---|---|
| Host simulation fixed tick | 50 Hz |
| Player command send rate | 30 Hz |
| Plane state snapshot rate | 20 Hz |
| Crisis progress update rate | 10 Hz |
| Item transform send rate | 15 Hz (25 Hz active throw window) |
| Client command max age | 300 ms |
| Disconnect timeout | 10 sec |
| Scene sync timeout | 30 sec |
| Reliable keyframe interval | 1 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
| Trigger | Severity | Action |
|---|---|---|
| Startup crash affecting >10% users | Critical | Immediate rollback to previous stable build |
| Matchmaking/join failures >15% sustained | Critical | Rollback unless hotfix ETA < 2h |
| Save corruption confirmed | Critical | Disable cloud sync path + rollback |
| Isolated subsystem bug w/ workaround | High | Hotfix forward if ETA < 6h |
| Cosmetic/non-blocking defect | Medium | Track for next patch, no rollback |
42.4 Rollback Execution Runbook
- Declare Incident
- Incident commander (IC) opens incident channel and timestamps start.
- Freeze Releases
- Stop any new deploy/promotions.
- Select Rollback Target
- Most recent green build with signed checklist.
- Execute Steam Branch Repoint
- Repoint
publicto rollback build depot manifest.
- Repoint
- Verify in Production-like Smoke
- Host/join, complete one flight, verify save path.
- Communicate
- Publish player-facing status note (short + factual).
- Monitor
- Confirm key metrics recover for at least 30 minutes.
- 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.