From 393b653ccae23e05f81b8a80861a41ce43dae67d Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 27 Apr 2026 10:53:50 +0200 Subject: [PATCH] fix: archi --- .agent/AGENTS.md | 113 ++++++++--------------- docs/technical/architecture.md | 60 ++++++------ docs/technical/target-architecture.md | 45 +++++---- docs/user/features.md | 51 +++++----- src/components/3d/InteractableObject.tsx | 80 +++++++++++----- src/stateManager/AudioManager.ts | 13 ++- src/stateManager/InteractionManager.ts | 13 ++- src/types/interaction.ts | 14 ++- src/types/logger.ts | 13 ++- src/utils/debug/Debug.ts | 3 +- src/utils/debug/isDebugEnabled.ts | 7 ++ src/utils/logger.ts | 5 +- src/world/World.tsx | 7 +- src/world/player/PlayerComponent.tsx | 4 +- src/world/player/PlayerController.tsx | 5 - vite.config.ts | 1 - 16 files changed, 242 insertions(+), 192 deletions(-) create mode 100644 src/utils/debug/isDebugEnabled.ts diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md index d6e9dc6..0f732ba 100644 --- a/.agent/AGENTS.md +++ b/.agent/AGENTS.md @@ -1,90 +1,55 @@ -# Agent — La Fabrik +# Agent - La Fabrik -You are working on **La Fabrik**, an interactive 3D web experience built with React Three Fiber. The player steps into the role of a technician in Altera (2050) and completes missions: repairing an e-bike, fixing a power grid, upgrading a vertical farm. +You are working on **La Fabrik**, an interactive 3D web experience built with React Three Fiber. -## Project Identity +## Read This First -- **Stack:** React 19, Three.js, @react-three/fiber 9, @react-three/drei, @react-three/rapier, GSAP, TypeScript, Vite -- **No external state lib.** State is managed by a custom `GameManager` singleton with a subscribe/getState pattern. -- **No Zustand, no Redux, no Context for global state.** -- **Versions are pinned** (no `^` in dependencies). Do not upgrade packages without explicit request. +- `docs/technical/architecture.md` describes the code that exists today. +- `docs/technical/target-architecture.md` describes the intended target-state. +- Do not assume target-state systems already exist. -## Architecture Rules +## Current Implementation -### Two patterns coexist +- Stack: React 19, Three.js, `@react-three/fiber`, `@react-three/drei`, `@react-three/rapier`, TypeScript, Vite +- No external global state library is used. +- Current singleton-style services are limited to: + - `InteractionManager` + - `AudioManager` + - `Debug` +- Current gameplay scope is still prototype-level: + - player movement + - trigger/grab interactions + - debug camera and scene switching + - simple audio playback -1. **Singleton manager classes** — for orchestration, audio, cinematics, zone detection, debug -2. **Declarative React components** — for all 3D scene objects (map, zones, lights, player, postprocessing) +## Current Architecture Rules -Scene objects are **never** singleton classes. Managers are **never** React components. +- Scene objects live in `src/world/` and `src/components/3d/`. +- HTML overlays live in `src/components/ui/`. +- Shared static config lives in `src/data/`. +- Debug tooling lives in `src/utils/debug/` and `src/hooks/debug/`. +- Use the `@/` alias for imports from `src/`. +- Prefer small, direct changes over adding new abstraction layers. +- Shared types should live close to their domain and only move outward when they gain multiple real consumers. -### State ownership +## Target-State Guidance -- `GameManager` is the single source of truth for durable gameplay state (phase, zone, mission, input lock, dialogue) -- Other managers (`CinematicManager`, `AudioManager`, `ZoneManager`) handle side effects only — they read from GameManager but do not duplicate its state -- React components subscribe to GameManager through `useGameState()` hook -- **High-frequency values** (movement, camera interpolation, physics) stay in `useRef` + `useFrame` — never in React state +The project may later grow toward a manager-driven gameplay architecture with clearer separation between: -### File conventions +- production world code +- gameplay orchestration +- UI overlays +- debug tooling -- Every file starts with a comment: `# route path ` (e.g. `# route path src/world/Map.tsx`) -- Scene components live in `src/world/` and `src/components/3d/` -- UI overlays live in `src/components/ui/` -- Managers live in `src/stateManager/` -- Debug tooling lives in `src/utils/debug/` -- Hooks live in `src/hooks/` -- Static data lives in `src/data/` -- Shaders live in `src/shaders/` -- Utilities live in `src/utils/` +That target-state is aspirational until the matching code exists. If a target-state rule conflicts with the current implementation, treat the current code as the source of truth and improve it incrementally. -### Import paths +## Do Not Assume -Use `@/` alias for imports from `src/`: - -```ts -import { GameManager } from "@/stateManager/GameManager"; -import { useGameState } from "@/hooks/useGameState"; -``` - -### Memory management - -- Dispose only what you own (custom materials, render targets, manual clones) -- Never blindly deep-dispose shared/cached assets (drei loaders cache models) -- Use `Dispose.material()`, `Dispose.mesh()`, `Dispose.renderTarget()` from `src/utils/Dispose.ts` - -### Debug - -- Debug panel activates with `?debug` in URL -- All debug logic goes through `Debug.getInstance()` from `src/utils/debug/Debug.ts` -- Never scatter `if (isDev)` blocks across files -- `r3f-perf` is lazy-loaded only in debug mode via `src/utils/debug/DebugPerf.tsx` - -## Managers (4 max) - -| Manager | Responsibility | -| ------------------ | ------------------------------------------------------------------- | -| `GameManager` | Phase, zone, mission, input lock, dialogue — single source of truth | -| `CinematicManager` | GSAP timelines, camera lock/unlock | -| `AudioManager` | Music, SFX, spatial audio | -| `ZoneManager` | Zone detection, LOD triggers | - -## Do NOT - -- Create new manager classes without explicit request -- Use Zustand, Redux, or React Context for global state -- Put high-frequency values in React state (`useState`) -- Import `CinematicManager`/`AudioManager`/`ZoneManager` directly from components — always go through `GameManager` -- Upgrade pinned dependency versions -- Create files outside the documented architecture without explicit request +- There is no `GameManager` in the current codebase. +- There are no implemented mission, zone, cinematic, or dialogue systems yet. +- Dependency versions are not pinned today; do not rewrite dependency strategy unless explicitly asked. +- The old `# route path ...` file header convention is not in use. ## Skills -See `.agent/skills/` for detailed patterns per technology: - -- `best-practices.md` — Code generation conventions (W3C, simple, scalable, modern) -- `r3f.md` — React Three Fiber component patterns -- `three.md` — Three.js conventions and AnimationMixer -- `gsap.md` — GSAP timeline and cinematic patterns -- `managers.md` — Singleton manager implementation -- `memory.md` — GPU memory and disposal rules -- `debug.md` — Debug utility and r3f-perf setup +Files in `.agent/skills/` are supplemental patterns and examples. Some describe target-state or generic practices rather than the exact current implementation, so verify against the code before applying them. diff --git a/docs/technical/architecture.md b/docs/technical/architecture.md index 97783d8..769575d 100644 --- a/docs/technical/architecture.md +++ b/docs/technical/architecture.md @@ -1,43 +1,47 @@ -# Implemented Architecture +# Current Architecture This document describes the code that exists today in the repository. ## Runtime Structure -- `src/App.tsx` mounts the `Canvas`, the 3D `World`, the debug perf overlay, and the HTML crosshair overlay. -- `src/world/World.tsx` composes the active 3D scene. -- `src/world/Map.tsx` loads and centers the blocking map model. -- `src/world/Lighting.tsx` owns the current ambient and directional light setup. -- `src/world/Environment.tsx` owns the current background color. -- `src/world/player/FPSController.tsx` provides the current player camera, pointer lock, and `ZQSD` movement. -- `src/utils/debug/` contains debug-only tooling such as `lil-gui`, scene helpers, and the free debug camera. -- `src/components/ui/Crosshair.tsx` is the only current HTML overlay component in use. +- `src/App.tsx` mounts the `Canvas`, the 3D `World`, the debug perf overlay, and the HTML overlays. +- `src/world/World.tsx` composes the active scene, including: + - environment and lighting + - debug helpers and debug camera mode + - either the map scene or the debug physics test scene + - the player rig when the active camera mode is `player` +- `src/world/Map.tsx` loads the main map model and builds the collision octree. +- `src/world/debug/TestScene.tsx` provides a debug-oriented interaction and physics scene. +- `src/world/player/PlayerComponent.tsx` mounts the camera and controller. +- `src/world/player/PlayerController.tsx` owns pointer lock movement, jump handling, and interaction input. -## Camera Modes +## Interaction Model -The application currently has two camera modes: +- `src/stateManager/InteractionManager.ts` is the current interaction state source. +- `src/components/3d/InteractableObject.tsx` handles focus detection through distance and raycasting. +- `src/components/3d/TriggerObject.tsx` implements trigger-style interactions. +- `src/components/3d/GrabbableObject.tsx` implements hold-and-release interactions. +- `src/hooks/useInteraction.ts` exposes the interaction snapshot to React UI. +- `src/components/ui/InteractPrompt.tsx` shows the `E` prompt for trigger interactions. -- `player` - - controlled by `FPSController` - - player height is `1.75m` - - movement uses `ZQSD` - - `E` is reserved for future interaction -- `debug` - - controlled by `DebugCameraControls` - - enabled from the debug panel +## Audio -The active mode is stored in the debug subsystem and consumed through `src/hooks/debug/useCameraMode.ts`. +- `src/stateManager/AudioManager.ts` currently provides pooled one-shot sound playback. +- Trigger interactions may play audio directly through `AudioManager`. ## Debug System -- `src/utils/debug/Debug.ts` is a singleton wrapper around `lil-gui` -- `src/utils/debug/DebugPerf.tsx` lazy-loads `r3f-perf` -- `src/utils/debug/scene/DebugHelpers.tsx` mounts grid and axes in debug mode -- `src/utils/debug/scene/DebugCameraControls.tsx` mounts the free camera in debug mode +- Debug mode is enabled with `?debug`. +- `src/utils/debug/Debug.ts` owns the `lil-gui` instance and debug controls. +- `src/hooks/debug/useCameraMode.ts` and `src/hooks/debug/useSceneMode.ts` subscribe to debug state. +- `src/utils/debug/DebugPerf.tsx` lazily mounts `r3f-perf` in debug mode. +- `src/utils/debug/scene/DebugHelpers.tsx` mounts debug helpers. +- `src/utils/debug/scene/DebugCameraControls.tsx` mounts the free debug camera. ## Current Limitations -- There is no gameplay state manager implemented yet. -- There are no zone systems, missions, dialogue systems, or cinematic systems implemented yet. -- Player movement currently uses a simple height clamp instead of real collision or ground detection. -- The map is currently a blocking preview scene, not a full playable world. +- The repository is still a prototype, not the full intended game runtime. +- `src/world/debug/TestScene.tsx` is still part of the active scene composition. +- There is no central gameplay orchestrator such as `GameManager` yet. +- Missions, zones, cinematics, and dialogue systems are not implemented. +- The player uses octree collision and simple movement rules, not a complete gameplay physics stack. diff --git a/docs/technical/target-architecture.md b/docs/technical/target-architecture.md index 38b1905..9ad9c0e 100644 --- a/docs/technical/target-architecture.md +++ b/docs/technical/target-architecture.md @@ -2,60 +2,69 @@ This document describes the intended medium-term architecture for the project. +## Relationship To The Current Code + +- `docs/technical/architecture.md` is the source of truth for what exists now. +- This document is intentionally aspirational. +- If this document conflicts with the current implementation, the current implementation wins. + ## Goals -- Keep `main` stable, `develop` as the integration branch, and `feat/*` for feature work. -- Keep the runtime split between scene composition, gameplay systems, debug tooling, and HTML UI. +- Keep `App.tsx` small and orchestration-oriented. +- Separate production world code from debug-only runtime paths. - Keep one clear source of truth per concern. +- Grow gameplay systems incrementally instead of prebuilding empty architecture. ## Intended Layers ### App Layer -- `App.tsx` should stay small and orchestration-oriented. -- It should mount the canvas scene and top-level HTML overlays. +- `App.tsx` mounts the canvas scene and top-level HTML overlays. +- It should stay thin and avoid gameplay logic. ### World Layer -- `src/world/` should contain only production scene objects and scene composition. +- `src/world/` should contain production scene composition and production scene objects. - Expected responsibilities: - world composition - - map/environment/lighting + - map, environment, lighting - player controller - - zones - - post-processing used in production + - production interaction anchors + - production post-processing, if needed ### Debug Layer -- `src/utils/debug/` should contain only developer tooling. +- Debug-only scenes and tooling should be isolated from the production world path. - Expected responsibilities: - `lil-gui` - performance overlay - scene helpers - free camera and calibration controls + - temporary test scenes used during development ### UI Layer -- `src/components/ui/` should contain HTML overlays used by the player. -- Expected examples: +- `src/components/ui/` should contain player-facing HTML overlays. +- Expected future examples: - crosshair - - loading screen + - loading flow - mission HUD - narrative overlays ### Gameplay Layer -- Gameplay state should eventually live in dedicated managers and thin hooks once those systems exist. -- Expected future concerns: +- As the project grows, gameplay state can move toward a clearer orchestration layer. +- Likely future concerns: - missions - zones - cinematics + - dialogue - audio - interactions ## Rules -- `world/` should not contain debug-only tooling. -- `debug/` should not own production gameplay systems. -- Shared types should live close to their domain and move outward only when they gain multiple real consumers. -- New files should only be created when they have an active runtime purpose. +- Prefer direct, working code over speculative scaffolding. +- Shared types should stay close to their domain until they have multiple real consumers. +- Avoid creating new managers or service layers without an active runtime need. +- Debug-only runtime paths should be clearly marked and easy to remove later. diff --git a/docs/user/features.md b/docs/user/features.md index 8334c37..133c126 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -1,44 +1,49 @@ # Implemented Features -This document lists features that are actually implemented in the current codebase. +This document lists features that are implemented in the current codebase. -## Scene Preview +## Scene - Fullscreen React Three Fiber scene -- Blocking map loaded from `public/models/map/blocking/model.glb` +- Main map scene loaded from `public/models/map/model.gltf` +- Debug physics test scene selectable from the debug panel - Ambient and directional lighting -- Solid background environment color +- Environment background setup -## Camera Modes +## Player - Player camera mode - - eye height at `1.75m` - - pointer lock mouse look - - movement with `ZQSD` - - vertical clamp to prevent falling below the map plane -- Debug camera mode - - free orbit camera - - switchable from the debug panel +- Pointer lock mouse look +- Movement with `ZQSD` +- Jumping +- Octree-based collision against the loaded map -## UI +## Interactions -- Center-screen crosshair shown only in player mode +- Focus detection by distance and raycast +- Trigger interactions activated with `E` +- Grab interactions activated with the primary mouse button +- Interaction prompt shown for trigger interactions + +## Audio + +- One-shot sound playback for trigger interactions +- Simple per-sound pooling through `AudioManager` ## Debug Tooling - `?debug` query param enables the debug panel -- `lil-gui` panel with camera mode selection -- debug lighting controls -- debug scene helpers +- `lil-gui` controls for camera mode, scene mode, and interaction spheres +- Debug scene helpers +- Free debug camera - `r3f-perf` overlay ## Not Implemented Yet -- missions -- interactions on `E` -- gameplay zones -- cinematics -- audio systems +- mission system +- zone system +- cinematic system +- dialogue system - loading flow - minimap and mission HUD -- collisions beyond the current simple player height clamp +- full production separation between gameplay and debug scenes diff --git a/src/components/3d/InteractableObject.tsx b/src/components/3d/InteractableObject.tsx index 216e51e..7db8034 100644 --- a/src/components/3d/InteractableObject.tsx +++ b/src/components/3d/InteractableObject.tsx @@ -1,7 +1,8 @@ -import { useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import type { RapierRigidBody } from "@react-three/rapier"; import * as THREE from "three"; +import type GUI from "lil-gui"; import type { RefObject } from "react"; import { INTERACTION_DEBUG_SPHERE_COLOR, @@ -13,54 +14,83 @@ import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; import { InteractionManager } from "@/stateManager/InteractionManager"; import { INTERACTION_RADIUS } from "@/data/interactionConfig"; import type { Vector3Tuple } from "@/types/3d"; -import type { InteractableHandle, InteractableKind } from "@/types/interaction"; +import type { + GrabInteractableHandle, + InteractableHandle, + TriggerInteractableHandle, +} from "@/types/interaction"; -interface InteractableObjectProps { - kind: InteractableKind; +interface InteractableObjectBaseProps { label: string; position: Vector3Tuple; bodyRef?: RefObject; onPress: () => void; - onRelease?: () => void; children: React.ReactNode; } +interface TriggerInteractableObjectProps extends InteractableObjectBaseProps { + kind: "trigger"; +} + +interface GrabInteractableObjectProps extends InteractableObjectBaseProps { + kind: "grab"; + onRelease: () => void; +} + +type InteractableObjectProps = + | TriggerInteractableObjectProps + | GrabInteractableObjectProps; + const _cameraPos = new THREE.Vector3(); const _cameraDir = new THREE.Vector3(); const _objectPos = new THREE.Vector3(); const _raycaster = new THREE.Raycaster(); -export function InteractableObject({ - kind, - label, - position, - bodyRef, - onPress, - onRelease = () => {}, - children, -}: InteractableObjectProps): React.JSX.Element { +export function InteractableObject( + props: InteractableObjectProps, +): React.JSX.Element { + const { kind, label, position, bodyRef, onPress, children } = props; const camera = useThree((state) => state.camera); const groupRef = useRef(null); const debugSphereRef = useRef(null); - const handle = useRef({ - kind, - label, - onPress, - onRelease, - }); + const handle = useRef( + props.kind === "grab" + ? { kind: props.kind, label, onPress, onRelease: props.onRelease } + : { kind: props.kind, label, onPress }, + ); useEffect(() => { - handle.current.onPress = onPress; - handle.current.onRelease = onRelease; - }); + if (props.kind === "grab") { + const current = handle.current as GrabInteractableHandle; + current.label = label; + current.onPress = onPress; + current.onRelease = props.onRelease; + return; + } - useDebugFolder("Interaction", (folder) => { + return undefined; + }, [label, onPress, props]); + + useEffect(() => { + if (kind === "grab") { + return undefined; + } + + const current = handle.current as TriggerInteractableHandle; + current.label = label; + current.onPress = onPress; + return undefined; + }, [kind, label, onPress]); + + const setupInteractionDebugFolder = useCallback((folder: GUI) => { folder .add({ radius: INTERACTION_RADIUS }, "radius") .name("Interaction radius") .disable(); - }); + }, []); + + useDebugFolder("Interaction", setupInteractionDebugFolder); useFrame(() => { const group = groupRef.current; diff --git a/src/stateManager/AudioManager.ts b/src/stateManager/AudioManager.ts index 31c014d..1fcc256 100644 --- a/src/stateManager/AudioManager.ts +++ b/src/stateManager/AudioManager.ts @@ -35,7 +35,7 @@ export class AudioManager { logger.error("AudioManager", "Failed to play sound", { path, - error, + error: AudioManager._toLogValue(error), }); }); } @@ -66,11 +66,20 @@ export class AudioManager { return pooledAudio; } - return existingPool[0]!; + const recycledAudio = existingPool[0]; + if (recycledAudio) return recycledAudio; } const initialAudio = new Audio(path); this._audioPools.set(path, [initialAudio]); return initialAudio; } + + private static _toLogValue(error: unknown): Error | DOMException | string { + if (error instanceof Error || error instanceof DOMException) { + return error; + } + + return String(error); + } } diff --git a/src/stateManager/InteractionManager.ts b/src/stateManager/InteractionManager.ts index abc1882..6dcdd45 100644 --- a/src/stateManager/InteractionManager.ts +++ b/src/stateManager/InteractionManager.ts @@ -1,4 +1,5 @@ import type { + GrabInteractableHandle, InteractableHandle, InteractionSnapshot, } from "@/types/interaction"; @@ -8,7 +9,7 @@ export class InteractionManager { private _focused: InteractableHandle | null = null; private _holding = false; - private _holdingHandle: InteractableHandle | null = null; + private _holdingHandle: GrabInteractableHandle | null = null; private _snapshot: InteractionSnapshot = { focused: null, holding: false, @@ -40,8 +41,14 @@ export class InteractionManager { pressInteract(): void { if (!this._focused) return; - this._holding = this._focused.kind === "grab"; - if (this._holding) this._holdingHandle = this._focused; + if (this._focused.kind === "grab") { + this._holding = true; + this._holdingHandle = this._focused; + } else { + this._holding = false; + this._holdingHandle = null; + } + this._focused.onPress(); this._emit(); } diff --git a/src/types/interaction.ts b/src/types/interaction.ts index 2353021..986bed9 100644 --- a/src/types/interaction.ts +++ b/src/types/interaction.ts @@ -1,12 +1,22 @@ export type InteractableKind = "grab" | "trigger"; -export interface InteractableHandle { - kind: InteractableKind; +export interface TriggerInteractableHandle { + kind: "trigger"; + label: string; + onPress: () => void; +} + +export interface GrabInteractableHandle { + kind: "grab"; label: string; onPress: () => void; onRelease: () => void; } +export type InteractableHandle = + | TriggerInteractableHandle + | GrabInteractableHandle; + export interface InteractionSnapshot { focused: InteractableHandle | null; holding: boolean; diff --git a/src/types/logger.ts b/src/types/logger.ts index a0cbd0c..3eb022a 100644 --- a/src/types/logger.ts +++ b/src/types/logger.ts @@ -1,6 +1,17 @@ export type LogLevel = "debug" | "info" | "warn" | "error"; -export type LogContext = Record; +export type LogValue = + | string + | number + | boolean + | null + | undefined + | Error + | DOMException + | { [key: string]: LogValue } + | LogValue[]; + +export type LogContext = Readonly>; export interface LogEntry { timestamp: string; diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts index 4860a23..2c97d30 100644 --- a/src/utils/debug/Debug.ts +++ b/src/utils/debug/Debug.ts @@ -1,5 +1,6 @@ import GUI from "lil-gui"; import type { CameraMode, SceneMode } from "@/types/debug"; +import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; export class Debug { private static instance: Debug | null = null; @@ -28,7 +29,7 @@ export class Debug { } private constructor() { - this.active = new URLSearchParams(window.location.search).has("debug"); + this.active = isDebugEnabled(); this.gui = this.active ? new GUI({ title: "La-Fabrik Debug" }) : null; if (this.gui) { diff --git a/src/utils/debug/isDebugEnabled.ts b/src/utils/debug/isDebugEnabled.ts new file mode 100644 index 0000000..63aa610 --- /dev/null +++ b/src/utils/debug/isDebugEnabled.ts @@ -0,0 +1,7 @@ +export function isDebugEnabled(): boolean { + if (typeof window === "undefined") { + return false; + } + + return new URLSearchParams(window.location.search).has("debug"); +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 005d4fd..b1629de 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -4,6 +4,7 @@ import type { LogLevel, LoggerConfig, } from "@/types/logger"; +import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; const LEVEL_PRIORITY: Record = { debug: 10, @@ -102,9 +103,7 @@ function resolveMinLevel(): LogLevel { return "info"; } - const debugEnabled = new URLSearchParams(window.location.search).has("debug"); - - return debugEnabled ? "debug" : "info"; + return isDebugEnabled() ? "debug" : "info"; } export const logger = new Logger({ diff --git a/src/world/World.tsx b/src/world/World.tsx index e0d3eee..861a409 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from "react"; +import { useState } from "react"; import type { Octree } from "three/addons/math/Octree.js"; import { PLAYER_SPAWN_Y_GAME, @@ -18,7 +18,6 @@ export function World(): React.JSX.Element { const cameraMode = useCameraMode(); const sceneMode = useSceneMode(); const [octree, setOctree] = useState(null); - const onOctreeReady = useCallback((o: Octree) => setOctree(o), []); return ( <> @@ -28,9 +27,9 @@ export function World(): React.JSX.Element { {cameraMode === "debug" ? : null} {sceneMode === "game" ? ( - + ) : ( - + )} {cameraMode !== "debug" ? ( diff --git a/src/world/player/PlayerComponent.tsx b/src/world/player/PlayerComponent.tsx index 3a64820..bcf4a22 100644 --- a/src/world/player/PlayerComponent.tsx +++ b/src/world/player/PlayerComponent.tsx @@ -6,13 +6,13 @@ import { PlayerCamera } from "@/world/player/PlayerCamera"; import { PlayerController } from "@/world/player/PlayerController"; interface PlayerComponentProps { - octree?: Octree | null; + octree: Octree | null; spawnY: number; } export function PlayerComponent({ - octree = null, spawnY, + octree, }: PlayerComponentProps): React.JSX.Element { const camera = useThree((state) => state.camera); diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 3192fce..6059cbc 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -123,11 +123,6 @@ export function PlayerController({ octree }: PlayerControllerProps): null { case MOVE_RIGHT_KEY: keys.current.right = false; break; - case INTERACT_KEY: - if (interaction.getState().focused?.kind === "trigger") { - interaction.releaseInteract(); - } - break; default: return; } diff --git a/vite.config.ts b/vite.config.ts index d779ded..ad58148 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import path from "node:path"; -// https://vite.dev/config/ export default defineConfig({ plugins: [react()], resolve: {