{
  "name": "3d-world",
  "title": "3D World - 3D Multiplayer Games",
  "description": "Build 3D World games (3D multiplayer): the playable app, genre recipes (obby, tycoon, simulator, PvP, shooter, horror, racing), features, multiplayer patterns, persistence",
  "guid": "sk_plat_game",
  "category": "Templates",
  "requiredTools": [
    "add",
    "realtime_room",
    "file_write",
    "project_deploy"
  ],
  "content": "# 3D World - 3D Multiplayer Starter\n\n**3D World** is a playable app on Gipity - a multiplayer rocket-launcher demo built on the `3d-engine` template. Use it when you want a working reference or a fun playground. All 3D World games share the same visual style, physics, and multiplayer backend.\n\n**Read [3d-engine](3d-engine.md) for the engine API.** 3D World *is* the 3d-engine template plus a demo, so every call you'll make - Parts, physics, constraints, workspace, player, camera modes, assets, UI/HUD - is documented once, there. This skill covers only what the starter adds on top: the demo it ships, the rocket-launcher feature, genre recipes, multiplayer patterns, and persistence.\n\n**When to use this:** When the user asks for a 3D World game, a playable 3D reference, or a multiplayer shooter. For a fresh build without rocket-launcher / demo-scene content to strip out, add `3d-engine` instead and build your own features. For 2D games (platformer, puzzle, arcade) add `2d-game`. For non-game web apps (wordle, quiz, card games), use `web-simple` or `web-fullstack`.\n\n## Quick Start - Start Here\n\n**STRONGLY RECOMMENDED:** Begin every 3D World game by adding the `3d-world` template with `add`. It sets up Three.js, Rapier physics, Gipity Realtime multiplayer, player controls, and the full engine layer for you. Only hand-roll files if the user explicitly tells you to skip the template.\n\n```\nadd name=3d-world title=\"<Game Name>\"\n```\n\n**Starting over in an existing project:** If `src/` already exists and the user wants a clean rebuild, delete `src/` first, then run `gipity add` normally. Or pass `--force` to `gipity add` to overwrite in one step - destructive, so confirm with the user first. Unrelated content (media, data, notes) is preserved either way.\n\n**Naming:** Use the user's name verbatim if given. If they didn't specify, blend \"Gip\" or \"Gipity\" into the name (e.g. \"Gipity World\", \"GipCraft\") - be creative but don't force it.\n\nThis creates a playable game immediately - ground, player character, physics, camera, mobile controls. Then edit `config.js` and `game.js` to build your game.\n\n## What the app ships\n\nAll files are in `src/` and fully editable. The engine layer is identical to `3d-engine` (see its \"Project Structure\"). What 3D World adds on top:\n\n- `game.js` - main orchestrator, already wired to the demo. **Start here.**\n- `scene.js` - the demo scene (voxel structures in two rings). Replace with your own world.\n- `config.js` - project metadata + `features` flags. `settings.js` - tunable gameplay values. `strings.js` - display text.\n- A `rocket-launcher` feature (projectile + explosion + audio) and block-collision tick sounds.\n- A player controller wired with an orbit + aim camera.\n\nRead the files before changing them - each opens with a header comment listing its exports and invariants.\n\nPress **`** (backtick) or **U** to toggle the built-in debug panel: FPS, version line, and all `console.log`/`warn`/`error` output. Log to it from game code with `ui.debug('message')`.\n\n## Features (Opt-in Gameplay Modules)\n\nEnable built-in gameplay features via `config.features`. Features are template-level modules that auto-initialize during boot.\n\n```js\n// config.js\nexport const config = {\n  title: 'My Game',\n  features: {\n    'rocket-launcher': true,  // enable with defaults\n  },\n};\n```\n\nWith custom settings:\n\n```js\nfeatures: {\n  'rocket-launcher': {\n    speed: 200,       // projectile speed (default: 120)\n    cooldown: 1.0,    // seconds between shots (default: 0.15)\n    blastRadius: 5,   // explosion radius (default: 10)\n    blastForce: 60,   // knockback strength (default: 40)\n    maxDistance: 300, // max range (default: 150)\n    size: 3.0,        // rocket model scale (default: 2.0)\n  },\n}\n```\n\nHook a feature's events from `game.js`:\n\n```js\nimport { features } from './core.js';\n\nonInit(() => {\n  const rl = features.get('rocket-launcher');\n  if (rl) {\n    rl.onHit((pos) => { /* rocket hit something at pos */ });\n    rl.onExplode((pos) => { /* explosion at pos */ });\n    rl.onFire((origin, dir) => { /* rocket fired */ });\n  }\n});\n```\n\n| Feature | Key | Description |\n|---------|-----|-------------|\n| Multiplayer | `multiplayer` | `@gipity/realtime` transport + remote-player avatars rendered automatically. Optional host-authoritative world-state sync via `sync.worldState: true`. Disable with `'multiplayer': false` for solo games. |\n| Rocket Launcher | `rocket-launcher` | Projectile weapon with physics explosions. Left-click to fire, B for debug traces. |\n\nTo write your own feature, see \"Features\" in [3d-engine](3d-engine.md).\n\n## Genre Recipes\n\n### Obby / Parkour\n- Platforms at varying heights with anchored Parts\n- Checkpoints as triggers (save spawn point)\n- Kill zones below platforms (trigger → respawn at last checkpoint)\n- Timer in HUD (top-right)\n- Finish trigger → show completion time\n\n### Tycoon\n- Resource nodes (triggers that give currency on proximity)\n- Shop system (`ui.setHud` for buy menu)\n- Upgrades stored in game state\n- Auto-generation timer\n- Persist progress with App API functions (below)\n\n### Simulator (Collect & Sell)\n- Collectibles scattered as voxel boxes with triggers\n- Inventory count in HUD\n- Sell zone (trigger → convert items to currency)\n- Upgrade tiers (speed, capacity, multiplier)\n- Leaderboard via App API\n\n### PvP Combat\n- Health bar in HUD\n- Weapon hitbox via raycast (`physics.castRay`)\n- Damage events via a `network.channel('combat', { sync: 'messages' })` channel\n- Respawn timer + invincibility frames\n- Score tracking\n\n### Shooter (FPS/TPS)\n- Camera mode: `player.cameraControl.mode = 'firstPerson'`\n- Crosshair in HUD center\n- Projectile: spawn small Part, apply impulse, raycast for hit detection\n- Ammo count in HUD\n- Network: broadcast shots, validate hits server-side\n\n### Tower Defense\n- Path defined as waypoint array\n- Enemy spawner on interval\n- Tower placement on grid (snap to voxel)\n- Projectile system (tower → nearest enemy)\n- Wave counter + health in HUD\n\n### Horror\n- Override world lighting: dim the sun (`workspace.lighting.timeOfDay`), pull fog closer\n- Flashlight: spotlight attached to camera\n- Jump scare: trigger zones that play sounds + show images\n- Inventory: key-item tracking\n- Narrative: text messages via `ui.showMessage`\n\n### Racing\n- Checkpoints as triggers around a track\n- Lap counter + timer in HUD\n- Speed boost zones (triggers that increase velocity)\n- Vehicle: replace player model, adjust move speed\n- Multiplayer: position sync shows other racers\n\n## Multiplayer Patterns\n\n### Custom game events\nOpen a `messages` channel and send/receive typed events. Each channel namespaces its own wire types, so use as many as you like:\n\n```js\nimport { network } from './core.js';\n\nconst events = network.channel('events', { sync: 'messages' });\nevents.send('item_collected', { itemId: 'coin-3', points: 10 });\nevents.on('item_collected', (data) => {\n  removeItem(data.itemId);\n  updateScore(data.points);\n});\n```\n\n### Remote players\nThe `multiplayer` feature already renders remote-player avatars, driven by the `network.avatars` presence channel. To react to joins/leaves yourself:\n\n```js\nnetwork.avatars.onJoin((sid) => console.log('joined', sid));\nnetwork.avatars.onLeave((sid) => console.log('left', sid));\nfor (const [sid, peer] of network.avatars.peers()) {\n  // peer.position {x,y,z}, peer.rotation (y radians)\n}\n```\n\n### Solo, basic, and authoritative world state\n\nMultiplayer is a feature like rocket-launcher - flip it on or off in `config.js`:\n\n```js\nfeatures: { 'multiplayer': false }                                   // solo game\nfeatures: { 'multiplayer': { room: 'my-arena' } }                    // default: presence + avatars\nfeatures: { 'multiplayer': { room: 'lobby', sync: { worldState: true } } }  // host-authoritative world\n```\n\nSolo is that one line and nothing else: **leave `gipity.yaml` as shipped.** Its `realtime` phase only registers a room name (no server, no cost, no plan limit) and an unconnected room does nothing, so there is no manifest to strip out.\n\nUse `sync.worldState` when every client must see the same blocks/objects in the same place - a shared sandbox or a destructible level. The feature creates a host-authoritative `world` entities channel; the 3D adapter (`js/network/adapter-3d.js`) already knows how to serialize and apply Parts, so there's no game-side registration. The first client to join becomes host (3-phase claim election); if the host drops, another takes over automatically (a client holding world data is promoted instantly, else alphabetical tiebreaker).\n\nThe room is declared in the template's `gipity.yaml` (a `realtime` deploy phase), so `gipity deploy` provisions it - no separate step. To sync your own non-Part state, open an `entities` channel and supply an adapter - see `packages/realtime/contracts/adapter.contract.md` and the worked `examples/`. Room management and advanced config: the `app-realtime` skill.\n\n## Persistence\n\nUse App API functions to save/load player data:\n\n```js\n// functions/save-progress.js\nexport default async function (ctx, { db }) {\n  const { data } = ctx.body;\n  await db.execute(\n    'INSERT INTO saves (user_guid, data) VALUES ($1, $2) ON CONFLICT (user_guid) DO UPDATE SET data = $2',\n    [ctx.auth.userGuid, data]\n  );\n  return { ok: true };\n}\n\n// functions/load-progress.js\nexport default async function (ctx, { db }) {\n  const row = await db.findOne('saves', { user_guid: ctx.auth.userGuid });\n  return { data: row?.data ?? null };\n}\n```\n\nDeclare in `gipity.yaml`:\n\n```yaml\nfunctions:\n  save-progress:\n    auth_level: user\n    tables: [saves]\n  load-progress:\n    auth_level: user\n    tables: [saves]\n```\n\n## Mobile and Performance\n\nTouch controls are automatic - floating joystick, Jump/Action buttons, camera drag, fullscreen toggle (details in [3d-engine](3d-engine.md)). To stay playable on both inputs, read movement from `player.inputState` and trigger actions from `inputState.action` / `inputState.jump` rather than raw key events.\n\nTyping into form fields (leaderboard name entry, chat) is already shielded: the template's key handlers ignore events targeting an `input`/`textarea`/`select`/contenteditable and clear held keys on focus, so W/A/S/D/Space type normally without moving the avatar. If you add your OWN global key listener, start it with `if (e.target.closest('input, textarea, select') || e.target.isContentEditable) return;`.\n\n- Use `assets.createVoxelGround()` for terrain - it's an InstancedMesh (one draw call for the whole ground)\n- For many identical objects, use `THREE.InstancedMesh` instead of individual meshes\n- Keep total triangle count under 100K for mobile\n- Limit shadow-casting objects (player + key objects only)\n- Use fog to hide pop-in at draw distance\n\n## Deploy Verification\n\nVerify a deploy when it matters - the first deploy, structural changes (new pages, new frameworks, changed imports), or anything that might have broken. Skip it for trivial changes (copy tweaks, style values).\n\n`gipity deploy dev --inspect` deploys and reports the live page in one step: console errors, failed resources, timing, layout overflow. A clean console is necessary but NOT sufficient for Canvas/WebGL - also capture `gipity page screenshot <url>` and look at it, because render failures are silent. A blank page, black canvas, or wrong-looking UI with a clean console is a real failure, not a pass.\n\nFull loop - reading function logs, calling a function directly, driving the page: the `app-debugging` skill.\n\n**The version line** `[3D World] Game Title v1.0 (build 2026-...)` appears in the console on every boot - use it to confirm the correct build is deployed.\n\n## Related Skills\n\n- **3d-engine** - the engine API this app is built on: Parts, physics, constraints, workspace, player, camera, assets, UI. **Read it first.**\n- **app-debugging** - verifying a 3D app headlessly: `advance(seconds)` instead of wall-clock waits, `page inspect`, screenshots\n- **app-development** - functions, database & API for persistence and leaderboards\n- **app-realtime** - advanced Gipity Realtime room configuration\n- **app-auth** - Sign in with Gipity for user identity\n- **app-llm** - AI-powered NPCs using the LLM service\n"
}
