A 3D browser game built with Three.js and TypeScript where the player controls a winged sea otter exploring a procedurally generated world that transitions from realistic terrain to surreal landscapes at its edges.
npm run dev # Start Vite dev server at http://localhost:5173
npm run build # Type-check (tsc) + production build
npm run preview # Preview production build- Three.js (r170) - 3D rendering, all geometry is procedural (no external models/assets)
- TypeScript - strict mode enabled
- Vite - bundler, ES2020 target
- simplex-noise - terrain and vegetation placement
Entry point: src/main.ts instantiates Game. The Game class (src/game.ts) owns the render loop, scene graph, and all subsystems.
src/
├── main.ts # Entry point
├── game.ts # Game loop, scene setup, system orchestration
├── character/
│ ├── otter.ts # Procedural otter mesh (createOtter())
│ ├── otter-controller.ts # Movement, state machine, animation
│ ├── wings.ts # Wing mesh + flap animation
│ └── rockets.ts # Rocket projectile system
├── camera/
│ ├── camera-system.ts # Camera mode manager (toggle V key)
│ ├── third-person.ts # Orbit camera with mouse/scroll control
│ └── first-person.ts # FPS camera attached to otter head
├── controls/
│ └── input-manager.ts # Keyboard, mouse, touch input handling
├── world/
│ ├── terrain.ts # Height function, vertex coloring, chunk mesh creation
│ ├── chunk-manager.ts # Chunk generation/visibility management
│ ├── water.ts # Water plane with gentle bob animation
│ ├── vegetation.ts # Instanced trees, bushes, rocks, grass
│ ├── surreal-zone.ts # Floating shapes + crystals in outer zones
│ ├── cactus-border.ts # Cactus ring boundary + isOutsideBorder()
│ └── skybox.ts # Procedural sky shader with sun glow
├── ui/
│ ├── loading.ts # Loading screen progress bar
│ ├── game-over.ts # Game over overlay + play again button
│ └── hud.ts # Controls hint overlay
└── utils/
├── noise.ts # octaveNoise(), ridgedNoise() wrappers
└── math-helpers.ts # lerp, clamp, smoothstep, inverseLerp
Game Loop (game.ts): Uses requestAnimationFrame with delta time capped (~80ms). Has a phased loading sequence (terrain batches -> vegetation -> surreal zone -> cacti -> ready) before entering the main update loop.
Terrain (world/terrain.ts): 2048x2048 world divided into 64x64 chunks. Height is simplex noise with octaves; center is flattened for spawn. getSurrealFactor(x, z) returns 0-1 based on distance from center, used to blend realistic/surreal colors and control object placement. Runtime movement/AI use getTerrainHeightCached() which samples a cached heightfield built during chunk generation.
Otter State Machine (character/otter-controller.ts): States are IDLE | WALK | FLY | FALL | GAME_OVER. Movement is camera-relative. Flying ascends/descends with Space/Shift. Crossing the cactus border triggers FALL -> GAME_OVER after 1.5s.
Input (controls/input-manager.ts): Keyboard (WASD/arrows + Space/Shift/V/B), mouse look/orbit via pointer lock (click canvas to lock), scroll zoom, and touch controls (virtual joystick + buttons). Uses consume pattern for one-shot events (camera toggle, rocket shoot).
Instanced Rendering: Vegetation, surreal objects, and cacti all use THREE.InstancedMesh with per-instance colors for performance.
Procedural Sky (world/skybox.ts): Custom ShaderMaterial on an inverted sphere with gradient + sun glow effect.
| Key | Action |
|---|---|
| WASD / Arrows | Move |
| Space (hold) | Fly / ascend |
| Shift (hold) | Descend while flying |
| V | Toggle third-person / first-person camera |
| B | Shoot rocket |
| Mouse | Orbit camera (3rd person) / Look (1st person) |
| Scroll | Zoom in/out (3rd person) |
Touch controls are shown on pointer: coarse devices.
- All 3D models are procedural geometry (no .glb/.gltf files)
- Classes expose a
groupormeshproperty for scene graph attachment - Subsystems are updated from the game loop with
update(dt)orupdate(time) - UI elements are defined in
index.htmlwith CSS, manipulated via DOM insrc/ui/ - Flat shading (
flatShading: true) is used throughout for a low-poly aesthetic - Physics is simple custom code (no physics engine): gravity, terrain snapping, velocity lerping
The world has concentric zones controlled by getSurrealFactor():
- Center (0-40%): Flat spawn area with realistic grass/dirt/rock terrain
- Mid (40-60%): Transition zone, colors begin shifting
- Outer (60-90%): Surreal zone with floating shapes, crystals, mushroom trees, vibrant colors
- Border (~92%): Cactus ring boundary; crossing it triggers fall + game over
| Constant | File | Value | Purpose |
|---|---|---|---|
WORLD_SIZE |
terrain.ts | 2048 | Total world extent |
CHUNK_SIZE |
terrain.ts | 64 | Terrain chunk dimensions |
BORDER_RADIUS |
cactus-border.ts | ~942 | Cactus ring radius |
VIEW_DISTANCE |
chunk-manager.ts | 500 | Chunk visibility range |
WALK_SPEED |
otter-controller.ts | 8 | Ground movement speed |
FLY_SPEED |
otter-controller.ts | 15 | Flying movement speed |
MAX_ROCKETS |
rockets.ts | 20 | Max simultaneous rockets |