THE HORIZON: six stations get a room to stand in

Six blurred backdrops off the FLUX lane — dusk backyard for the grill,
copper-hung splashback for the pan, steaming stockpot shelf for the
poach, butcher's tile for the steak board, pantry shelves for the eggs,
steel cold room for the fridge — and a lesson in frustums for their
integration. A far wall plane (the kitchen splashback trick) is
invisible to these stations' pitched-down cameras: at 45 degrees of
downward tilt nothing above the horizontal ever enters the frame. So
the art ships as the scene BACKGROUND instead — screen-space, swapped
in on enter() and restored on exit() — and the oversized 24x12 bench
slabs that covered every pixel shrank to real bench size so the room
can peek around them. The stations went from furniture in fog to
furniture in a kitchen.

Mechanics untouched by the slab diet — the drag planes are infinite
THREE.Planes, not the meshes: grill/pan/fridge/steak drivers all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-21 02:52:58 +10:00
parent d998b74abc
commit d0fc805705
14 changed files with 65 additions and 5 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

View File

@ -44,4 +44,13 @@ gen hero_eggs 304 "$JSTYLE, glass bowl of whipped glossy egg whites beside t
gen hero_poach 305 "$JSTYLE, single perfect poached egg on a slotted spoon, white silky and intact, droplet falling"
gen hero_benedict 306 "$JSTYLE, eggs benedict on a toasted muffin, hollandaise dripping down, single plate centered"
# Station backdrops — blurred walls so every scene has a horizon, not fog.
BSTYLE="soft-focus heavily blurred background, muted warm dark tones, no sharp foreground objects, cozy game environment backdrop art, wide shot"
gen bg_grill 401 "$BSTYLE, backyard at dusk seen past a grill, wooden fence, string lights, drifting smoke"
gen bg_stove 402 "$BSTYLE, dark tiled kitchen splashback with hanging copper utensils and a warm lamp"
gen bg_pot 403 "$BSTYLE, steamy stove wall with a shelf of stockpots and rising steam wisps"
gen bg_butcher 404 "$BSTYLE, white tiled butcher shop wall with hanging tools and a wooden rail"
gen bg_pantry 405 "$BSTYLE, warm pantry shelves with egg cartons, flour sacks and preserving jars"
gen bg_coldroom 406 "$BSTYLE, stainless steel commercial kitchen cold room, soft blue-grey light"
echo done

View File

