// R3F shell around the imperative game core. The Canvas owns the renderer, // default camera and the rAF loop; boots the core once and drives it // from useFrame. Lights and environment are declarative; the grid, walls, // duck rig, ball and props are added to the scene by the core (they carry // ceremony-driven shader state). import { useEffect } from "react"; import * as THREE from "three"; import { Canvas, useThree, useFrame } from "@react-three/fiber"; import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js"; import { bootGame } from "../game/game.js"; import { gameApi } from "../store.js"; import { SPAWN_X, SPAWN_Y } from "../game/constants.js"; import CrtDistortion, { CRT_DISTORTION_ENABLED } from "./CrtDistortion.jsx"; function Game() { const { scene, camera, gl } = useThree(); useEffect(() => { scene.background = new THREE.Color(0x08080c); gl.setClearColor(0x08080c, 1); const pmrem = new THREE.PMREMGenerator(gl); scene.environment = pmrem.fromScene(new RoomEnvironment()).texture; scene.environmentIntensity = 0.45; bootGame({ scene, camera, renderer: gl }); }, [scene, camera, gl]); // dt clamped so a background-tab stall can't slingshot the camera orbit. useFrame((_, dt) => { gameApi.frame?.(Math.min(dt, 0.05)); }); return ( <> ); } export default function GameCanvas() { return ( {CRT_DISTORTION_ENABLED && } ); }