{ "$comment": "VIGNETTES — dialogue scenes played ON A PLANET (SurfaceScene), triggered by the world's location buttons: the EXPLORE drawer's service points (the neutral capital's Bar) and the SHOP (deck slot). A vignette is a sequence of LINES — each with a speaker (a character), text, and a speech clip — played in a window centered on the screen (js/ui/VignetteWindow.js) OVER the location's looping clip, which keeps playing in the background. NEXT advances the lines; the LAST line's button (DONE) closes the window and returns to the looping clip alone. Vignettes are GATED BY QUEST STEPS: each entry names the quest that holds it + the index of the step it is — it is available while that quest is granted, unclaimed, and the step still open (js/vignettes/VignetteModel.js → vignetteAvailable, the scene builds the gate's facts from the sleeping GameScene). Finishing it is the step's WITNESS (GameScene._questCheckEval's 'vignettePlayed' check reads the run's played ledger — the same shape as the comms' trestleTalked/trestleMet witnesses). A speaker's face is the PLANET cut of their clip — assets/videos/characters/planet-.mp4 (the comms' space cut is .mp4) — and is ALWAYS played muted (the voice is the line's speech clip, never the video). Assets (the character clip + each line's speech) load ON DEMAND by the hub (js/vignettes/VignetteHub.js) when a vignette is armed — never in a scene's preload, so the initial load stays lean. The played ledger marks when the scene COMPLETES (the opposite of comms' mark-on-start): a conversation is a place you can return to — leave mid-scene and it re-arms on the next visit to the location.", "enabled": true, "audio": { "$comment": "A line'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 a voice talks, the other playing sounds (the surface hum) are ducked PER-SOUND by musicDuck (a fraction of each one's own volume) and restored exactly when the voice ends — the comms' recipe (data/comms.json → audio).", "volume": 1.0, "musicDuck": 0.3 }, "characters": { "$comment": "The speakers. label = the window's header; role = the small line beside it. `video` is optional: the speaker's PLANET clip (the window's left panel), defaulting to assets/videos/characters/planet-.mp4 — the planet cut of the comms' space cut (planet-trestle.mp4 vs trestle.mp4, assets/videos/characters/).", "trestle": { "label": "TRESTLE", "role": "OUT-OF-SYSTEM MENTOR" }, "carol": { "label": "CAROL", "role": "COURIER" } }, "vignettes": [ { "id": "special_delivery_bar_meeting", "location": "bar", "quest": "special_delivery", "step": 0, "$comment": "'Special Delivery' step 1 — the neutral capital's bar: Trestle's welcome (trestle-q3-01) + Carol's four beats (carol-q3-01..04). It plays when the quest is held and the bar is entered on the capital. COMPLETING it (the DONE press) runs the scene's story beats (GameScene.onVignetteComplete): the ship's hold is built out FIRST (effects.cargoHold — the run's ship starts with cargoHold 0, data/ship.json) and THEN the 'special-package' joins the hold — Carol's 'I've asked some of my men to build out 4 cargo bays' / 'the cargo I need transported has been loaded' lines are the truth the moment the scene ENDS; and — while the quest is HELD (granted, unclaimed) — the HOME WORLD becomes the destination (the delivery's route — Carol's 'just bring this package back to the merchant on your home world' line is the trigger, and the compass points home for the flight back; the beat is gated on the quest's holding, so once the quest ends a bar visit never re-aims the destination). The completion also marks the step: the quest's check is 'vignettePlayed' (check.id = this id) and reads the run's played ledger (registry 'vignettesPlayed' — VignetteHub marks it on completion, SaveData persists it); the 'cargoUpgraded' step stands done with the package aboard (its witness — check.id 'special-package').", "effects": { "$comment": "The story beat the DONE press runs (GameScene.onVignetteComplete — BEFORE the quest's package handoff, the hold must fit the package): cargoHold = the ship's unlocked cargo bays, MAX-wins (never a downgrade — the run's ship starts at 0, data/ship.json). The scene's lines are the truth: Carol's crew built out the bays.", "cargoHold": 4 }, "lines": [ { "from": "trestle", "text": "Hey Kid. Thanks for meeting us here. Meet Carol, she's part of a team that.. well, let's discuss that later. Right now Carol has a package that needs delivering, and you need training on how to trade cargo.", "audio": "assets/speech/trestle/trestle-q3-01.mp3" }, { "from": "carol", "text": "It's nice to meet you. Trestle speaks highly of you. He thinks you might be able to help me with a delivery I need to make.", "audio": "assets/speech/carol/carol-q3-01.mp3" }, { "from": "carol", "text": "That ship of yours is.. well, lets say there is room for improvement. I hope you don't mind, I've asked some of my men to build out 4 cargo bays inside of it. That should give us what we need to get you transporting.", "audio": "assets/speech/carol/carol-q3-02.mp3" }, { "from": "carol", "text": "I've also seen to it that the cargo I need transported has been loaded. This should be a quick job for you. I can't go back to your home star system, for reasons I can't devuldge, but just bring this package back to the merchant on your home world and sell it to him.", "audio": "assets/speech/carol/carol-q3-03.mp3" }, { "from": "carol", "text": "After you've done that, launch back into space and I'll contact you. Get this job done and there might be a future for you in this galaxy afterall. We'll talk more later.", "audio": "assets/speech/carol/carol-q3-04.mp3" } ] } ], "$vignettes_comment": "The playable scenes — each one: `id` (the played ledger's key + the quest's 'vignettePlayed' witness), `location` (the world slot that triggers it — 'bar' the neutral capital's BAR service, 'shop' the SHOP deck slot; case-insensitive), `quest` + `step` (the gate: the quest it is a step of, and the index of that step in the quest's checks — available while the quest is granted, unclaimed, and the step is not yet done; omit both for an ungated scene), `once` (default true — a scene plays once per run; once:false re-arms on every visit), and `lines` (the dialogue, in order — each line: `from` a speaker id, `text` the words, `audio` the speech clip file (optional — a text-only line is valid)). The first scene ships: the bar's Trestle + Carol meeting (special_delivery_bar_meeting — 'Special Delivery' step 1; assets/speech/trestle/trestle-q3-01.mp3 + assets/speech/carol/carol-q3-01..04.mp3).", "window": { "$comment": "The centered window (js/ui/VignetteWindow.js) — the comms box's centered twin: on the left the speaker's clip in a bracketed feed panel (muted, looping while the line holds — a NO SIGNAL plate if the clip never lands), on the right the line's text decoding in, with the NEXT button below it — the primary action in amber (the same attention pulse as the comms' ACKNOWLEDGE), reading DONE on the last line. videoSize 256 = the planet cut at FULL size (the clip is 256×256 — the face is never downscaled); width 800 leaves a 502px text column (800 − 2·14 pad − 256 feed − 14 gap). The window SIZES TO THE LINE IN PLAY (textHeightFor → _rebuildLayout): a later line taller than the first grows the window — backplate, button, pulse, re-center — so the words never run into the button or the window's edge. The SCRIM dims the world behind the window so it owns the moment — the looping clip behind it keeps moving (the location is the set). startDelayMs = the beat between the location's clip coming up and the window opening: the place reads first, then the scene begins.", "width": 800, "pad": 14, "headerGap": 8, "videoSize": 256, "videoGap": 14, "startDelayMs": 700, "text": { "fontSize": 13, "lineHeight": 18, "letterSpacing": 0.4 }, "button": { "label": "NEXT", "doneLabel": "DONE", "$gap": "the row of space between the line's text and the NEXT/DONE button — one full line-height (18) on top of the old 10: the words and the button read as separate rows, never crowded", "gap": 28, "fontSize": 11, "paddingX": 16, "paddingY": 7, "letterSpacing": 2 }, "pulse": { "$comment": "The NEXT/DONE button's attention pulse: the button wears the window's amber, an amber halo breathes under it, and two staggered sonar rings expand out — the one live control on the surface is unmistakable while the scene holds.", "enabled": true, "color": "#ffd84d", "periodMs": 1300 }, "animation": { "inMs": 260, "outMs": 420 }, "scrim": { "color": "#02040a", "alpha": 0.4 }, "colors": { "panel": "#0a1120", "panelAlpha": 0.94, "border": "#22405f", "borderAlpha": 0.9, "notch": 10, "neon": "#00e5ff", "ink": "#eaf6ff", "dim": "#7d92c4", "faint": "#3d4c74", "amber": "#ffc94d" } } }