--- name: ngin description: > Build a browser game on NGIN, a code-first Three.js 0.186 WebGPU engine with Jolt physics, an R6 character, vehicles, boats, water, carry/inventory, particles and weather already written. Use when asked to make or change a game in an NGIN checkout: walking or driving worlds, Roblox/Minecraft-style levels, props with colliders, day/night and weather. The engine source under src/ is the reference. If this file and the source disagree, the source wins. --- # NGIN Static HTML and browser ES modules. No bundler, no npm, no framework, no build step. Three.js 0.186 (`three.webgpu.js`, `three.tsl.js`, addons) and Jolt 1.1.0 come from a CDN through the page's import map. `WebGPURenderer` only. Serve the NGIN folder with `php -S localhost:8080`; `file://` will not load the modules. Vanilla JS in functional style: factory functions and closures, no classes. Simple geometry, shared materials, 16 px procedural textures. Target responsive Roblox/Minecraft-style play, not photorealism. ## Where you may write One game lives in one folder: `games//`. That folder is the only writable place. Everything else is read-only engine, demo, starter, shared assets, docs, tests and tooling. Read them to learn the APIs. Do not edit, move, format, monkey-patch or fork them into the game. A missing engine capability is a separate maintenance request, not permission to change `src/`. Start a game by copying the complete `games/template/` folder to `games//` and keeping its page as is: the ``, the pinned import map, and the entry script that resolves `./scripts/main.js` against `location.href`. Import engine modules from `scripts/game.js` with `../../../src/...`. Game-local assets use `new URL('../assets/x.glb', import.meta.url).href`. Before editing, run `node tools/game-boundary.mjs snapshot ` from the NGIN root and keep the printed baseline path. After editing, run `node tools/game-boundary.mjs check `. Report violations; never regenerate the baseline to hide one. ## Boot order The starter already does this in `scripts/game.js`. Keep the order. ```js const engine = await createEngine(canvas); const input = createInputManager(canvas, engine); input.setLookMode(settings.lookMode); // 'drag' or 'capture' const physics = await createPhysicsWorld(engine); const environment = createEnvironment(engine); const materials = createMaterialLibrary(); const character = createCharacterModel(); environment.load('sunny'); // default-overcast, industrial, sunny, dusk, night // ...build the world, every solid mesh gets a collider... const player = createPlayer(engine, physics, input, settings, character); engine.resize(canvas.clientWidth, canvas.clientHeight); engine.onUpdate((dt) => { /* game rules */ }); await engine.start(); ``` `settings` is a plain object the player reads live: `alwaysRun`, `invertMouse`, `viewMode` ('firstPerson' | 'thirdPerson'), `lookMode`, `fov`, `mouseSensitivity`, `thirdPersonDistance`, `thirdPersonHeight`. Walking, running, crouching, jumping, mouse look and physics stepping are the player's job. Do not write locomotion. ## Engine API - `engine`: `scene`, `camera`, `renderer`, `add(obj)`, `remove(obj)`, `onUpdate(fn(dt))`, `onLateUpdate(fn)`, `resize(w, h)`, `setPixelRatio(n)`, `start()`, `stop()`, `register(name, service)`, `get(name)`. - `input`: `wasPressed(action)`, `isDown(action)`, `getMove()` (shared object, copy if kept), `setLookMode(mode)`. Actions: moveForward/Back/Left/Right, jump, sprint, crouch, interact (E), carry (F), stow (G), flashlight (L). - `physics`: `addStaticBox(id, {x,y,z}, [hx,hy,hz], mesh?, {x,y,z} rotation?)`, `addStaticConvexHull`, `addStaticMesh` (GLB), `addDynamicBox(id, pos, half, mesh, mass, extras)`, `addDynamicSphere(id, pos, r, mesh, mass, extras)`, `removeDynamic(id)`, `trackBody(entry)` for bodies made elsewhere, `addForceField({ origin, direction, reach, radius, acceleration })`, `addWaterVolume`, `castRay`, `getCharacterPosition()` (shared object), `setCharacterPosition(x, y, z)`, `halfHeight()`, `onPreStep(fn)`. `extras` for dynamics: `buoyancy`, `linearDamping`, `angularDamping`. - `player`: `setViewMode(mode)`, `toggleViewMode()`, `getPosition()` (shared object), `zoomOrbit(delta)`. - `materials`: `get(name)` solids: orange, teal, pushable, glass, glassDark, water, red, blue, yellow, white, darkGrey, chrome, rubber, taillight, headlight. `pack(name)` returns three shaded block materials: grass, dirt, stone, rock, wood, brick, grassBlock, rustyMetal. Use `createGrassBlockGeometry(unit)` from `ProceduralTextures.js` for grass blocks, `applyWorldUVs(geometry, tile, origin)` for world-tiled ground. - `environment`: `load(preset)`, `setTimeOfDay(0..1)`, `setCycle(bool)`. - `createWeather(engine)`: `setRain(bool)`, `setSnow(bool)`. - `createAO(engine)`: `setEnabled(bool)`, `warm(onProgress)`. Opaque-only SSAO. - `createVehicle(engine, physics, materials, player, input, { kind, id?, x, y, z, interaction })` with `kind` car, truck, bus, kenworth (tows a trailer), or `meshUrl` for a GLB. `createBoat(..., { kind: 'fishing' | 'speedboat', ... })`. Both return `{ warmLights(bool) }` plus seat handling. E enters and exits. - `createUse(engine, physics, input, player, { prompt, inventory })` gives the look-at prompt, carry and inventory. Pass `use.interaction` to vehicles and boats. `createOrangeCrates(engine, physics, materials, use).spawn(x, y, z, size, count)`. Interactables implement `getText()` / `getAltText()`. - `createFlashlight(engine, input, player, settings)`: L toggles it. - `createEffect(engine, type, { position, ... })` types: smoke, fire, sparks, dust, spray, wake, steam, and the rest listed in `src/graphics/ParticleEffects.js`. Returns `{ setEnabled, setRate, setPosition, burst }`. Unknown types throw. - World pieces that come with colliders: `createPoolInGround`, `createVoxelHill`, `createStalkerYard`, `createFirePit`, `createFountain`, `createTrampoline`, `createFan`, `createParticleGallery`, `createWalkWedge`, `createPushables`. - Props: `createAssetManager().loadGltf(url)` then `placeGltf(engine, physics, gltf, { id, x, z, targetHeight, body: 'static-mesh', batchStatic: true })`. Never add `gltf.scene` raw. ## Rules - Every solid mesh gets a matching collider. Floating meshes are a bug. - Keep the R6 character for the player unless told otherwise. - Game rules go in `engine.onUpdate`. Do not step physics yourself. - Shared materials and few pipelines: reuse `materials`, do not create a `MeshStandardNodeMaterial` per object. Instanced meshes for repeated blocks. - Objects returned as "shared" are reused every frame. Copy their fields if you keep them. - Do not stage, commit, push or branch. ## Done means The game boots at `http://localhost:8080/games//` without importing the sandbox, the player walks, jumps, lands and switches camera with V, solids collide, the page resizes through `engine.resize`, the boundary check passes with the original baseline, and `games/sandbox/` still launches.