orbit/data/comms.json

597 lines
58 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"$comment": "COMMS — the characters of the ship speaking to the player (OAC first). Three comm types: 'ack' pauses the action and holds the box open (muted looping video + text + voice) until the player clicks ACKNOWLEDGE; 'visual' shows the box (video plays once + text + voice) and fades out when the voice ends; 'audio' is voice only — no box, no pause. Comms are EVENTS: each one binds a trigger (when) + a sender (who) + text + a speech clip (what). Trigger vocabulary (live): 'newGame' — the fresh-run tutorial welcome (oac-intro → oac-tether → oac-scan; the hub's FIFO plays each right after the previous is acknowledged); 'firstScan' — oac-upgrade reacts to the player's first SCAN press (GameScene.startScan), 5 s after the click; 'tetherResearch' — oac-minerals → oac-fly-mine react to the player committing to the Tether Level 2 research (GameScene.beginResearch, held by the busy() contract until the console closes); 'firstCluster' — oac-first-cluster celebrates the run's first asteroid-cluster discovery (GameScene.celebrateDiscovery), 1.5 s after the chime so the discovery SFX lands first; 'minerals200' — oac-tether2 confirms the moment the run's lifetime mining first crosses 200 (the 'Mine 200 Minerals' quest line and the tether-l2 build cost; GameScene's ore event); 'tetherL2Launch' — oac-relaunch → oac-explore, the launch-back pair: SurfaceScene's take-off that puts the player in space after the home world's level-2 tether is installed (the run's build record is the witness); the hub's FIFO plays the pair back-to-back; 'firstWorld' — trestle-meetup → trestle-quest: the FIRST world the player discovers — the starter quest's own witness (discoveredWorldCount: any planet or space station, the home world never counts, in any system — the comm and the quest can never drift; GameScene.celebrateDiscovery, plus a scene-create arm for loaded saves that already hold discovered worlds), the hub's FIFO plays the pair back-to-back; 'researchComplete' — oac-research-complete, the AUDIO-ONLY sign-off on EVERY research completion (GameScene._completeResearch; once:false, held by the busy() contract while the research console is still open), and oac-research-new — the 'new research available' beat that ANSWERS the sign-off: armed the moment the sign-off's voice has FINISHED (the hub's onEnd — GameScene._commEnded) and only if a tech is still ready to research that isn't yet (researchAnyAvailable — nothing in flight, a node whose requires are met); the hub's FIFO plays it right behind the sign-off, so the two voices never overlap; 'systemComplete' — oac-system-complete, the system-complete milestone: the moment a system's NAV chart COMPLETES — its last planet/space station/jump-gate discovery (GameScene.celebrateDiscovery), which is exactly when its 'Unlock Jumpgates' research becomes AVAILABLE (the SYSTEM tree's available hook, js/research/SystemCategory.js) — NOT on the research's completion; once per system (once:false re-arms per system; the scene's arming set keeps it a single fire, so later cluster discoveries in a charted system don't re-arm it). 'questNew' — oac-quest-new: the quest-grant voice (GameScene._grantUnlockedQuests — the starter on the first boot, 'Leaving the Nest' on the starter's claim, every future unlock; once:false re-arms per grant); 'trestleBrief' — trestle-brief-1 → trestle-brief-2 → trestle-brief-3: the 'Leaving the Nest' quest's three back-to-back ACK briefs — armed the moment the home system's jumpgates go ONLINE (the activateGates effect in GameScene._applyResearchEffects — the player can now jump out of the nest; also the quest's grant, and scene create for loaded saves whose gates are already online — GameScene._trestleBeats). The hub's FIFO plays the sequence back-to-back (each 'ack' holds until its own ACKNOWLEDGE, so the 1→2→3 order is guaranteed); ALL THREE in the run's comms played-ledger (registry 'commsPlayed' — marked when a comm starts, saved with the run) are the quest's 'Talk to Trestle' witness (GameScene._questCheckEval), and a quit mid-sequence re-arms only the unplayed beats on the next load; 'trestleArrival' — trestle-arrive-1 → trestle-arrive-2: the 'Leaving the Nest' quest's ARRIVAL PAIR on the NEUTRAL CAPITAL WORLD (the destination trestle-brief-3's acknowledgment sends the player to) — the quest completes on the PLANET, not on entering the neighboring system: armed the moment the ship reaches the capital world (GameScene._checkDestinationReached), plus a scene-create arm in GameScene._trestleBeats for a loaded save parked at the capital's rim. The hub's FIFO plays the pair back-to-back (each 'ack' holds until its own ACKNOWLEDGE); BOTH in the run's comms played-ledger (registry 'commsPlayed' — marked when a comm starts, saved with the run) are the quest's 'Meet Trestle' witness (GameScene._questCheckEval). 'deliveredLaunch' — carol-delivered-1 → carol-delivered-2 → carol-delivered-3 → oac-delivered-1: the 'Special Delivery' quest's PAYOFF — armed the moment the player LAUNCHES after the package's home-world sale (GameScene.onSurfaceDeparture: the run's delivery witness, story.delivered, stands when the shop's CONFIRM sells it — data/goods.json → special-package) — Carol's thanks + the trade sign-off, then OAC's confirmation that buy/sell codes are live on every world (the four 'ack' beats chained by the hub's FIFO, each holding until its own ACKNOWLEDGE; the run's comms played-ledger keeps it a one-shot — a later re-arm is a no-op). 'trestleQ4' — trestle-q4-1 → trestle-q4-2 → trestle-q4-3: the 'Pirate Problems' quest's WARNING TRIO — armed the moment the quest GRANTS (GameScene._questStartBeats — the grant gate: 'Special Delivery' claimed AND its deliveredLaunch chain fully ACKed, data/quests.json → pirate_problems.unlocks), the destination already set to the Neutral Capital world; each 'ack' holds until its own ACKNOWLEDGE, so the warning always lands before the jump into the capital's system (the ambush's midpoint); ALL THREE in the run's comms played-ledger (registry 'commsPlayed') are the quest's 'Speak with Trestle' witness (GameScene._questCheckEval). 'trestleQ4Payoff' — trestle-q4-4 → trestle-q4-5: the 'Pirate Problems' quest's PAYOFF — armed the moment the quest becomes COMPLETE + UNCLAIMED (all its checks standing, the reward claimable — GameScene._questRefresh's once-per-quest READY pass, the same moment the QUEST READY toast + chime fire; the last step to stand is the capital's arrival, story.capitalReturned): the job-board unlock (patrols, bounty hunts, escorts) + the level-5 hook (the open game's next door); the two 'ack' beats chained by the hub's FIFO, each holding until its own ACKNOWLEDGE; the run's comms played-ledger keeps the pair a one-shot — a later re-arm is a no-op (a save loaded with the quest complete + unclaimed hears the unplayed beats on that load). 'combatStart' — oac-enemies-detected: the COMBAT-LIVE beat — the moment CombatSystem's active flag flips ON (GameScene._setCombatBar: the wave run's lead, before the first hostile lands, or the ?combat pack's spawn) — OAC's visual contact call (box + voice, no pause — it rides over the live fight); the voice rolls ONE of the three enemies-detected clips at random per arm (audioPool); once:false — re-arms on every combat start; 'combatEnd' — oac-enemies-defeated: the COMBAT-OVER beat — the moment the last hostile is down (the active flag flips OFF, GameScene._setCombatBar), 1 s after the final kill (delayMs 1000 — the last explosion reads first) — OAC's visual sign-off; the voice rolls ONE of the three enemies-defeated clips at random per arm (audioPool); once:false — re-arms on every combat end; 'pirateAttack' — pirate-attack: the PIRATE'S OWN attack line — the moment a comming hostile (data/combat.json → enemyTypes.raider.comms) flies on-screen (a 50% roll per pirate, js/combat/CombatSystem.js) or a new pirate is sent into a rolling wave (a 20% roll per arrival) — the box's clip is the pirate's face (assets/videos/enemies/pirate.mp4) with the voice rolling ONE of the four attack clips at random per arm (audioPool); 'pirateDefeat' — pirate-defeat: the PIRATE'S OWN defeat line — a 20% roll per pirate killed, the box's clip the dying pirate (assets/videos/enemies/pirate-defeat.mp4) with the voice rolling ONE of the four defeat clips at random per arm (audioPool); 'pirateBountyAttack' — pirate-bounty-attack: the BOUNTY HUNT's MARK's attack line — GameScene._fireBountyEngagements arms it the moment the wanted leader stands up its fight (the dossier's frame at ×2, data/sidequests.json → bounty) — the box's clip the bounty clip (assets/videos/enemies/pirate-bounty.mp4) with the voice rolling ONE of the four bounty clips at random per arm (audioPool). All three once:false — re-arming is the hub's in-flight guard's job (one comm in the queue at a time, never a barrage). 'neutralScan' — neutral-scan: the NEUTRAL PILOT's SCAN line — the moment a pilot flies ON SCREEN while the encounter is neutral-only (at least one escort in, zero hostiles anywhere — js/combat/CombatSystem.js, once per encounter) — the box's clip is the pilot (assets/videos/allies/neutral-pilot.mp4) with the voice rolling ONE of the three scan clips at random per arm (audioPool); 'neutralScanComplete' — neutral-scan-complete: the PILOT's SCAN-COMPLETE line — the moment a pilot BREAKS OFF (escort window lapsed, peeling off screen) in a neutral-only encounter (js/combat/CombatSystem.js, once per encounter, on the first pilot to break) — the same face with the voice rolling ONE of the three scan-complete clips at random per arm (audioPool). Both once:false (the encounter-level marks in CombatSystem keep them to one per encounter; the hub's in-flight guard keeps one in the queue at a time). 'escortDeny' — oac-escort-wait: the ESCORT contract's DENIAL — armed the moment the player is TURNED AWAY by the escort's guards: a JUMP through a gate while the convoy is linked/setting down (GameScene.jumpThroughGate's escort hold — the convoy + gate not all in view, or the convoy on its touchdown), or a LANDING on the contract's destination world while the transport is still out (GameScene.startLanding's escort door — the world holds until the transport has REACHED it, phase 'delivered', or has been DESTROYED, phase 'failed', the landing being the failure's reset remedy). OAC's VISUAL box + the escort-wait voice (assets/speech/oac/escort-wait.mp3), no pause — the denial rides over the scene's own flat-denial toast (the panel is already closed — the box IS the ack). once:false — re-arms on every denial (the hub's in-flight guard + idempotent trigger keep a barrage of denied presses to a single voice). 'escortLink' — neutral-transport-link: the TRANSPORT CAPTAIN's LINK greeting — the moment the convoy LINKS at the contract's first waypoint (the ship reaches the rendezvous radius and the convoy stands up on the wing — GameScene.updateEscort, phase 'rendezvous' → 'transit', the escortLinkToast + 'construct' SFX beat) — the box's clip is the transport (assets/videos/allies/neutral-transport.mp4) with the voice rolling ONE of the three transport clips at random per arm (audioPool); no pause — the greeting rides over the link. once:false (the phase transition is a one-shot per contract — the hub's in-flight guard keeps the queue clean). 'escortDelivered' — neutral-transport-delivery: the TRANSPORT CAPTAIN's DELIVERY thanks — the moment the convoy BREAKS OFF to land beside the destination world (the delivery witness stands — destination + convoy on screen, the sky clear — GameScene.updateEscort, phase 'transit' → 'delivering', the escortArrivingToast beat; the convoy flies in and fades out, the seal lands on the fade's close) — the same face with the voice rolling ONE of the three transport-success clips at random per arm (audioPool); no pause — the thanks rides over the touchdown. once:false (the phase transition is a one-shot per contract — the hub's in-flight guard keeps the queue clean). Reserved for future comms: questComplete, discovery, landing, jump, buildComplete. Assets are loaded ON DEMAND by the hub (js/comms/CommsHub.js) when a comm is due — never in a scene's preload, so the initial load stays lean. A comm's `audioPool` (a list of speech files) speaks one clip at RANDOM per arm — the pick is made when the deferred-load spec is built (js/comms/CommsModel.js → commAssets), so only the chosen clip is fetched.",
"enabled": true,
"audio": {
"$comment": "The comm's voice plays at its OWN volume (volume) — the manager's master is left untouched (in v4 `sound.setVolume` IS the master, so ducking it would quiet the voice too). While the voice talks, the other playing sounds (the game music) are ducked PER-SOUND by musicDuck (a fraction of each one's own volume) and restored exactly when the voice ends.",
"volume": 1.0,
"musicDuck": 0.3
},
"characters": {
"$comment": "The senders. label = the box's header; role = the small line beside it; video = the character clip (left panel of the box, ALWAYS played muted — the voice is the speech clip, never the video).",
"oac": {
"label": "O.A.C.",
"role": "ONBOARD A.I. COMPUTER",
"video": "assets/videos/characters/oac.mp4"
},
"trestle": {
"$comment": "Trestle — the out-of-system mentor (the 'A Friend' behind the starter quest, data/quests.json). Same box, same contracts; the clip + speech are his own (the speech key is folder-scoped, so his intro-01.mp3 never collides with OAC's).",
"label": "TRESTLE",
"role": "OUT-OF-SYSTEM MENTOR",
"video": "assets/videos/characters/trestle.mp4"
},
"carol": {
"$comment": "Carol — the neutral capital's courier, the 'Special Delivery' quest's issuer (data/quests.json → special_delivery.issuer). Same box, same contracts; the clip + speech are hers (the speech key is folder-scoped, so her carol-q3-*.mp3 never collide with OAC's). The 'Special Delivery' payoff (trigger 'deliveredLaunch') is the first comm from her: the launch back into space after the package's home-world sale.",
"label": "CAROL",
"role": "COURIER",
"video": "assets/videos/characters/carol.mp4"
},
"pirate": {
"$comment": "The pirate raider (data/combat.json → enemyTypes.raider — the 'Pirate Problems' ambush craft, the 'Pirate Problems' quest's enemy, data/quests.json → pirate_problems). The ATTACK comm's face (trigger 'pirateAttack'): the box's clip is assets/videos/enemies/pirate.mp4 — played muted, once (the voice is the speech clip, never the video). The DEFEAT beat wears its own sender below (a separate dying clip).",
"label": "PIRATE",
"role": "UNLAWFUL HOSTILE RAIDER",
"video": "assets/videos/enemies/pirate.mp4"
},
"pirate_defeat": {
"$comment": "The same raider's DEFEAT beat (trigger 'pirateDefeat') — a SEPARATE sender so the box's clip is the dying pirate (assets/videos/enemies/pirate-defeat.mp4) instead of the attack clip (the comm model ties the video to the sender, so two faces = two senders).",
"label": "PIRATE",
"role": "UNLAWFUL HOSTILE RAIDER — DITCHING",
"video": "assets/videos/enemies/pirate-defeat.mp4"
},
"pirate_bounty": {
"$comment": "The BOUNTY HUNT's mark (data/sidequests.json → bounty — the wanted leader the 'BOUNTY HUNT' contract flies: the dossier's frame buffed ×2, js/quests/SideQuests.js → bountyEngagementScript). The bounty attack comm's face (trigger 'pirateBountyAttack'): the box's clip is assets/videos/enemies/pirate-bounty.mp4 — played muted, once (the voice is the speech clip, never the video). The mark's own name rides the hunt's sighting toast — the comms model is static, the name is the contract's.",
"label": "PIRATE",
"role": "WANTED — BOUNTY ACTIVE",
"video": "assets/videos/enemies/pirate-bounty.mp4"
},
"neutral_pilot": {
"$comment": "The NEUTRAL PILOT (data/combat.json → neutralTypes — the escort roster, js/combat/NeutralShip.js). The scan + scan-complete comms' face (triggers 'neutralScan' / 'neutralScanComplete', js/combat/CombatSystem.js): the box's clip is assets/videos/allies/neutral-pilot.mp4 — played muted, once (the voice is the rolled speech clip, never the video).",
"label": "NEUTRAL PILOT",
"role": "CARGO RUNNER — UNDER SCAN",
"video": "assets/videos/allies/neutral-pilot.mp4"
},
"transport_captain": {
"$comment": "The TRANSPORT CAPTAIN (data/sidequests.json → escort — the contract's transport ship the player escorts: its convoy stands at the rendezvous, links on the wing, and sets down at the destination world). The escort's two ally comms' face (triggers 'escortLink' — the convoy's LINK at the first waypoint — and 'escortDelivered' — the convoy's delivery, the touchdown on the destination world's surface): the box's clip is assets/videos/allies/neutral-transport.mp4 — played muted, once (the voice is the rolled speech clip, never the video).",
"label": "TRANSPORT CAPTAIN",
"role": "CARGO TRANSPORT — UNDER ESCORT",
"video": "assets/videos/allies/neutral-transport.mp4"
}
},
"comms": [
{
"$comment": "The fresh-run welcome: OAC introduces the ship. type 'ack' — the action pauses and the box holds (video loops silently) until ACKNOWLEDGE. delayMs beats the opening toast so the signal is clean.",
"id": "oac-intro",
"from": "oac",
"type": "ack",
"trigger": "newGame",
"text": "So you want to explore the galaxy do you?... Welcome to your ship... she's not much but she's got room to grow. I'm O.A.C. your Onboard A I Computer, and I'm here to help you get around.",
"audio": "assets/speech/oac/intro-01.mp3",
"once": true,
"delayMs": 1400
},
{
"$comment": "Welcome beat two (the tether region). Queued right behind oac-intro on newGame: the hub's FIFO plays it the moment the welcome is acknowledged — each 'ack' holds until its own ACKNOWLEDGE press, so the order is guaranteed.",
"id": "oac-tether",
"from": "oac",
"type": "ack",
"trigger": "newGame",
"text": "First things first. We are tethered to your home planet, this limits how far you can go and what you can explore. We can build more tethers on planets and space stations, but we're going to need to find another planet or space station before we can do that.",
"audio": "assets/speech/oac/intro-02.mp3",
"once": true
},
{
"$comment": "The quest-grant voice — the run's NEW QUEST beat (trigger 'questNew'). once:false: it re-arms on EVERY quest grant — the starter on the first boot, 'Leaving the Nest' (data/quests.json → leaving_the_nest) on the starter's claim, and future unlocks — a fresh voice each time, no box (voice only; the NEW QUEST toast + the dossier already show the quest). Armed by GameScene._grantUnlockedQuests (the grant itself — starter boot + every unlock).",
"id": "oac-quest-new",
"from": "oac",
"type": "audio",
"trigger": "questNew",
"audio": "assets/speech/oac/quest-new.mp3",
"once": false,
"delayMs": 1800
},
{
"$comment": "Welcome beat three (the first instruction): scan the tether region. The player clicks SCAN on the deck after acknowledging this — oac-upgrade (firstScan) is the reaction to that click.",
"id": "oac-scan",
"from": "oac",
"type": "ack",
"trigger": "newGame",
"text": "Let's begin by scanning. Scanning allows us to find other objects inside of our tether region. Lets see what we can scan right now. Click Acknowledge on this comms window, then click Scan on the bottom bar to scan your tether region.",
"audio": "assets/speech/oac/intro-03.mp3",
"once": true
},
{
"$comment": "The scan reaction: fires when the player clicks SCAN on the deck (GameScene.startScan → comms.trigger('firstScan')) and plays 5 s after that click (delayMs) — the sweep is well underway by then; the busy() contract holds it until the scene is free. once per run.",
"id": "oac-upgrade",
"from": "oac",
"type": "ack",
"trigger": "firstScan",
"text": "Good. Now you will see several indicators around your ship that point in the direction of where other objects are within your tether region. Hmm... I don't see any planets or space stations. We're going to need to upgrade our tether. Click Acknowledge, then click research below, and research a Tether Level 2 upgrade.",
"audio": "assets/speech/oac/intro-04.mp3",
"once": true,
"delayMs": 5000
},
{
"$comment": "The Tether Level 2 reaction (trigger 'tetherResearch' — GameScene.beginResearch, the moment the player commits to the tether_l2 node). Queued before oac-fly-mine: each ack holds until its own ACKNOWLEDGE press, so the pair plays back-to-back. The busy() contract holds it until the research console closes (comms never play over a full-screen window).",
"id": "oac-minerals",
"from": "oac",
"type": "ack",
"trigger": "tetherResearch",
"text": "Excellent, that's going to help a lot. We will upgrade the tether on your home planet itself, but we're going to need to mine 200 minerals to afford to do so.",
"audio": "assets/speech/oac/intro-05.mp3",
"once": true
},
{
"$comment": "Chained right after oac-minerals (same trigger, config order): the fly-to / continuous-fly instructions and the go signal to hunt asteroids.",
"id": "oac-fly-mine",
"from": "oac",
"type": "ack",
"trigger": "tetherResearch",
"text": "Let's press scan again to find those nearby objects. Then click in the direction of one of the gray indicators and you'll fly your ship to wherever you click. If you want to continuously fly in a direction, hold shift when you click. Now lets go find some asteroids to mine!",
"audio": "assets/speech/oac/intro-06.mp3",
"once": true
},
{
"$comment": "The first rock field (trigger 'firstCluster' — GameScene.celebrateDiscovery, the run's first asteroid-cluster discovery; the scene flag fires the event once, the comm's once-ledger dedupes across saves). delayMs beats the discovery chime (1.5 s, sfx.discovery) so it lands before OAC speaks.",
"id": "oac-first-cluster",
"from": "oac",
"type": "ack",
"trigger": "firstCluster",
"text": "Good work. You found your first asteroid cluster. Let's mine these until we reach 200 minerals. Just click on whichever asteroid you want to mine and your mining laser will do the rest. Keep mining until you reach 200 minerals or more.",
"audio": "assets/speech/oac/intro-07.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Every research completion (trigger 'researchComplete' — GameScene._completeResearch, the moment any project's clock runs out). AUDIO ONLY: no box, no pause — OAC's short sign-off rides under the action (the voice ducks whatever else is playing, per the audio section). once:false — it re-arms on EVERY completion (the FIFO serializes them). The busy() contract is TYPE-AWARE: the sign-off rides OVER in-progress actions (mining live, the scan sweep out — voice with no box and no pause), but still holds while a MODAL owns the screen (the research console, any window/panel, a jump in flight). delayMs beats the completion chime (1.5 s, sfx.discovery) so it lands before the voice.",
"id": "oac-research-complete",
"from": "oac",
"type": "audio",
"trigger": "researchComplete",
"audio": "assets/speech/oac/research-complete.mp3",
"once": false,
"delayMs": 1500
},
{
"$comment": "The NEW-RESEARCH beat (trigger 'researchNew') — OAC's answer to its own sign-off: the moment oac-research-complete's voice has FINISHED (the hub's onEnd — GameScene._commEnded), if the completed tech OPENED one (or another is already ready) — researchAnyAvailable: nothing in flight, at least one node whose requires are met — this line plays right behind it. AUDIO ONLY: no box, no pause — the voice ducks the music, same as the sign-off. once:false — the played ledger never bars it (every sign-off end re-arms; the FIFO + the in-flight gate keep it honest: a completion that opens nothing stays silent, and a player who already started the next project hears no stale line). delayMs 0: the sign-off's voice is already done — the beat lands immediately (no chime to beat).",
"id": "oac-research-new",
"from": "oac",
"type": "audio",
"trigger": "researchNew",
"audio": "assets/speech/oac/new-research.mp3",
"once": false,
"delayMs": 0
},
{
"$comment": "The system-complete milestone (trigger 'systemComplete' — GameScene.celebrateDiscovery: the moment the system's NAV chart COMPLETES — its last planet / space station / jump-gate discovery — which is exactly when the 'Unlock Jumpgates' research becomes AVAILABLE (the SYSTEM tree's available hook, js/research/SystemCategory.js — the node flips live in the research console); NOT on the research's completion). One fire per system: once:false just lets the NEXT system re-arm it, and the scene's _systemCompleteSeen set keeps the fire to the chart-completing discovery (a later cluster discovery in a charted system still passes isSystemNavComplete() — clusters aren't NAV points). type 'visual': OAC's box READS the milestone line and the voice rides with it, no pause — a passing sign-off, held by the busy() contract while the research console (or any modal/action) is open. delayMs beats the discovery chime (1.5 s, sfx.discovery). audioPool: the voice rolls ONE of the three discovery-complete clips at random per arm (the hub's deferred load fetches only the chosen file).",
"id": "oac-system-complete",
"from": "oac",
"type": "visual",
"trigger": "systemComplete",
"text": "System Discovery Complete. Jumpgate Research Unlocked",
"audioPool": [
"assets/speech/oac/discovery-complete-01.mp3",
"assets/speech/oac/discovery-complete-02.mp3",
"assets/speech/oac/discovery-complete-03.mp3"
],
"once": false,
"delayMs": 1500
},
{
"$comment": "The 200-mineral threshold (trigger 'minerals200' — GameScene's ore event when the run's lifetime mining first crosses 200 — the 'Mine 200 Minerals' quest line and the tether-l2 build cost, data/builds.json). OAC confirms the build can be afforded and sends the player home to build it. The 1.5 s beat lets the HUD/quest beat land before the voice.",
"id": "oac-tether2",
"from": "oac",
"type": "ack",
"trigger": "minerals200",
"text": "Looks like we have enough minerals to afford that level 2 tether. To build it return to your home planet. You can auto pilot to it by clicking it's name in that little green box. When you land click the build menu and build the level 2 tether. Then launch back into space and we'll see if we can find another planet or space station.",
"audio": "assets/speech/oac/intro-08.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "The launch-back beat (trigger 'tetherL2Launch' — SurfaceScene.finishTakeoff, the take-off that puts the player back in space; armed only when the run's build record shows the home world holding a level-2 tether — BuildState.isBuilt, which outlives the surface stay and saves with the run). OAC clears the player to scan again: planets green, stations purple.",
"id": "oac-relaunch",
"from": "oac",
"type": "ack",
"trigger": "tetherL2Launch",
"text": "OK we should be all set. Click Scan again and let's see what we can find. Planets show up as green and space stations show up as purple on our indicator.",
"audio": "assets/speech/oac/intro-09.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Chained right after oac-relaunch is acknowledged (same trigger, config order, delayMs 0 — the FIFO starts it the moment the previous is ACKed). OAC's send-off: the jump-gate primer, then good luck.",
"id": "oac-explore",
"from": "oac",
"type": "ack",
"trigger": "tetherL2Launch",
"text": "This should be everything you need to get around and explore. Once you discover all the objects in a solar system, you can research that systems jump gates and then use them to jump into other solar systems. I'll let you do some exploring for a while on your own now... Good luck.",
"audio": "assets/speech/oac/intro-10.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "Trestle's introduction (trigger 'firstWorld' — GameScene.celebrateDiscovery, the FIRST world the player discovers: the starter quest's own witness, discoveredWorldCount, the 'Discover another planet or space station' check — any planet or space station, the home world never counts, in any system, so the comm and the quest can never drift; the scene flag fires the event once per scene life, the comm's once-ledger dedupes across saves, and a scene-create arm covers loaded saves that already hold discovered worlds). delayMs 0 — it fires AT the moment of discovery: discovery happens 540 px out of the world's edge (data/game.json → discovery.distance), so the ack hold is up long before the player could be in a landing (landing is the comms-panel button — a two-step action), and the box's input gate blocks any landing click for the rest of the hold.",
"id": "trestle-meetup",
"from": "trestle",
"type": "ack",
"trigger": "firstWorld",
"text": "Hey Kid. Not bad.. not bad at all.. You're getting the hang of this quicker than I thought. Listen, I'm just out of system, but I think it's time we meet up. Finish exploring this system so you can unlock the jump gates. I'll ping you once you're ready.",
"audio": "assets/speech/trestle/intro-01.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "Chained right after trestle-meetup is acknowledged (same trigger, config order, delayMs 0 — the FIFO starts it the moment the previous is ACKed). Trestle points at the completed starter quest (data/quests.json → start_your_journey, the 200-mineral reward) and how to claim it.",
"id": "trestle-quest",
"from": "trestle",
"type": "ack",
"trigger": "firstWorld",
"text": "Oh and by the way, you just completed your first quest. If you click on the quest in the upper left, or click the quests button below, you can complete the quest and claim 200 minerals I set aside just for you. See you soon kid!",
"audio": "assets/speech/trestle/intro-02.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "The 'Leaving the Nest' quest's BRIEFING, beat 1 of 3 (trigger 'trestleBrief') — armed the moment the home system's jumpgates go ONLINE (the activateGates effect in GameScene._applyResearchEffects — i.e. the moment the player can first jump OUT of the nest), or already-online when the quest is granted / a save loads (GameScene._trestleBeats). It lands in FRONT of the generic 'researchComplete' sign-off _completeResearch queues after the effects — the hub's FIFO plays this sequence back-to-back (each 'ack' holds until its own ACKNOWLEDGE, so the 1→2→3 order is guaranteed), then OAC's sign-off. The quest's 'Talk to Trestle' witness is the SEQUENCE: all three briefs in the run's comms played-ledger (registry 'commsPlayed' — marked when a comm starts, saved with the run) complete the check (GameScene._questCheckEval); a quit mid-sequence re-arms only the unplayed beats on the next load.",
"id": "trestle-brief-1",
"from": "trestle",
"type": "ack",
"trigger": "trestleBrief",
"text": "Hey Kid. You've discovered all the important objects in the system. That means Planets, Space Stations and Jump gates. As you move around the galaxy, you can always find out how many objects a system has in the map view on your console.",
"audio": "assets/speech/trestle/q2-01.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Briefing beat 2 of 3 (trigger 'trestleBrief') — queued right behind beat 1: the hub's FIFO plays it the moment beat 1 is acknowledged (delayMs 0). He sets up the arc that follows (moving cargo, a friend named Carol who has something to deliver).",
"id": "trestle-brief-2",
"from": "trestle",
"type": "ack",
"trigger": "trestleBrief",
"text": "Listen, I could use your help. Nothing major, but I think it's time you learned how to move cargo. I have a friend here named Carol who has something we need to get delivered pronto.",
"audio": "assets/speech/trestle/q2-02.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "Briefing beat 3 of 3 (trigger 'trestleBrief') — the last of the sequence: the rendezvous (Trestle is in the home-NEIGHBOR system, waiting ON ITS CAPITAL WORLD — the quest's final check's destination) and the way to find it (the orange markers on the compass/map). The 'star cluster's capital' is the NEUTRAL CAPITAL (data/factions.json — the roster's faction pass, js/galaxy/Galaxy.js → _assignFactions): the scene acts on the acknowledgment — the moment the player clicks ACKNOWLEDGE, its destination is set to that system's capital world (the system's outermost planet) — 'sending the coordinates to your computer right now' (js/comms/CommsHub.js → onEnd, js/scenes/GameScene.js → _commEnded → setDestination — the route, the orange compass marker, and the map's route lane all follow from it). With all three heard, the quest's 'Talk to Trestle' check is complete (the played-ledger witness above).",
"id": "trestle-brief-3",
"from": "trestle",
"type": "ack",
"trigger": "trestleBrief",
"text": "I'm in the system right next to you in this star cluster's capital. I'm sending the coordinates to your computer right now. Just follow the orange markers on your compass or map and we'll talk more when you get here.",
"audio": "assets/speech/trestle/q2-03.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "The 'Leaving the Nest' quest's ARRIVAL, beat 1 of 2 (trigger 'trestleArrival') — Trestle's welcome ON the capital world (the neutral capital — the outermost world of the designated system, the destination trestle-brief-3's acknowledgment sets): the quest completes on the PLANET, not on entering the neighboring system — the payoff of his briefing's last line ('we'll talk more when you get here'). Armed the moment the ship reaches the capital world's keep-out rim (GameScene._checkDestinationReached), or by GameScene._trestleBeats on scene create for a loaded save parked there (AFTER the boot comms, so the jump-in landing doesn't lose it to the queue race); the comm's once-ledger + the quest guard keep it a one-shot. Both beats in the run's comms played-ledger (registry 'commsPlayed' — marked when a comm starts, saved with the run) are the quest's 'Meet Trestle' witness (GameScene._questCheckEval).",
"id": "trestle-arrive-1",
"from": "trestle",
"type": "ack",
"trigger": "trestleArrival",
"text": "Welcome Kid, you made it! The capital world of this star system, and it's totally neutral. You're not going to find another capital planet in the galaxy that isn't under faction control... But we'll get to that later.",
"audio": "assets/speech/trestle/q2-04.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Arrival beat 2 of 2 (trigger 'trestleArrival') — queued right behind beat 1: the hub's FIFO plays it the moment beat 1 is acknowledged (delayMs 0). The rendezvous (Trestle + Carol at the bar on the third ringed city) and the 200-credit top-up (the quest's reward, claimed in the console).",
"id": "trestle-arrive-2",
"from": "trestle",
"type": "ack",
"trigger": "trestleArrival",
"text": "In the meantime, head on down here. You'll find me and Carol at the bar on the third ringed city. I'm dropping 200 credits into your account. Let's take a break, and then it's back to work.",
"audio": "assets/speech/trestle/q2-05.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "The 'Special Delivery' quest's PAYOFF, beat 1 of 4 (trigger 'deliveredLaunch') — the launch back into space AFTER the package's home-world sale: GameScene.onSurfaceDeparture arms it when the run's delivery witness (story.delivered) stands — the shop's CONFIRM of the special-package sale set it (data/goods.json → special-package; the sale is the quest's step 3, not this comm). Carol's bar line set it up — 'After you've done that, launch back into space and I'll contact you' (data/vignettes.json → special_delivery_bar_meeting). It lands a beat after the take-off settles (delayMs); the hub's FIFO chains the other three back-to-back, each 'ack' holding until its own ACKNOWLEDGE, and the run's comms played-ledger (registry 'commsPlayed') keeps the whole chain a one-shot — any later re-arm is a no-op.",
"id": "carol-delivered-1",
"from": "carol",
"type": "ack",
"trigger": "deliveredLaunch",
"text": "Thank you. I appreciate you taking care of that for me.",
"audio": "assets/speech/carol/carol-q3-05.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Payoff beat 2 of 4 (trigger 'deliveredLaunch') — the trade sign-off: the shops' trading stood unlocked since the capital's landing (the run's gate — GameScene.isTradeUnlocked — is already open; the line is the story's word on it). Queued behind beat 1: the FIFO starts it the moment beat 1 is acknowledged (delayMs 0).",
"id": "carol-delivered-2",
"from": "carol",
"type": "ack",
"trigger": "deliveredLaunch",
"text": "All shops in the galaxy are now fully unlocked for you. Trade is an important an often lucrative way to make money in the galaxy.",
"audio": "assets/speech/carol/carol-q3-06.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "Payoff beat 3 of 4 (trigger 'deliveredLaunch') — the open-ended game's hint: risk and route/planet-type yield are the play the delivery ends the tutorial for. Queued behind beat 2 (delayMs 0).",
"id": "carol-delivered-3",
"from": "carol",
"type": "ack",
"trigger": "deliveredLaunch",
"text": "The higher the risk, the higher the rewards. Long routes and different planet types have different yields on products. I'll leave it to you to figure out the nuance.",
"audio": "assets/speech/carol/carol-q3-07.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "Payoff beat 4 of 4 (trigger 'deliveredLaunch') — OAC's confirmation on the delivery: the buy/sell codes are live on every world (the trade gate's truth) and credits are the currency from here on (the 200-credit top-up the bar beat set up). Queued behind beat 3 (delayMs 0).",
"id": "oac-delivered-1",
"from": "oac",
"type": "ack",
"trigger": "deliveredLaunch",
"text": "I've confirmed. We have buy and sell codes on all worlds in the galaxy. This is a big deal. We can make real credits now.",
"audio": "assets/speech/oac/oac-q3-01.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "The 'Pirate Problems' quest (data/quests.json → pirate_problems) START beat 1 of 3 (trigger 'trestleQ4') — Trestle's warning, armed the moment the quest GRANTS (GameScene._questStartBeats — the 'Special Delivery' reward claimed AND this deliveredLaunch chain fully ACKed, the quest's unlocks gate; the grant pass runs on whichever completes last — claimQuest or the _commEnded pass), with the player's destination already set to the Neutral Capital world. It lands a beat after the grant settles (delayMs); the hub's FIFO chains beats 2–3 back-to-back, each 'ack' holding until its own ACKNOWLEDGE (the pause contract — the player cannot jump until all three are heard, so the warning always precedes the ambush). The quest's 'Speak with Trestle' witness is the SEQUENCE: all three in the run's comms played-ledger (registry 'commsPlayed', marked when a comm starts) — the same _trestleCheck seam as 'Leaving the Nest' (GameScene._questCheckEval).",
"id": "trestle-q4-1",
"from": "trestle",
"type": "ack",
"trigger": "trestleQ4",
"text": "Listen up kid. That delivery has gotten your ship the attention of some local pirates.",
"audio": "assets/speech/trestle/trestle-q4-01.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Pirate Problems START beat 2 of 3 (trigger 'trestleQ4') — the ship's basic cannon is enough for THIS fight, the upgrades are the horizon (the open-ended game's next hook). Queued behind beat 1: the FIFO starts it the moment beat 1 is acknowledged (delayMs 0).",
"id": "trestle-q4-2",
"from": "trestle",
"type": "ack",
"trigger": "trestleQ4",
"text": "Your ship has a basic cannon on it, which might handle these pirates, but you're going to need upgrades if you're going to travel much deeper in the galaxy.",
"audio": "assets/speech/trestle/trestle-q4-02.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "Pirate Problems START beat 3 of 3 (trigger 'trestleQ4') — the send-back: the bar is the destination (already set by the grant's _questStartBeats) and the warning names the ambush — the pirates live in the capital's system, and the ambush itself (GameScene._maybeStartPirateAmbush → the pirate_ambush wave, data/combat.json) is what 'all bets are off' means. Queued behind beat 2 (delayMs 0).",
"id": "trestle-q4-3",
"from": "trestle",
"type": "ack",
"trigger": "trestleQ4",
"text": "Get back here and meet me in the bar again. As far as I know your system is clear, but once you jump in this system all bets are off. Be on the lookout for pirate fighters.",
"audio": "assets/speech/trestle/trestle-q4-03.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "The 'Pirate Problems' quest's PAYOFF, beat 1 of 2 (trigger 'trestleQ4Payoff') — Trestle's sign-off on the run's pirate arc, armed the moment the quest becomes COMPLETE + UNCLAIMED (all its checks standing — the capital's arrival, story.capitalReturned, the last to land — the reward claimable), from GameScene._questRefresh's once-per-quest READY pass (the same moment the QUEST READY toast + chime fire); it lands a beat after that announcement settles (delayMs); the hub's FIFO chains beat 2 behind it (each 'ack' holding until its own ACKNOWLEDGE), and the run's comms played-ledger (registry 'commsPlayed') keeps the pair a one-shot — a save loaded with the quest complete + unclaimed hears the unplayed beats on that load.",
"id": "trestle-q4-4",
"from": "trestle",
"type": "ack",
"trigger": "trestleQ4Payoff",
"text": "Not bad kid. You made quick work of those pirates. There are many more where those came from. I'm unlocking the job boards for you. From patrols to bounty hunts and escorts, it will get you the exposure you need to become a threat to be reckoned with.",
"audio": "assets/speech/trestle/trestle-q4-04.mp3",
"once": true,
"delayMs": 1500
},
{
"$comment": "Payoff beat 2 of 2 (trigger 'trestleQ4Payoff') — the open horizon: the neutral star systems' job boards (patrols, bounty hunts, escorts) + the cargo play, and the level-5 hook (the run's next door — the ship's class + the factions' turn). Queued behind beat 1: the FIFO starts it the moment beat 1 is acknowledged (delayMs 0).",
"id": "trestle-q4-5",
"from": "trestle",
"type": "ack",
"trigger": "trestleQ4Payoff",
"text": "Take some of these jobs on to help fight the pirate threat in the neutral star systems, and run a few cargo jobs. Earn enough experience to reach level 5 and I'll reach back out to you. We'll pick a class for your ship and talk about the factions controlling the rest of the galaxy then. Good luck!",
"audio": "assets/speech/trestle/trestle-q4-05.mp3",
"once": true,
"delayMs": 0
},
{
"$comment": "The COMBAT-LIVE beat (trigger 'combatStart' — GameScene._setCombatBar, the moment CombatSystem's active flag flips ON: the wave run's lead, BEFORE the first hostile lands, or the ?combat pack's spawn — the same flip the combat bar's rise reads). VISUAL: OAC's box reads the contact call with the voice, no pause — it rides over the live fight (the busy() contract only holds it while a modal owns the screen or an action like mining is out). once:false — re-arms on EVERY combat start (the played-ledger never bars it; the hub's in-flight guard keeps one voice per flip). audioPool: the voice rolls ONE of the three enemies-detected clips at RANDOM per arm (the hub's deferred load fetches only the chosen file — the same contract as oac-system-complete's pool).",
"id": "oac-enemies-detected",
"from": "oac",
"type": "visual",
"trigger": "combatStart",
"text": "Contact. Hostile signatures inbound. Weapons free.",
"audioPool": [
"assets/speech/oac/enemies-detected-01.mp3",
"assets/speech/oac/enemies-detected-02.mp3",
"assets/speech/oac/enemies-detected-03.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The COMBAT-OVER beat (trigger 'combatEnd' — GameScene._setCombatBar, the moment CombatSystem's active flag flips OFF: the LAST hostile down, the run's force spent). VISUAL: OAC's box reads the sign-off with the voice, no pause. delayMs 1000: the beat lands 1 s after the final kill so the last explosion reads first (the same 'beat the SFX' pattern as the discovery-chime delays). once:false — re-arms on EVERY combat end. audioPool: the voice rolls ONE of the three enemies-defeated clips at RANDOM per arm.",
"id": "oac-enemies-defeated",
"from": "oac",
"type": "visual",
"trigger": "combatEnd",
"text": "All hostiles destroyed. The area is clear.",
"audioPool": [
"assets/speech/oac/enemies-defeated-01.mp3",
"assets/speech/oac/enemies-defeated-02.mp3",
"assets/speech/oac/enemies-defeated-03.mp3"
],
"once": false,
"delayMs": 1000
},
{
"$comment": "The pirate's ATTACK line (trigger 'pirateAttack' — js/combat/CombatSystem.js arms it for a comming type (data/combat.json → enemyTypes.raider.comms): the first time a pirate flies ON SCREEN (50% roll) or a NEW pirate is sent into a rolling wave (the opening burst, each kill's replacement — 20% roll; a hit marks the ship so its later on-screen roll doesn't stack, a pirate never double-comms). VISUAL: the box shows the pirate's face (assets/videos/enemies/pirate.mp4 — played once, muted) with the line while the voice rolls ONE of the four pirate attack clips at RANDOM per arm (audioPool — the hub's deferred load fetches only the chosen file); no pause — it rides over the live fight. once:false — re-arms on every roll that hits (the hub's in-flight guard keeps one in the queue at a time, so a burst of rolling pirates never stacks a barrage).",
"id": "pirate-attack",
"from": "pirate",
"type": "visual",
"trigger": "pirateAttack",
"text": "You're in my waters now, and your ship is already mine. Fight on if you like, pretty dart — it'll only be faster that way.",
"audioPool": [
"assets/speech/pirates/attack-01.mp3",
"assets/speech/pirates/attack-02.mp3",
"assets/speech/pirates/attack-03.mp3",
"assets/speech/pirates/attack-04.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The pirate's DEFEAT line (trigger 'pirateDefeat' — js/combat/CombatSystem.js arms it 20% of the time a comming type (data/combat.json → enemyTypes.raider.comms) goes down, on the kill itself). VISUAL: the box shows the dying pirate (assets/videos/enemies/pirate-defeat.mp4 — played once, muted) with the line while the voice rolls ONE of the four pirate defeat clips at RANDOM per arm (audioPool); no pause — it rides over the live fight. once:false — re-arms on every roll that hits (the hub's in-flight guard keeps one in the queue at a time).",
"id": "pirate-defeat",
"from": "pirate_defeat",
"type": "visual",
"trigger": "pirateDefeat",
"text": "Aaargh! You've won this round, pretty dart… but this isn't over. My crew is watching, and they'll be coming for what's left of you.",
"audioPool": [
"assets/speech/pirates/defeat-01.mp3",
"assets/speech/pirates/defeat-02.mp3",
"assets/speech/pirates/defeat-03.mp3",
"assets/speech/pirates/defeat-04.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The pirate BOUNTY's ATTACK line (trigger 'pirateBountyAttack' — GameScene._fireBountyEngagements arms it the moment a BOUNTY HUNT's mark stands up its fight (data/sidequests.json → bounty): the wanted leader — the dossier's frame buffed ×2 (js/quests/SideQuests.js → bountyEngagementScript) — has flown into view. VISUAL: the box shows the bounty clip (assets/videos/enemies/pirate-bounty.mp4 — played once, muted) with the line while the voice rolls ONE of the four pirate bounty clips at RANDOM per arm (audioPool — the hub's deferred load fetches only the chosen file); no pause — it rides over the live fight. once:false — re-arms on every engagement that stands (a jump out mid-fight re-arms it on return; the hub's in-flight guard keeps one in the queue at a time, so a burst of rolling pirates never stacks a barrage).",
"id": "pirate-bounty-attack",
"from": "pirate_bounty",
"type": "visual",
"trigger": "pirateBountyAttack",
"text": "So it's you, the bounty hunter, pretty dart. The Office pays a fortune for my head — but I've got better use for your hull. Come and take me!",
"audioPool": [
"assets/speech/pirates/bounty-01.mp3",
"assets/speech/pirates/bounty-02.mp3",
"assets/speech/pirates/bounty-03.mp3",
"assets/speech/pirates/bounty-04.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The NEUTRAL PILOT's SCAN line (trigger 'neutralScan' — js/combat/CombatSystem.js arms it the moment a pilot flies ON SCREEN while the encounter is neutral-only: at least one escort in, zero hostiles anywhere): the box shows the pilot's face (assets/videos/allies/neutral-pilot.mp4 — played once, muted) with the line while the voice rolls ONE of the three scan clips at RANDOM per arm (audioPool — the hub's deferred load fetches only the chosen file); no pause — it rides over the escort window. once:false — re-arms on every neutral encounter (the encounter-level mark in CombatSystem keeps it to one per encounter; the hub's in-flight guard keeps one in the queue at a time).",
"id": "neutral-scan",
"from": "neutral_pilot",
"type": "visual",
"trigger": "neutralScan",
"text": "Hold your heading, runner. You're in a contraband lane — we're running a full scan of your ship before you go any further. Nothing to worry about if you've got nothing to hide.",
"audioPool": [
"assets/speech/neutral/scan-01.mp3",
"assets/speech/neutral/scan-02.mp3",
"assets/speech/neutral/scan-03.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The NEUTRAL PILOT's SCAN-COMPLETE line (trigger 'neutralScanComplete' — js/combat/CombatSystem.js arms it the moment a pilot BREAKS OFF (escort window lapsed, peeling off screen) in a neutral-only encounter): the box shows the pilot's face (assets/videos/allies/neutral-pilot.mp4 — played once, muted) with the line while the voice rolls ONE of the three scan-complete clips at RANDOM per arm (audioPool); no pause. once:false — re-arms on every neutral encounter (one per encounter, on the first pilot to break; the hub's in-flight guard keeps one in the queue at a time).",
"id": "neutral-scan-complete",
"from": "neutral_pilot",
"type": "visual",
"trigger": "neutralScanComplete",
"text": "That's a clear scan — nothing contraband aboard. You're free to run, runner. Safe travels out there.",
"audioPool": [
"assets/speech/neutral/scan-complete-01.mp3",
"assets/speech/neutral/scan-complete-02.mp3",
"assets/speech/neutral/scan-complete-03.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The ESCORT contract's DENIAL (trigger 'escortDeny' — GameScene's escort guards: jumpThroughGate's hold (the convoy + gate not all in view while the convoy is linked, or the convoy on its touchdown) and startLanding's door (the destination world holds while the transport is still out — phase 'rendezvous'/'transit'/'delivering'; 'failed' never blocks — that landing is the reset's remedy)). VISUAL: OAC's box + the escort-wait voice, no pause — the denial rides over the scene's own flat-denial toast (the comms panel is already closed — the box IS the ack). once:false — re-arms on EVERY denial; the hub's trigger idempotency + in-flight guard keep a barrage of denied presses to a single voice.",
"id": "oac-escort-wait",
"from": "oac",
"type": "visual",
"trigger": "escortDeny",
"text": "Our access is being denied. We need to wait for the transport ship.",
"audio": "assets/speech/oac/escort-wait.mp3",
"once": false,
"delayMs": 0
},
{
"$comment": "The TRANSPORT CAPTAIN's LINK greeting (trigger 'escortLink' — GameScene.updateEscort's rendezvous link: the ship reaches the convoy's standing point at the contract's FIRST WAYPOINT, the convoy stands up on the wing, phase 'rendezvous' → 'transit' — the escortLinkToast + 'construct' SFX beat). VISUAL: the transport's face (assets/videos/allies/neutral-transport.mp4 — played once, muted) with the greeting while the voice rolls ONE of the three transport clips at RANDOM per arm (audioPool — the hub's deferred load fetches only the chosen file); no pause — the greeting rides over the link (the player is in the scene, the convoy just formed). once:false — the phase transition is a one-shot per contract (a saved/reloaded mid-transit escort never re-fires it; the hub's in-flight guard keeps the queue clean).",
"id": "neutral-transport-link",
"from": "transport_captain",
"type": "visual",
"trigger": "escortLink",
"text": "Good to see you. Appreciate you escorting us.",
"audioPool": [
"assets/speech/neutral/transport-01.mp3",
"assets/speech/neutral/transport-02.mp3",
"assets/speech/neutral/transport-03.mp3"
],
"once": false,
"delayMs": 0
},
{
"$comment": "The TRANSPORT CAPTAIN's DELIVERY thanks (trigger 'escortDelivered' — GameScene.updateEscort's delivery: the destination world AND the convoy stand on screen with the sky clear, the convoy breaks off to land beside the destination planet/station and fades out, phase 'transit' → 'delivering' — the escortArrivingToast beat; the seal + payout land on the fade's close, _sealEscortDelivery). VISUAL: the same face (assets/videos/allies/neutral-transport.mp4 — played once, muted) with the thanks while the voice rolls ONE of the three transport-success clips at RANDOM per arm (audioPool — the hub's deferred load fetches only the chosen file); no pause — the thanks rides over the touchdown (the surviving escorts + the transport fly in beside the world). once:false — the phase transition is a one-shot per contract (a saved/reloaded escort never re-fires it; the hub's in-flight guard keeps the queue clean).",
"id": "neutral-transport-delivery",
"from": "transport_captain",
"type": "visual",
"trigger": "escortDelivered",
"text": "We're set down beside the surface, safe and sound. Thank you for the escort — you had our backs the whole lane.",
"audioPool": [
"assets/speech/neutral/transport-success-01.mp3",
"assets/speech/neutral/transport-success-02.mp3",
"assets/speech/neutral/transport-success-03.mp3"
],
"once": false,
"delayMs": 0
}
],
"reservedTriggers": [
"researchNew",
"questComplete",
"discovery",
"landing",
"jump",
"buildComplete"
],
"box": {
"$comment": "The wide comm box, docked just below the mineral bar (its right edge sticks to the bar's right edge; topPad px of padding below the bar). Left: the sender's clip in a bracketed feed panel (muted, contain-fit). Right: the comm text decoding in, and — for 'ack' only — the ACKNOWLEDGE button below it. The height grows to the content; long comms just get taller.",
"width": 560,
"topPad": 12,
"pad": 14,
"headerGap": 8,
"videoSize": 150,
"videoGap": 14,
"text": {
"fontSize": 13,
"lineHeight": 18,
"letterSpacing": 0.4
},
"button": {
"label": "ACKNOWLEDGE",
"gap": 10,
"fontSize": 11,
"paddingX": 16,
"paddingY": 7,
"letterSpacing": 2
},
"pulse": {
"$comment": "The ACKNOWLEDGE button's attention pulse (ack comms only): the button wears the box's amber, an amber halo breathes under it, and two staggered sonar rings expand out — the primary action is unmistakable while the comm holds (the bottom deck is on standby until then).",
"enabled": true,
"color": "#ffd84d",
"periodMs": 1300
},
"animation": {
"inMs": 260,
"outMs": 420,
"visualHoldMs": 350,
"$comment": "visualHoldMs = the beat the box holds after the voice ends (visual comms) before it fades."
},
"colors": {
"panel": "#0a1120",
"panelAlpha": 0.94,
"border": "#22405f",
"borderAlpha": 0.9,
"notch": 10,
"neon": "#00e5ff",
"ink": "#eaf6ff",
"dim": "#7d92c4",
"faint": "#3d4c74",
"amber": "#ffc94d"
},
"combat": {
"$comment": "In-combat 'visual' comms: the box sheds to a video-only plate hugging the screen's right edge (the hub's compact hook — GameScene passes it when combat is live). scale = the clip's size as a fraction of videoSize; margin = px from the screen's right edge.",
"scale": 0.667,
"margin": 16
}
}
}