@ -14,6 +14,7 @@ import {
cartonWord,
} from '../sim/eggs';
import { el, Panel } from '../ui/hud';
import { loadBackdrop } from './props';
const BENCH_Y = 0;
@ -62,6 +63,9 @@ export class EggBenchView implements View {
onDone: ((result: ReturnType<typeof eggResult>) => void) | null = null;
private bgTex = loadBackdrop('/assets/img/bg_pantry.png');
private prevBg: unknown = null;
constructor(private app: App) {
this.buildScenery();
this.panel = new Panel('egg-panel');
@ -81,7 +85,7 @@ export class EggBenchView implements View {
private buildScenery(): void {
const bench = new THREE.Mesh(
new THREE.BoxGeometry(24, 0.4, 12),
new THREE.BoxGeometry(9, 0.4, 6),
new THREE.MeshStandardMaterial({ color: 0x4a3524, roughness: 0.85 }),
);
bench.position.y = -0.2;
@ -163,9 +167,12 @@ export class EggBenchView implements View {
}
enter(): void {
this.prevBg = this.app.scene.background;
this.app.scene.background = this.bgTex;
this.panel.show();
}
exit(): void {
this.app.scene.background = this.prevBg as never;
this.panel.hide();
}

View File

@ -13,6 +13,7 @@ import {
FRIDGE_MUSHROOM_ID,
} from '../sim/coldchain';
import { el, Panel } from '../ui/hud';
import { loadBackdrop } from './props';
/** A drop target on the fridge front: a rectangle mapped to (zone, shelf). */
interface Bay {
@ -65,6 +66,9 @@ export class FridgeView implements View {
onDone: ((result: ReturnType<typeof storeHealth> & { store: ColdStore }) => void) | null = null;
private bgTex = loadBackdrop('/assets/img/bg_coldroom.png');
private prevBg: unknown = null;
constructor(private app: App) {
this.buildScenery();
this.panel = new Panel('fridge-panel');
@ -177,9 +181,12 @@ export class FridgeView implements View {
}
enter(): void {
this.prevBg = this.app.scene.background;
this.app.scene.background = this.bgTex;
this.panel.show();
}
exit(): void {
this.app.scene.background = this.prevBg as never;
this.panel.hide();
}

View File

@ -18,6 +18,7 @@ import {
} from '../sim/charcoal';
import { newOutdoorEnv } from '../sim/heatsource';
import { el, Panel } from '../ui/hud';
import { loadBackdrop } from './props';
const GRATE_Y = 0.5;
const GRATE_W = 3.6;
@ -56,6 +57,9 @@ export class GrillView implements View {
onDone: ((result: ReturnType<typeof grillResult>) => void) | null = null;
private bgTex = loadBackdrop('/assets/img/bg_grill.png');
private prevBg: unknown = null;
constructor(private app: App) {
this.buildScenery();
const n = 48;
@ -89,7 +93,7 @@ export class GrillView implements View {
private buildScenery(): void {
const ground = new THREE.Mesh(
new THREE.BoxGeometry(30, 0.4, 20),
new THREE.BoxGeometry(8, 0.4, 5),
new THREE.MeshStandardMaterial({ color: 0x5a6b3e, roughness: 0.95 }),
);
ground.position.y = -0.2;
@ -155,9 +159,12 @@ export class GrillView implements View {
}
enter(): void {
this.prevBg = this.app.scene.background;
this.app.scene.background = this.bgTex;
this.panel.show();
}
exit(): void {
this.app.scene.background = this.prevBg as never;
this.panel.hide();
}

View File

@ -17,6 +17,7 @@ import {
} from '../sim/pan';
import { newIndoorEnv } from '../sim/heatsource';
import { el, Panel } from '../ui/hud';
import { loadBackdrop } from './props';
import { loadProp } from './assets';
const PAN_Y = 0.55;
@ -49,6 +50,9 @@ export class PanView implements View {
onDone: ((result: ReturnType<typeof panResult>) => void) | null = null;
private bgTex = loadBackdrop('/assets/img/bg_stove.png');
private prevBg: unknown = null;
constructor(private app: App) {
this.buildScenery();
this.panel = new Panel('pan-panel');
@ -68,7 +72,7 @@ export class PanView implements View {
private buildScenery(): void {
const bench = new THREE.Mesh(
new THREE.BoxGeometry(24, 0.4, 12),
new THREE.BoxGeometry(9, 0.4, 6),
new THREE.MeshStandardMaterial({ color: 0x3a3a3e, roughness: 0.7, metalness: 0.2 }),
);
bench.position.y = -0.2;
@ -149,9 +153,12 @@ export class PanView implements View {
}
enter(): void {
this.prevBg = this.app.scene.background;
this.app.scene.background = this.bgTex;
this.panel.show();
}
exit(): void {
this.app.scene.background = this.prevBg as never;
this.panel.hide();
}

View File

@ -20,6 +20,7 @@ import {
YOLK_TREMBLE,
} from '../sim/poach';
import { el, Panel } from '../ui/hud';
import { loadBackdrop } from './props';
const POT_Y = 0.55;
const POT_R = 1.15;
@ -60,6 +61,9 @@ export class PoachView implements View {
onDone: ((result: ReturnType<typeof poachResult>) => void) | null = null;
private bgTex = loadBackdrop('/assets/img/bg_pot.png');
private prevBg: unknown = null;
constructor(private app: App) {
this.buildScenery();
this.panel = new Panel('poach-panel');
@ -79,7 +83,7 @@ export class PoachView implements View {
private buildScenery(): void {
const bench = new THREE.Mesh(
new THREE.BoxGeometry(24, 0.4, 12),
new THREE.BoxGeometry(9, 0.4, 6),
new THREE.MeshStandardMaterial({ color: 0x3a3a3e, roughness: 0.7, metalness: 0.2 }),
);
bench.position.y = -0.2;
@ -188,9 +192,12 @@ export class PoachView implements View {
}
enter(): void {
this.prevBg = this.app.scene.background;
this.app.scene.background = this.bgTex;
this.panel.show();
}
exit(): void {
this.app.scene.background = this.prevBg as never;
this.panel.hide();
}

View File

@ -196,3 +196,12 @@ export function makeBench(): THREE.Group {
g.add(wall);
return g;
}
/** A station's blurred backdrop, served as the scene BACKGROUND (screen-space)
* the top-down cameras never see a far wall, but every uncovered pixel
* becomes horizon this way. Swap it in on enter(), restore on exit(). */
export function loadBackdrop(url: string): THREE.Texture {
const tex = new THREE.TextureLoader().load(assetUrl(url));
tex.colorSpace = THREE.SRGBColorSpace;
return tex;
}

View File

@ -12,6 +12,7 @@ import {
type Doneness,
} from '../sim/steak';
import { el, Panel } from '../ui/hud';
import { loadBackdrop } from './props';
const BOARD_Y = 0.06;
const STEAK_W = 1.9;
@ -52,6 +53,9 @@ export class SteakBoardView implements View {
onDone: ((result: ReturnType<typeof steakResult>) => void) | null = null;
private bgTex = loadBackdrop('/assets/img/bg_butcher.png');
private prevBg: unknown = null;
constructor(private app: App) {
this.buildScenery();
this.panel = new Panel('steak-panel');
@ -71,7 +75,7 @@ export class SteakBoardView implements View {
private buildScenery(): void {
const bench = new THREE.Mesh(
new THREE.BoxGeometry(24, 0.4, 12),
new THREE.BoxGeometry(9, 0.4, 6),
new THREE.MeshStandardMaterial({ color: 0x4a3524, roughness: 0.85 }),
);
bench.position.y = -0.2;
@ -147,9 +151,12 @@ export class SteakBoardView implements View {
}
enter(): void {
this.prevBg = this.app.scene.background;
this.app.scene.background = this.bgTex;
this.panel.show();
}
exit(): void {
this.app.scene.background = this.prevBg as never;
this.panel.hide();
}