Hide the load behind the roster board
~130 sprites meant several seconds of blank screen after picking a shift. RosterScene now warms the prop textures while you read the cards — opportunistic and non-blocking, so clicking through early is no worse than before. Manifest via plain fetch to avoid the nested load.once(COMPLETE) pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8bd219796a
commit
442efa4c3c
@ -2432,3 +2432,36 @@ paperwork. Tweens here decorate; `update()` owns the state machine.
|
|||||||
The desk handed off to NightSummary while **staying active underneath it** —
|
The desk handed off to NightSummary while **staying active underneath it** —
|
||||||
nobody stopped it. Same class as the dev-route bug that left a live Night
|
nobody stopped it. Same class as the dev-route bug that left a live Night
|
||||||
driving a dead Door. `go()` now stops itself before calling `next()`.
|
driving a dead Door. `go()` now stops itself before calling `next()`.
|
||||||
|
|
||||||
|
## SESSION — FABLE-SOLO-27 · 2026-07-22
|
||||||
|
|
||||||
|
**Branch:** main (solo, Fable — sole operator from here) · **Gate:** lint ✓ build ✓ test ✓ (831 tests, 57 files)
|
||||||
|
|
||||||
|
### The blank screen after picking a shift
|
||||||
|
The art batch is ~130 sprites now and NightScene loads every one of them before
|
||||||
|
it launches Door and Floor. Cold, that is several seconds of BLANK SCREEN after
|
||||||
|
you pick a card, with nothing to say the game had not died. (It also fooled me
|
||||||
|
mid-verification into calling a regression on main that was never there — the
|
||||||
|
loader was simply still working, progress climbing 0.25 → 0.5 → 0.76 → 1.)
|
||||||
|
|
||||||
|
Fix: **RosterScene prewarms the prop textures while you read the board.** You
|
||||||
|
are looking at three shift cards anyway; by the time you pick one the room is
|
||||||
|
already loaded and Door/Floor come up at once. A quiet line under the board
|
||||||
|
reads `unlocking the doors · 64%` → `the room is ready`.
|
||||||
|
|
||||||
|
- Opportunistic, never blocking: the cards are live immediately, and clicking
|
||||||
|
through early just leaves NightScene to load the rest exactly as before. It
|
||||||
|
is never worse than it was, only usually better.
|
||||||
|
- The manifest is fetched with plain `fetch()`, not the Phaser loader, so there
|
||||||
|
is no `load.once(COMPLETE)` registered from inside another COMPLETE handler —
|
||||||
|
that nested pattern is what NightScene already has to live with, and it is
|
||||||
|
the kind of thing that works until an asset count changes.
|
||||||
|
- Textures are game-global, so anything warmed here counts for the night.
|
||||||
|
- No manifest at all stays a supported state (placeholders, silence).
|
||||||
|
|
||||||
|
### Git hygiene check (asked for, and worth recording)
|
||||||
|
Working tree clean · HEAD == origin/main exactly (0 ahead, 0 behind) · no
|
||||||
|
stashes · two dangling commits are pre-rebase copies of my own work, and every
|
||||||
|
file in them verified byte-identical on HEAD or differing only where the other
|
||||||
|
session also edited · all 14 feature markers from this session's work confirmed
|
||||||
|
present · deployed bundle hash matches the local build.
|
||||||
|
|||||||
@ -14,6 +14,10 @@ const H = 360;
|
|||||||
const MONO = 'monospace';
|
const MONO = 'monospace';
|
||||||
const NIGHT_NAMES = ['thursday', 'friday', 'saturday'] as const;
|
const NIGHT_NAMES = ['thursday', 'friday', 'saturday'] as const;
|
||||||
|
|
||||||
|
/** Quiet, because it is not the player's problem — just proof of life. */
|
||||||
|
const PREWARM = 'unlocking the doors · {pct}%';
|
||||||
|
const PREWARM_DONE = 'the room is ready';
|
||||||
|
|
||||||
const CARD_W = 168;
|
const CARD_W = 168;
|
||||||
const CARD_H = 150;
|
const CARD_H = 150;
|
||||||
|
|
||||||
@ -25,6 +29,7 @@ interface PassThrough {
|
|||||||
|
|
||||||
export class RosterScene extends Phaser.Scene {
|
export class RosterScene extends Phaser.Scene {
|
||||||
private carried: PassThrough = {};
|
private carried: PassThrough = {};
|
||||||
|
private prewarm!: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Roster');
|
super('Roster');
|
||||||
@ -34,6 +39,44 @@ export class RosterScene extends Phaser.Scene {
|
|||||||
this.carried = data ?? {};
|
this.carried = data ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warm the prop textures while the player reads the board.
|
||||||
|
*
|
||||||
|
* The art batch is ~130 sprites now, and NightScene loads them all before it
|
||||||
|
* launches Door and Floor — which meant several seconds of BLANK SCREEN after
|
||||||
|
* you picked a shift, with nothing to say the game hadn't died. The roster is
|
||||||
|
* the natural place to hide that: you are reading three cards anyway.
|
||||||
|
*
|
||||||
|
* Opportunistic, never blocking. The cards are live immediately; click
|
||||||
|
* through early and NightScene loads whatever is left exactly as before.
|
||||||
|
* Textures are game-global, so anything that lands here counts there.
|
||||||
|
*/
|
||||||
|
private prewarmProps(): void {
|
||||||
|
// Plain fetch rather than the Phaser loader for the manifest: chaining a
|
||||||
|
// second `load.once(COMPLETE)` from inside the first COMPLETE handler is
|
||||||
|
// the fragile pattern NightScene already has to live with. One queue, one
|
||||||
|
// start, no nesting.
|
||||||
|
void fetch('props/manifest.json')
|
||||||
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
|
.then((m: { props?: string[] } | null) => {
|
||||||
|
if (!m || !this.scene.isActive()) return;
|
||||||
|
const missing = (m.props ?? []).filter((k) => !this.textures.exists(`gen:prop:${k}`));
|
||||||
|
if (missing.length === 0) return;
|
||||||
|
for (const k of missing) this.load.image(`gen:prop:${k}`, `props/${k}.png`);
|
||||||
|
this.load.on(Phaser.Loader.Events.PROGRESS, (v: number) => {
|
||||||
|
if (this.prewarm.active) this.prewarm.setText(PREWARM.replace('{pct}', String(Math.round(v * 100))));
|
||||||
|
});
|
||||||
|
this.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||||
|
if (this.prewarm.active) this.prewarm.setText(PREWARM_DONE);
|
||||||
|
});
|
||||||
|
this.load.start();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// No manifest at all is a supported state — the game renders
|
||||||
|
// placeholders and says nothing about it. Same here.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
create(): void {
|
create(): void {
|
||||||
// Display only — NightScene remains the authority on run state (it will
|
// Display only — NightScene remains the authority on run state (it will
|
||||||
// clearSave on freshRun itself; we just show the board that run implies).
|
// clearSave on freshRun itself; we just show the board that run implies).
|
||||||
@ -95,5 +138,10 @@ export class RosterScene extends Phaser.Scene {
|
|||||||
fontFamily: MONO, fontSize: '7px', color: '#6a5a44',
|
fontFamily: MONO, fontSize: '7px', color: '#6a5a44',
|
||||||
})
|
})
|
||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
|
|
||||||
|
this.prewarm = this.add
|
||||||
|
.text(W / 2, H - 32, '', { fontFamily: MONO, fontSize: '7px', color: '#5a4c38' })
|
||||||
|
.setOrigin(0.5);
|
||||||
|
this.prewarmProps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user