Fix floor-start shifts booting inert, and the floor carrying last night into this one

applyLocation() only moved flags — it never announced the move. FloorDemoScene
latches `present` when it binds the night context, which (now the night preloads
every prop texture) happens BEFORE the initial applyLocation runs, so every
floor-start shift — bartender, glassie, DJ — came up visible but completely
inert, with the audio filter still in door mode. The night now emits
night:phaseChange and audio:location for the starting post.

Phaser reuses scene instances across restarts, so class-field initialisers run
once per GAME, not once per night: a DJ shift left djMode set and the next
night's floor booted with movement locked and the keyboard eaten, last night's
puddles kept slipping people through destroyed sprites, and the handled-patron
sets marked fresh arrivals as already dealt with. resetNightState() now wipes
all of it in create().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-22 02:24:22 +10:00
parent 79cd80dc01
commit 164b4480c5
2 changed files with 48 additions and 1 deletions

View File

@ -310,7 +310,17 @@ export class NightScene extends Phaser.Scene {
// Door and Floor BOTH run all night; the flags below decide which one
// the player is inhabiting. Floor starts hidden.
// The roster decides where you stand when the doors open (design §6).
this.time.delayedCall(0, () => this.applyLocation(this.role.startLoc));
this.time.delayedCall(0, () => {
this.applyLocation(this.role.startLoc);
// ...and ANNOUNCE it. applyLocation only moves flags; the room latches
// onto the event. FloorDemoScene reads `present` when it binds this
// context, which — now that the night preloads every prop texture —
// happens before this runs, so a floor-start shift (bartender, glassie,
// DJ) came up visible but completely inert, and the audio filter stayed
// in door mode while you stood on the dancefloor.
this.bus.emit('night:phaseChange', { location: this.role.startLoc });
this.bus.emit('audio:location', { location: this.role.startLoc });
});
};
if (missing.length === 0) return launch();
for (const k of missing) this.load.image(`gen:prop:${k}`, `props/${k}.png`);

View File

@ -203,6 +203,7 @@ export class FloorDemoScene extends Phaser.Scene {
create(data?: { night?: NightContext }): void {
this.nightCtx = data?.night ?? null;
this.resetNightState();
// Wired explicitly: Phaser does not invoke a `shutdown` METHOD on a Scene
// subclass; without this, listeners and sprites leak on every scene stop.
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => this.teardown());
@ -217,6 +218,42 @@ export class FloorDemoScene extends Phaser.Scene {
this.load.start();
}
/**
* Wipe everything that belongs to ONE night.
*
* Phaser reuses the scene instance across restarts, so class-field
* initialisers run exactly once in the lifetime of the game every field
* below would otherwise carry last night into this one. That is not
* theoretical: a DJ shift left `djMode` set, so the next night's floor booted
* with movement locked and the keyboard eaten; last night's puddles kept
* slipping people through sprites Phaser had already destroyed; and the
* handled-patron sets marked fresh arrivals as already dealt with, because
* patron ids restart with the run.
*/
private resetNightState(): void {
this.djMode = false;
this.djBuild = null;
this.barMode = false;
this.carryingCup = false;
this.mop = null;
// The graphics these referenced went down with the previous scene shutdown;
// dropping the references is the whole job.
this.puddles = [];
this.vomitCount = 0;
this.filming = null;
this.fainter = null;
this.rack = null;
this.escorting = null;
this.target = null;
this.liveFight = null;
this.uvMode = false;
this.jointDealt.clear();
this.maggotSeen.clear();
this.spotted.clear();
this.cutOffDealt.clear();
this.slipCooldown.clear();
}
private loadGenProps(): void {
const manifest = this.cache.json.get('propManifest') as { props?: string[] } | undefined;
const kinds = manifest?.props ?? [];