112 lines
9.4 KiB
JSON
112 lines
9.4 KiB
JSON
{
|
||
"$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-<id>.mp4 (the comms' space cut is <id>.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-<id>.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"
|
||
}
|
||
}
|
||
}
|