Set Special Delivery destination from bar conversation
- Move the home-world destination from the capital departure to the Special Delivery bar vignette completion, matching the story trigger. - Keep the capital departure only for story trail and post-sale comms, so later capital visits no longer override the player's destination. - Show a destination-set console note in SurfaceScene, staggered behind the package-handoff note when it appears.
This commit is contained in:
parent
e5e7f6ce78
commit
da2f1fecb9
|
|
@ -154,7 +154,7 @@
|
||||||
"title": "Special Delivery",
|
"title": "Special Delivery",
|
||||||
"description": "The mail routes are dead, and a sealed package that must reach the home world is sitting at the neutral capital's bar. Carol is paying for the run, and Trestle will see it off — the only question is whether your ship can hold the cargo. Meet the two of them at the bar, upgrade the hold, and deliver the package to the home world.",
|
"description": "The mail routes are dead, and a sealed package that must reach the home world is sitting at the neutral capital's bar. Carol is paying for the run, and Trestle will see it off — the only question is whether your ship can hold the cargo. Meet the two of them at the bar, upgrade the hold, and deliver the package to the home world.",
|
||||||
"issuer": "Carol",
|
"issuer": "Carol",
|
||||||
"_comment": "The run's THIRD quest (the 'Leaving the Nest' follow-up — the bar at the neutral capital). It GRANTS the moment 'Leaving the Nest' is COMPLETED AND CLAIMED (unlocks.claimed — the same chain mechanic as leaving_the_nest). The three checks: step 1 'vignettePlayed' (check.id 'special_delivery_bar_meeting' — the bar's dialogue scene in data/vignettes.json: Trestle + Carol, the run's played ledger registry 'vignettesPlayed' is the witness — VignetteHub marks it when the scene's DONE press completes it; the gate — the quest granted, unclaimed, this step open — is what plays it when the bar is entered). The CAPITAL LANDING runs the story ledger (registry 'story', saved as record.story — GameScene._recordLanding): the trade gate lifts (the shop's Buy/Sell tab — ShopView reads GameScene.isTradeUnlocked; the deck's Shop button itself is always live). The scene's DONE press (GameScene.onVignetteComplete) runs the handoff: FIRST the ship's hold is built out (the scene's effects.cargoHold — the run's ship starts with cargoHold 0, data/ship.json), THEN the SPECIAL PACKAGE — packageGood 'special-package', data/goods.json, sheet frame 114, tradeable:false so no shop floor touches it — lands in the hold (GameScene._handoffSpecialPackage); step 2 'cargoUpgraded' (check.id 'special-package') — the witness is the PACKAGE ABOARD: it only fits once the hold is upgraded, so its presence IS the upgrade's proof (the scene's lines are the truth — Carol's crew built the bays); step 3 'packageDelivered' — the delivery's proof (story.delivered, MONOTONIC): the package SOLD on the HOME WORLD for ZERO CREDITS — the home world is the ONE floor that takes it (TradeGrid's questSell gate — the SELL grid refuses the bay everywhere else, the tooltip reads NOT FOR SALE), priced at the good's flat sellFor (0 cr — Economy.questCargoSellPrice; the shop's Confirm is ShopView.confirm → GameScene.onQuestPackageSold, which records it — the parcel in Carol's hands IS the delivery, landing alone no longer completes the step; the sale is not buyback-eligible). The capital's launch still sets the destination back to the home world (SurfaceScene.finishTakeoff → GameScene.onSurfaceDeparture — the compass points the delivery's route). Reward: 200 credits (claimQuest's credit seam — the same as 'Leaving the Nest').",
|
"_comment": "The run's THIRD quest (the 'Leaving the Nest' follow-up — the bar at the neutral capital). It GRANTS the moment 'Leaving the Nest' is COMPLETED AND CLAIMED (unlocks.claimed — the same chain mechanic as leaving_the_nest). The three checks: step 1 'vignettePlayed' (check.id 'special_delivery_bar_meeting' — the bar's dialogue scene in data/vignettes.json: Trestle + Carol, the run's played ledger registry 'vignettesPlayed' is the witness — VignetteHub marks it when the scene's DONE press completes it; the gate — the quest granted, unclaimed, this step open — is what plays it when the bar is entered). The CAPITAL LANDING runs the story ledger (registry 'story', saved as record.story — GameScene._recordLanding): the trade gate lifts (the shop's Buy/Sell tab — ShopView reads GameScene.isTradeUnlocked; the deck's Shop button itself is always live). The scene's DONE press (GameScene.onVignetteComplete) runs the handoff: FIRST the ship's hold is built out (the scene's effects.cargoHold — the run's ship starts with cargoHold 0, data/ship.json), THEN the SPECIAL PACKAGE — packageGood 'special-package', data/goods.json, sheet frame 114, tradeable:false so no shop floor touches it — lands in the hold (GameScene._handoffSpecialPackage), and — while the quest is HELD (granted, unclaimed) — the HOME WORLD becomes the destination (the delivery's route: the bar conversation is the trigger — Carol's 'just bring this package back to the merchant on your home world' line, and the compass points it for the flight home — GameScene.onVignetteComplete's beat 3, _specialDeliveryHeld re-reading the gate so a scene that outlived the quest can't re-aim the destination); step 2 'cargoUpgraded' (check.id 'special-package') — the witness is the PACKAGE ABOARD: it only fits once the hold is upgraded, so its presence IS the upgrade's proof (the scene's lines are the truth — Carol's crew built the bays); step 3 'packageDelivered' — the delivery's proof (story.delivered, MONOTONIC): the package SOLD on the HOME WORLD for ZERO CREDITS — the home world is the ONE floor that takes it (TradeGrid's questSell gate — the SELL grid refuses the bay everywhere else, the tooltip reads NOT FOR SALE), priced at the good's flat sellFor (0 cr — Economy.questCargoSellPrice; the shop's Confirm is ShopView.confirm → GameScene.onQuestPackageSold, which records it — the parcel in Carol's hands IS the delivery, landing alone no longer completes the step; the sale is not buyback-eligible). The capital's launch (SurfaceScene.finishTakeoff → GameScene.onSurfaceDeparture) records the delivery's trail (launchedFromCapital) and, once the sale stands, arms the payoff chain — it no longer touches the destination, so a post-quest visit to the capital (a 'Pirate Problems' return leg) leaves the player's destination alone. Reward: 200 credits (claimQuest's credit seam — the same as 'Leaving the Nest').",
|
||||||
"unlocks": {
|
"unlocks": {
|
||||||
"_comment": "The grant condition: the listed quests' rewards CLAIMED — the run's chain (leaving_the_nest paid out → this one grants).",
|
"_comment": "The grant condition: the listed quests' rewards CLAIMED — the run's chain (leaving_the_nest paid out → this one grants).",
|
||||||
"claimed": ["leaving_the_nest"]
|
"claimed": ["leaving_the_nest"]
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
"location": "bar",
|
"location": "bar",
|
||||||
"quest": "special_delivery",
|
"quest": "special_delivery",
|
||||||
"step": 0,
|
"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 beat (effects + the quest's package handoff — 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. 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').",
|
"$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": {
|
"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.",
|
"$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
|
"cargoHold": 4
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,50 @@
|
||||||
*
|
*
|
||||||
* node dev/delivery-launch.test.mjs
|
* node dev/delivery-launch.test.mjs
|
||||||
*
|
*
|
||||||
* The launch-after-sale beat (data/comms.json → trigger 'deliveredLaunch'
|
* Two seams, pinned here:
|
||||||
* — carol-delivered-1/2/3 → oac-delivered-1, the four ack comms the
|
*
|
||||||
* player gets when they LAUNCH after selling the special package on the
|
* A — the launch-after-sale beat (data/comms.json → trigger
|
||||||
* home world): the scene-side seam is GameScene.onSurfaceDeparture
|
* 'deliveredLaunch' — carol-delivered-1/2/3 → oac-delivered-1, the four
|
||||||
* (SurfaceScene.finishTakeoff → it), which arms the chain when the run's
|
* ack comms the player gets when they LAUNCH after selling the special
|
||||||
* delivery witness (story.delivered — set by the shop's CONFIRM of the
|
* package on the home world): the scene-side seam is
|
||||||
* package's sale, GameScene.onQuestPackageSold) stands. The comms' own
|
* GameScene.onSurfaceDeparture (SurfaceScene.finishTakeoff → it), which
|
||||||
* once-ledger (js/comms/CommsHub.js) keeps the chain a one-shot, so the
|
* arms the chain when the run's delivery witness (story.delivered — set
|
||||||
* seam may arm on every post-delivery departure — a later re-arm is a
|
* by the shop's CONFIRM of the package's sale, GameScene.onQuestPackageSold)
|
||||||
* no-op. Pinned here:
|
* stands. The comms' own once-ledger (js/comms/CommsHub.js) keeps the
|
||||||
|
* chain a one-shot, so the seam may arm on every post-delivery departure
|
||||||
|
* — a later re-arm is a no-op. The capital departure also records the
|
||||||
|
* story trail (launchedFromCapital) — but NO LONGER sets the
|
||||||
|
* destination (the route moved to the bar conversation — B). Pinned:
|
||||||
* - a post-delivery departure arms 'deliveredLaunch' exactly once
|
* - a post-delivery departure arms 'deliveredLaunch' exactly once
|
||||||
* (from any world — the sale's world is the home world, not the
|
* (from any world — the sale's world is the home world, not the
|
||||||
* capital);
|
* capital);
|
||||||
* - a pre-delivery departure arms nothing;
|
* - a pre-delivery departure arms nothing;
|
||||||
* - the capital leg still stands alongside the new arm (the departure
|
* - the capital departure still records the trail (launchedFromCapital)
|
||||||
* records launchedFromCapital, routes the compass home, and refreshes
|
* and refreshes the quest ledger — and sets NO destination (a
|
||||||
* the quest ledger — the pre-existing behavior this beat rides on);
|
* post-quest visit to the capital — a 'Pirate Problems' return leg —
|
||||||
* - the defensive no-op (already at home) still leaves the arm in place.
|
* leaves the player's destination alone);
|
||||||
|
* - the defensive no-op (already at home) still leaves the arm in
|
||||||
|
* place.
|
||||||
|
*
|
||||||
|
* B — the delivery's ROUTE (the destination → the home world): owned by
|
||||||
|
* the quest's bar conversation (data/vignettes.json →
|
||||||
|
* special_delivery_bar_meeting — the DONE press,
|
||||||
|
* GameScene.onVignetteComplete's beat 3): it lands only while the quest
|
||||||
|
* is HELD (granted, unclaimed — the scene's own gate already keeps it
|
||||||
|
* playing only then, GameScene._specialDeliveryHeld re-reads it as
|
||||||
|
* belt-and-braces). Pinned:
|
||||||
|
* - the bar scene's DONE press routes home (and stands the hold +
|
||||||
|
* handoff beats + the ledger refresh) while the quest is held;
|
||||||
|
* - the same DONE press sets NO destination once the quest is claimed
|
||||||
|
* (or was never granted, or belongs to another quest).
|
||||||
|
*
|
||||||
* (The config side of the same beat — the roster, the chain's order, and
|
* (The config side of the same beat — the roster, the chain's order, and
|
||||||
* the verbatim text — is pinned in dev/comms.test.mjs.)
|
* the verbatim text — is pinned in dev/comms.test.mjs.)
|
||||||
*/
|
*/
|
||||||
// GameScene.onSurfaceDeparture arms 'deliveredLaunch' when story.delivered
|
// GameScene.onSurfaceDeparture arms 'deliveredLaunch' when story.delivered
|
||||||
// stands, keeps the capital leg, and stays quiet otherwise.
|
// stands, records the capital's trail without routing; GameScene.
|
||||||
|
// onVignetteComplete routes the delivery (home) only while the quest is
|
||||||
|
// held.
|
||||||
import { pathToFileURL } from 'node:url';
|
import { pathToFileURL } from 'node:url';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
|
|
@ -89,7 +110,7 @@ function makeScene({ delivered, capital = true }) {
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 — post-delivery departure (any world): the payoff chain arms
|
// A — 1: post-delivery departure (any world): the payoff chain arms
|
||||||
{
|
{
|
||||||
const s = makeScene({ delivered: true });
|
const s = makeScene({ delivered: true });
|
||||||
s.onSurfaceDeparture('SomePlanet');
|
s.onSurfaceDeparture('SomePlanet');
|
||||||
|
|
@ -97,24 +118,25 @@ function makeScene({ delivered, capital = true }) {
|
||||||
check('delivered departure (non-capital) does not claim the capital leg', s.story.launchedFromCapital === false);
|
check('delivered departure (non-capital) does not claim the capital leg', s.story.launchedFromCapital === false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 — not delivered: no arm
|
// A — 2: not delivered: no arm
|
||||||
{
|
{
|
||||||
const s = makeScene({ delivered: false });
|
const s = makeScene({ delivered: false });
|
||||||
s.onSurfaceDeparture('SomePlanet');
|
s.onSurfaceDeparture('SomePlanet');
|
||||||
check('pre-delivery departure arms nothing', s.triggers.length === 0);
|
check('pre-delivery departure arms nothing', s.triggers.length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3 — capital leg still stands (the old behavior, alongside the new arm)
|
// A — 3: capital departure still records the trail — but NO LONGER
|
||||||
|
// routes (the delivery's route moved to the bar conversation — B)
|
||||||
{
|
{
|
||||||
const s = makeScene({ delivered: true });
|
const s = makeScene({ delivered: true });
|
||||||
s.onSurfaceDeparture('CapitalWorld');
|
s.onSurfaceDeparture('CapitalWorld');
|
||||||
check('capital departure still arms deliveredLaunch (once)', JSON.stringify(s.triggers) === JSON.stringify(['deliveredLaunch']));
|
check('capital departure still arms deliveredLaunch (once)', JSON.stringify(s.triggers) === JSON.stringify(['deliveredLaunch']));
|
||||||
check('capital departure still records the leg', s.story.launchedFromCapital === true);
|
check('capital departure still records the trail', s.story.launchedFromCapital === true);
|
||||||
check('capital departure still routes home', JSON.stringify(s.destination) === JSON.stringify({ id: 'sys-home', via: 'home' }));
|
check('capital departure sets no destination (the bar scene owns the route)', s.destination === undefined);
|
||||||
check('capital departure still refreshes the quest ledger', s.questRefreshes === 1);
|
check('capital departure still refreshes the quest ledger', s.questRefreshes === 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4 — already home: the arm still fires (the player left home), the leg is a no-op
|
// A — 4: already home: the arm still fires (the player left home), the leg is a no-op
|
||||||
{
|
{
|
||||||
const s = makeScene({ delivered: true });
|
const s = makeScene({ delivered: true });
|
||||||
s.systemRecord = { id: 'sys-home' };
|
s.systemRecord = { id: 'sys-home' };
|
||||||
|
|
@ -123,5 +145,71 @@ function makeScene({ delivered, capital = true }) {
|
||||||
check('home-departure sets no destination (defensive no-op)', s.destination === undefined);
|
check('home-departure sets no destination (defensive no-op)', s.destination === undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// B — the bar scene's DONE press (GameScene.onVignetteComplete) is what
|
||||||
|
// routes the delivery — and only while the quest is HELD.
|
||||||
|
function makeBarScene({ granted, claimed }) {
|
||||||
|
const s = makeScene({ delivered: false });
|
||||||
|
s.questState = {
|
||||||
|
isGranted: (id) => id === 'special_delivery' && granted,
|
||||||
|
isClaimed: (id) => id === 'special_delivery' && claimed,
|
||||||
|
};
|
||||||
|
const items = [];
|
||||||
|
s.ship = {
|
||||||
|
stats: { cargoHold: 0 },
|
||||||
|
cargo: {
|
||||||
|
items,
|
||||||
|
room: 4,
|
||||||
|
has: (id) => items.includes(id),
|
||||||
|
add: (id) => { items.push(id); return 1; },
|
||||||
|
},
|
||||||
|
setCargoHold: (n) => { s.ship.stats.cargoHold = n; return n; },
|
||||||
|
};
|
||||||
|
s.refreshCreditsHud = () => {};
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
const barVig = {
|
||||||
|
id: 'special_delivery_bar_meeting',
|
||||||
|
location: 'bar',
|
||||||
|
quest: 'special_delivery',
|
||||||
|
step: 0,
|
||||||
|
effects: { cargoHold: 4 },
|
||||||
|
lines: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// B — 1: quest held (granted, unclaimed): the DONE press routes home,
|
||||||
|
// alongside the hold + handoff beats
|
||||||
|
{
|
||||||
|
const s = makeBarScene({ granted: true, claimed: false });
|
||||||
|
const beat = s.onVignetteComplete(barVig);
|
||||||
|
check('bar scene (quest held) stands the hold + handoff beats', beat.upgradedTo === 4 && beat.handoff === true);
|
||||||
|
check('bar scene (quest held) routes home', JSON.stringify(s.destination) === JSON.stringify({ id: 'sys-home', via: 'home' }));
|
||||||
|
check('bar scene (quest held) marks the routed beat', beat.routed === true);
|
||||||
|
check('bar scene (quest held) refreshes the quest ledger', s.questRefreshes === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// B — 2: quest claimed (the scene outlived the quest — belt-and-braces):
|
||||||
|
// the beats still stand, but the destination is left alone
|
||||||
|
{
|
||||||
|
const s = makeBarScene({ granted: true, claimed: true });
|
||||||
|
const beat = s.onVignetteComplete(barVig);
|
||||||
|
check('bar scene (quest claimed) still stands the beats', beat.upgradedTo === 4 && beat.handoff === true);
|
||||||
|
check('bar scene (quest claimed) sets no destination', s.destination === undefined && beat.routed === false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// B — 3: quest not granted (a drift the gate should have caught): same
|
||||||
|
// no-routing answer
|
||||||
|
{
|
||||||
|
const s = makeBarScene({ granted: false, claimed: false });
|
||||||
|
s.onVignetteComplete(barVig);
|
||||||
|
check('bar scene (quest not granted) sets no destination', s.destination === undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// B — 4: a scene for ANOTHER quest never routes the delivery
|
||||||
|
{
|
||||||
|
const s = makeBarScene({ granted: true, claimed: false });
|
||||||
|
s.onVignetteComplete({ ...barVig, id: 'other_quest_bar', quest: 'pirate_problems' });
|
||||||
|
check('a non-delivery scene sets no destination', s.destination === undefined);
|
||||||
|
}
|
||||||
|
|
||||||
if (failures > 0) { console.error(`\n${failures} delivery-launch check(s) failed`); process.exit(1); }
|
if (failures > 0) { console.error(`\n${failures} delivery-launch check(s) failed`); process.exit(1); }
|
||||||
console.log('\ndelivery-launch: all checks passed');
|
console.log('\ndelivery-launch: all checks passed');
|
||||||
|
|
|
||||||
|
|
@ -3569,14 +3569,19 @@ export class GameScene extends Phaser.Scene {
|
||||||
// delivery's proof (the sale is — below);
|
// delivery's proof (the sale is — below);
|
||||||
// · onVignetteComplete — the bar scene's DONE press: the hold is
|
// · onVignetteComplete — the bar scene's DONE press: the hold is
|
||||||
// built out (the scene's effects) and the
|
// built out (the scene's effects) and the
|
||||||
// special package lands in the hold;
|
// special package lands in the hold, and —
|
||||||
|
// while the quest is held — the HOME WORLD
|
||||||
|
// becomes the destination (the delivery's
|
||||||
|
// route — the compass points it); the bar
|
||||||
|
// conversation IS the trigger;
|
||||||
// · onSurfaceDeparture — SurfaceScene.finishTakeoff (a real
|
// · onSurfaceDeparture — SurfaceScene.finishTakeoff (a real
|
||||||
// launch): after the package's sale it
|
// launch): after the package's sale it
|
||||||
// arms the 'deliveredLaunch' comms chain
|
// arms the 'deliveredLaunch' comms chain
|
||||||
// (Carol ×3 → OAC's sign-off, the payoff);
|
// (Carol ×3 → OAC's sign-off, the payoff);
|
||||||
// the capital launch sets the destination
|
// the capital launch records the delivery
|
||||||
// back to the HOME WORLD (the delivery's
|
// leg (launchedFromCapital). It no longer
|
||||||
// route — the compass points it);
|
// touches the destination — the bar
|
||||||
|
// conversation owns the route (above);
|
||||||
// · onQuestPackageSold — the shop's CONFIRM of the package's sale:
|
// · onQuestPackageSold — the shop's CONFIRM of the package's sale:
|
||||||
// saleable on the home world ONLY (TradeGrid's
|
// saleable on the home world ONLY (TradeGrid's
|
||||||
// gate), at zero credits — that sale is
|
// gate), at zero credits — that sale is
|
||||||
|
|
@ -3678,13 +3683,26 @@ export class GameScene extends Phaser.Scene {
|
||||||
* the 'special-package' joins the hold — AFTER the upgrade (the
|
* the 'special-package' joins the hold — AFTER the upgrade (the
|
||||||
* hold must fit it). Its presence is the 'cargoUpgraded' step's
|
* hold must fit it). Its presence is the 'cargoUpgraded' step's
|
||||||
* witness (the check's id) — the parcel aboard proves the ship
|
* witness (the check's id) — the parcel aboard proves the ship
|
||||||
* can hold cargo.
|
* can hold cargo;
|
||||||
|
* 3. the DELIVERY ROUTE (the 'Special Delivery' bar scene, and only
|
||||||
|
* while the quest is HELD — granted, unclaimed): the HOME WORLD
|
||||||
|
* becomes the run's destination the moment the conversation is
|
||||||
|
* over. Carol's line set it up ('just bring this package back to
|
||||||
|
* the merchant on your home world' — data/vignettes.json →
|
||||||
|
* special_delivery_bar_meeting): hearing it IS the trigger, so
|
||||||
|
* the compass points home from the bar's DONE press onward. The
|
||||||
|
* scene's own gate (VignetteModel.vignetteAvailable — granted +
|
||||||
|
* unclaimed + the step open) already keeps it playing only during
|
||||||
|
* the quest, and _specialDeliveryHeld re-verifies that here
|
||||||
|
* (belt and braces — the route is the quest's, and a scene that
|
||||||
|
* outlived it must not re-aim the destination once it ends).
|
||||||
* _questRefresh then stands the just-made steps (steps 1 + 2) DONE.
|
* _questRefresh then stands the just-made steps (steps 1 + 2) DONE.
|
||||||
* @returns {{upgradedTo: number|null, handoff: boolean}} the beats that
|
* @returns {{upgradedTo: number|null, handoff: boolean, routed: boolean}}
|
||||||
* fired (the SurfaceScene calls them out in its console slot)
|
* the beats that fired (the SurfaceScene calls them out in its
|
||||||
|
* console slot)
|
||||||
*/
|
*/
|
||||||
onVignetteComplete(vig) {
|
onVignetteComplete(vig) {
|
||||||
const out = { upgradedTo: null, handoff: false };
|
const out = { upgradedTo: null, handoff: false, routed: false };
|
||||||
if (!vig) return out;
|
if (!vig) return out;
|
||||||
// 1 — the hold's built out (the scene's effects — pure data the
|
// 1 — the hold's built out (the scene's effects — pure data the
|
||||||
// model kept): MAX-wins, never a downgrade (a later hold — a
|
// model kept): MAX-wins, never a downgrade (a later hold — a
|
||||||
|
|
@ -3703,13 +3721,47 @@ export class GameScene extends Phaser.Scene {
|
||||||
if (questId !== '') {
|
if (questId !== '') {
|
||||||
const goodId = questDef(questId)?.packageGood;
|
const goodId = questDef(questId)?.packageGood;
|
||||||
if (goodId && this._handoffSpecialPackage(goodId)) out.handoff = true;
|
if (goodId && this._handoffSpecialPackage(goodId)) out.handoff = true;
|
||||||
|
// 3 — the delivery ROUTE: the bar conversation is the trigger (the
|
||||||
|
// quest's bar scene, and only while the quest stands) — the home
|
||||||
|
// world becomes the destination the moment the scene completes, so
|
||||||
|
// the compass points the delivery's route for the flight home.
|
||||||
|
// setDestination does the rest (the route derives from it, the
|
||||||
|
// compass marks the gate to fly; its own toast sits on the sleeping
|
||||||
|
// flight scene, so the SurfaceScene calls this beat out in its
|
||||||
|
// console slot — the scout's SET DESTINATION is the same shape).
|
||||||
|
if (questId === 'special_delivery' && this._specialDeliveryHeld()) {
|
||||||
|
const homeId = this.galaxy?.homeSystemId;
|
||||||
|
if (homeId && this.systemRecord?.id !== homeId) {
|
||||||
|
this.setDestination(homeId, 'home'); // the compass points home
|
||||||
|
out.routed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (out.upgradedTo != null || out.handoff) {
|
if (out.upgradedTo != null || out.handoff || out.routed) {
|
||||||
this._questRefresh(); // steps 1 + 2 may stand done now
|
this._questRefresh(); // steps 1 + 2 may stand done now
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the 'Special Delivery' quest HELD (granted, unclaimed)? The bar
|
||||||
|
* scene's delivery route (onVignetteComplete, beat 3 — the destination
|
||||||
|
* → the home world) lands only while it stands: the scene itself plays
|
||||||
|
* only under the same gate (VignetteModel.vignetteAvailable — granted
|
||||||
|
* + unclaimed + the step open), so this read is the seam's
|
||||||
|
* belt-and-braces against the scene outliving the quest (a ledger
|
||||||
|
* drifted by a save, a re-armed scene) — once the quest ends, the bar
|
||||||
|
* must not re-aim the player's destination.
|
||||||
|
*/
|
||||||
|
_specialDeliveryHeld() {
|
||||||
|
if (!this.questState) return false;
|
||||||
|
const qid = 'special_delivery';
|
||||||
|
return (
|
||||||
|
this.questState.isGranted(qid) === true &&
|
||||||
|
this.questState.isClaimed(qid) !== true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The surface TAKE OFF seam (SurfaceScene.finishTakeoff → here).
|
* The surface TAKE OFF seam (SurfaceScene.finishTakeoff → here).
|
||||||
* Two beats ride on it:
|
* Two beats ride on it:
|
||||||
|
|
@ -3727,25 +3779,24 @@ export class GameScene extends Phaser.Scene {
|
||||||
* run — the hub plays the chain once the flight world is back
|
* run — the hub plays the chain once the flight world is back
|
||||||
* (the FIFO, each ack holding until its own ACKNOWLEDGE).
|
* (the FIFO, each ack holding until its own ACKNOWLEDGE).
|
||||||
*
|
*
|
||||||
* 2 — 'Special Delivery' step 3's leg: launching off the NEUTRAL
|
* 2 — the CAPITAL DEPARTURE leg: launching off the NEUTRAL CAPITAL
|
||||||
* CAPITAL (the bar is done, the package is aboard) records the
|
* (the bar is done, the package is aboard) records the launch in
|
||||||
* launch in the story ledger and sets the run's destination back
|
* the story ledger (launchedFromCapital — the delivery's trail).
|
||||||
* to the HOME WORLD (the delivery's route — the compass marks it,
|
* A launch off any other world is a no-op for this leg. This seam
|
||||||
* and the REACHED toast fires on the home world's arrival via
|
* NO LONGER touches the destination: the delivery's route
|
||||||
* _checkDestinationReached). A launch off any other world is a
|
* (destination → the home world) is owned by the bar conversation
|
||||||
* no-op for this leg — the quest's auto-course earns itself only
|
* itself — the quest's bar scene DONE press sets it, while the
|
||||||
* from the capital's departure.
|
* quest is held (onVignetteComplete, beat 3) — so a post-quest
|
||||||
|
* visit to the capital (a 'Pirate Problems' return) leaves the
|
||||||
|
* player's destination alone.
|
||||||
*/
|
*/
|
||||||
onSurfaceDeparture(worldName) {
|
onSurfaceDeparture(worldName) {
|
||||||
if (this.story?.delivered === true) {
|
if (this.story?.delivered === true) {
|
||||||
this.comms?.trigger('deliveredLaunch'); // the payoff chain — the once-ledger dedupes
|
this.comms?.trigger('deliveredLaunch'); // the payoff chain — the once-ledger dedupes
|
||||||
}
|
}
|
||||||
if (!this.story || !this.isCapitalWorld(worldName)) return;
|
if (!this.story || !this.isCapitalWorld(worldName)) return;
|
||||||
this.story.launchedFromCapital = true; // the leg is made
|
this.story.launchedFromCapital = true; // the trail: the capital's departure
|
||||||
this._questRefresh(); // the delivery leg may have just completed
|
this._questRefresh(); // a step may have just landed (defensive)
|
||||||
const homeId = this.galaxy?.homeSystemId;
|
|
||||||
if (!homeId || this.systemRecord?.id === homeId) return; // already home (defensive)
|
|
||||||
this.setDestination(homeId, 'home'); // the compass points home
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -708,9 +708,13 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
* run's ship starts with cargoHold 0, data/ship.json), THEN the
|
* run's ship starts with cargoHold 0, data/ship.json), THEN the
|
||||||
* scene's quest's package joins the hold (it must fit — and its
|
* scene's quest's package joins the hold (it must fit — and its
|
||||||
* presence is the 'cargoUpgraded' step's witness, so both steps stand
|
* presence is the 'cargoUpgraded' step's witness, so both steps stand
|
||||||
* done at the DONE press). The surface calls the beats out in its
|
* done at the DONE press), and — the 'Special Delivery' bar scene,
|
||||||
* console slot (the flight HUD sits behind the opaque plate for the
|
* while the quest is held — the HOME WORLD becomes the destination
|
||||||
* whole stay). The bar is the loop again; the shop raises its
|
* (the delivery's route: the bar conversation is the trigger). The
|
||||||
|
* surface calls the beats out in its console slot (the flight HUD
|
||||||
|
* sits behind the opaque plate for the whole stay — the game scene's
|
||||||
|
* own toasts would sit frozen on it; the scout's SET DESTINATION note
|
||||||
|
* is the same recipe). The bar is the loop again; the shop raises its
|
||||||
* buy/sell view now — it deferred from entry while the scene held
|
* buy/sell view now — it deferred from entry while the scene held
|
||||||
* (the scene plays before the player can touch buy/sell).
|
* (the scene plays before the player can touch buy/sell).
|
||||||
*/
|
*/
|
||||||
|
|
@ -731,6 +735,27 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
durationMs: 3600,
|
durationMs: 3600,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (beat?.routed === true) {
|
||||||
|
// The delivery route (onVignetteComplete's beat 3 — the bar
|
||||||
|
// conversation just set the destination to the home world): the
|
||||||
|
// surface calls it out here (the game scene's own toast sits on
|
||||||
|
// the sleeping scene), the map's SET DESTINATION toast in shape.
|
||||||
|
// Staggered behind the package note (the slot is one-at-a-time —
|
||||||
|
// a new note replaces the old): the beats read in order, each for
|
||||||
|
// a full hold, and a take-off before the delay simply drops the
|
||||||
|
// echo (the destination is set either way — the compass shows it).
|
||||||
|
const homeId = g?.galaxy?.homeSystemId;
|
||||||
|
const plan = homeId ? planRoute(g.galaxy, g.systemRecord?.id, homeId) : null;
|
||||||
|
const dest = String(g?.homeWorldName ?? 'HOME WORLD').toUpperCase();
|
||||||
|
const label = `DESTINATION SET — ${dest}` + (plan?.hops != null ? ` \u00b7 ${plan.hops} HOP(S)` : '');
|
||||||
|
const note = () => this.consoleNote(label, {
|
||||||
|
glyph: '\u25c6',
|
||||||
|
glyphColor: toCss(config.get('gates.route.compassColor', '#ff8c1a')),
|
||||||
|
durationMs: 3600,
|
||||||
|
});
|
||||||
|
if (beat?.handoff === true) this.time.delayedCall(3200, note); // behind the package note
|
||||||
|
else note(); // no package beat to read first — land it now
|
||||||
|
}
|
||||||
if (this.shopMode) this.buildShopView();
|
if (this.shopMode) this.buildShopView();
|
||||||
this.vignetteLocation = null;
|
this.vignetteLocation = null;
|
||||||
}
|
}
|
||||||
|
|
@ -1743,10 +1768,16 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
this.destroyVideo(this.takeoffVideo);
|
this.destroyVideo(this.takeoffVideo);
|
||||||
this.takeoffVideo = null;
|
this.takeoffVideo = null;
|
||||||
// The 'Special Delivery' departure seam (GameScene.onSurfaceDeparture):
|
// The 'Special Delivery' departure seam (GameScene.onSurfaceDeparture):
|
||||||
// a launch OFF the neutral capital is the delivery's leg — the run's
|
// a launch OFF the neutral capital records the delivery's trail
|
||||||
// destination is set back to the home world (the compass points it).
|
// (launchedFromCapital — the story ledger) and, once the package's
|
||||||
// Runs now, while the GameScene still owns the run: the wake back to
|
// home-world sale stands, arms the 'deliveredLaunch' payoff chain.
|
||||||
// the flight world is the beat the quest's step 3 waits on.
|
// It no longer sets the destination — the delivery's route is owned
|
||||||
|
// by the bar conversation itself (the quest's bar scene DONE press,
|
||||||
|
// GameScene.onVignetteComplete, while the quest is held), so a
|
||||||
|
// post-quest departure (a 'Pirate Problems' return leg) leaves the
|
||||||
|
// player's destination alone. Runs now, while the GameScene still
|
||||||
|
// owns the run: the wake back to the flight world is the beat the
|
||||||
|
// quest's step 3 waits on.
|
||||||
this.gameScene?.onSurfaceDeparture?.(this.planetName);
|
this.gameScene?.onSurfaceDeparture?.(this.planetName);
|
||||||
// The comms beat (data/comms.json: tetherL2Launch — oac-relaunch →
|
// The comms beat (data/comms.json: tetherL2Launch — oac-relaunch →
|
||||||
// oac-explore): the player is launching back into space with the
|
// oac-explore): the player is launching back into space with the
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue