feat(app): wire the voyage together
Controller drives world and curtain; caption with Back to sea; play, previous and next in the timeline bar; keyboard; no-WebGL frieze stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ac017787b5
commit
d355b3a41f
+28
-8
@@ -7,12 +7,14 @@ export interface ControllerWorld {
|
||||
sailTo(songId: string, opts?: { instant?: boolean }): Promise<void>;
|
||||
setLanding(id: LandingId | null): Promise<void>;
|
||||
setRibbons(motifIds: string[] | null, highlight: string | null): void;
|
||||
horizonFromFrieze(groundYpx: number): void;
|
||||
}
|
||||
/** The parts of the frieze curtain the controller drives (src/app/frieze/curtain.ts). */
|
||||
export interface ControllerCurtain {
|
||||
drop(sagaId: PlaceId, opts?: { landing?: LandingId }): Promise<void>;
|
||||
lift(): Promise<void>;
|
||||
fade(): Promise<void>;
|
||||
groundY(): number;
|
||||
}
|
||||
|
||||
export interface Controller {
|
||||
@@ -23,6 +25,11 @@ export interface Controller {
|
||||
stop(): void;
|
||||
playing(): boolean;
|
||||
onPlayingChange(fn: (playing: boolean) => void): void;
|
||||
/** The landing the stage is in, if any. */
|
||||
landing(): LandingId | null;
|
||||
/** "Back to sea": leave the landing, keep the song. */
|
||||
leaveLanding(): Promise<void>;
|
||||
onLandingChange(fn: (landing: LandingId | null) => void): void;
|
||||
}
|
||||
|
||||
const PLAY_HOLD_MS = 3400;
|
||||
@@ -30,7 +37,8 @@ const PLAY_HOLD_MS = 3400;
|
||||
/**
|
||||
* Moves the stage when the song changes. Steps run one at a time; a newer request makes the running step
|
||||
* stop at its next await, and the last request wins. A curtain left down by a cancelled step is lifted by
|
||||
* the next one, so rapid navigation never leaves the stage covered.
|
||||
* the next one, so rapid navigation never leaves the stage covered. Order under a curtain: cover → sail →
|
||||
* landing → horizon on the frieze's ground line → lift (a landing drop lifts through the doorway).
|
||||
*/
|
||||
export function createController(opts: {
|
||||
store: Store;
|
||||
@@ -47,6 +55,8 @@ export function createController(opts: {
|
||||
let queue: Promise<void> = Promise.resolve();
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
const playListeners: ((p: boolean) => void)[] = [];
|
||||
const landingListeners: ((l: LandingId | null) => void)[] = [];
|
||||
const setLanded = (l: LandingId | null) => { landed = l; landingListeners.forEach((fn) => fn(l)); };
|
||||
|
||||
const ribbons = () => {
|
||||
const s = store.get();
|
||||
@@ -68,18 +78,20 @@ export function createController(opts: {
|
||||
if (world) {
|
||||
if (landed && landed !== landing) {
|
||||
await world.setLanding(null);
|
||||
landed = null;
|
||||
setLanded(null);
|
||||
}
|
||||
await world.sailTo(to, { instant: kind !== "sail" });
|
||||
}
|
||||
if (my !== gen) return;
|
||||
if (curtainDown) {
|
||||
await curtain.lift();
|
||||
curtainDown = false;
|
||||
}
|
||||
if (world && landing !== landed) {
|
||||
await world.setLanding(landing);
|
||||
landed = landing;
|
||||
setLanded(landing);
|
||||
}
|
||||
if (my !== gen) return;
|
||||
if (curtainDown) {
|
||||
world?.horizonFromFrieze(curtain.groundY());
|
||||
await curtain.lift();
|
||||
curtainDown = false;
|
||||
}
|
||||
settled = to;
|
||||
ribbons();
|
||||
@@ -118,7 +130,7 @@ export function createController(opts: {
|
||||
const first = songById(settled)!;
|
||||
running++;
|
||||
queue = world.sailTo(settled, { instant: true })
|
||||
.then(async () => { if (first.landing) { await world.setLanding(first.landing); landed = first.landing; } ribbons(); })
|
||||
.then(async () => { if (first.landing) { await world.setLanding(first.landing); setLanded(first.landing); } ribbons(); })
|
||||
.catch((e) => console.error(e))
|
||||
.finally(() => { running--; });
|
||||
}
|
||||
@@ -130,5 +142,13 @@ export function createController(opts: {
|
||||
stop() { setPlaying(false); },
|
||||
playing: () => playingNow,
|
||||
onPlayingChange(fn) { playListeners.push(fn); },
|
||||
landing: () => landed,
|
||||
leaveLanding() {
|
||||
if (!world || !landed) return Promise.resolve();
|
||||
const done = queue.then(async () => { await world.setLanding(null); setLanded(null); });
|
||||
queue = done.catch((e) => console.error(e));
|
||||
return done;
|
||||
},
|
||||
onLandingChange(fn) { landingListeners.push(fn); },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user