{"slug": "claude-of-duty", "title": "Claude of Duty", "summary": "A first-person shooter built entirely with procedural generation in the browser using Three.js r180 and WebGL2 has been released, written by a fleet of AI agents under orchestration. The game contains roughly 55,000 lines of code across 11 subsystems, with no art assets — every texture, mesh, animation, and sound is generated at load time from code. The repository's harness tools, including a baseline capture system for bit-identical screenshots and a profiler that revealed median frame time hides actual performance issues, are highlighted as the most interesting part of the project.", "body_md": "Get updates [here](https://shumer.dev/newsletter).\n\nA first-person shooter built in the browser with Three.js r180 and WebGL2. Roughly 55k lines across 11 subsystems, written by a fleet of AI agents under orchestration.\n\n**There are no art assets.** Every texture, mesh, animation and sound is generated\nprocedurally at load time from code. No models, no HDRIs, no image files, no audio\nfiles. The only runtime dependency is `three`\n\n.\n\n```\nnpm install\nnpm run dev          # http://127.0.0.1:5173\n```\n\nClick the canvas to lock the cursor. WASD move, mouse aim, LMB fire, RMB ADS, R reload, Shift sprint, Ctrl crouch, Space jump, Q/E lean, Esc release.\n\n| subsystem | what it does |\n|---|---|\n`render` |\nHDR pipeline, cascaded shadow maps in a `sampler2DArray` with texel snapping and PCSS contact hardening, MRT depth/normal/velocity prepass, GTAO, TAA with YCoCg variance clipping, tile-dilated motion blur, Karis bloom pyramid, GPU EV100 metering, procedural 33³ grade LUT, AgX composite |\n`materials` |\nGPU texture forge: 19 procedural surfaces (concrete, brick, plaster, asphalt, sand, rusted/painted/brushed metal, wood, fabric, burlap, glass…), periodic noise so everything tiles seamlessly, Sobel height→normal, parallax occlusion mapping, triplanar projection, curvature-driven edge wear |\n`sky` |\nAtmospheric scattering, time of day, PMREM environment generation, volumetric fog and light shafts |\n`world` |\n~120×120 m market street: modular building kit with real wall thickness, enterable interiors, several hundred instanced props |\n`physics` |\nWritten from scratch, no library. Binned-SAH BVH (29k tris → 14k nodes in 22 ms, 0.25 µs/raycast), swept-capsule character controller with a 5-plane crease stack, impulse rigid bodies with CCD, PBD ragdolls, multi-layer bullet penetration |\n`player` |\nMovement state machine, slide/mantle/lean, camera feel |\n`weapons` |\nProcedural weapon geometry, viewmodel rig, ADS, spring recoil, procedural reloads, ballistics with travel time and drop |\n`fx` |\nGPU particles, decals, tracers, muzzle flash, explosions |\n`ai` |\nSkinned soldiers, navmesh pathing, perception, cover behaviour, ragdoll death |\n`ui` |\nDOM/CSS HUD: crosshair, hitmarkers, minimap, compass, killfeed |\n`audio` |\nWeb Audio synthesis — no sound files. Layered weapon fire, convolution reverb, HRTF spatialisation, occlusion |\n\n`ARCHITECTURE.md`\n\nis the contract the agents worked against: subsystem interface,\ndirectory ownership, the cross-subsystem event vocabulary, and shared surface types.\n\nThe interesting part of this repo is arguably the harness, not the game.\n\n| tool | purpose |\n|---|---|\n`tools/capture.mjs` |\nScreenshot one named shot via GPU-backed headless Chromium |\n`tools/shotset.mjs` |\nAll 11 shots in one session — fast review set |\n`tools/baseline.mjs` |\nReproducible capture: each shot in an isolated page, fixed frame budget. Bit-identical across runs |\n`tools/imagediff.mjs` |\nPer-pixel gate. Exits non-zero if any pixel moved |\n`tools/profile.mjs` |\nGameplay profiler at real device pixel ratio. Frame-time distribution and hitch attribution via per-frame WebGL program counts |\n`tools/playtest.mjs` |\nScripted movement/fire smoke test |\n\nTwo findings worth recording, because both invalidated earlier measurements:\n\n**Median frame time hides the actual problem.** A static-camera benchmark reported\n94 fps while the game was unplayable. Real gameplay at Retina DPR (internal 3.34 MP,\nnot 2.07) ran 12–17 fps with **728–1236 ms stalls** caused by 34+ WebGL programs\ncompiling lazily mid-frame. `profile.mjs`\n\nreports p50/p95/p99 and attributes each\nhitch, which is what surfaced it.\n\n**Captures were not reproducible.** `shotset.mjs`\n\nreuses one page across all 11\nshots, so particle age, decal buffers and exposure state leak forward — two identical\nruns differed on 10 of 11 shots. `baseline.mjs`\n\nisolates each shot in a fresh page,\nwhich is bit-identical and is what makes `imagediff.mjs`\n\na usable gate.\n\nMeasured on an Apple silicon laptop at 1512×982, DPR 2 (3.34 MP internal), `ultra`\n\npreset,\n3 runs, gameplay in motion with AI and firing active:\n\n| before optimization | after | |\n|---|---|---|\n| fps p50 | 12–17 | 28–30 |\n| fps p99 | 4–9 | 14–17 |\n| worst frame | 728–1236 ms | 66–82 ms |\n| shader compiles during play | 34–35 | 0 |\n| boot | ~9–12 s | 3.7–4.6 s |\n\nThe optimization pass was constrained to produce **zero visual change**, enforced by\n`imagediff.mjs`\n\nrather than by assertion — the shipped build is bit-identical to its\npre-optimization reference across all 11 shots.\n\nShader pre-warm (`src/core/prewarm.js`\n\n) is what removed the stalls. Making it\n*provably* pixel-neutral required first fixing subsystems that animated off\n`performance.now()`\n\ninstead of the engine clock, since any change to boot duration\notherwise shifted output.\n\nThe goal was to match a modern Call of Duty. **It does not.**\n\nEleven independent adversarial critics scored the frames against that bar. Scores\nwent 3.59 → 4.14 → 4.05 → **5.05** out of 10. Two shots reached \"CLOSE\"; the rest\nremain \"AMATEUR\". In a blind A/B, **every critic in every round picked the real Call\nof Duty frame.**\n\nWhere it falls short, specifically:\n\n**Hands.** Blocky finger slabs that don't convincingly grip the weapon.**Material richness.** Surfaces read as procedural noise rather than photographed reality at close range — the ceiling of generating texture from code.**Characters.** Enemies read as mannequins at distance.**Indirect light.** An approximation, not real GI.**Frame rate.** 28–30 fps at Retina. The art passes tripled geometry cost (5.9M → 11.3M triangles) and optimization recovered about half.\n\nA known root cause remains unfixed: the viewmodel light rig in `render/index.js`\n\ndelivers roughly 20× the irradiance per unit albedo that the world does — a plain\n*black* material in the view scene renders at L=110 against a background of 91,\npurely from F0=0.04. Every weapon albedo is cheated to a third of physical to\ncompensate, which caps material separation on the most-looked-at object in the game.\n\nSequential single-owner passes beat parallel fan-out decisively. Three rounds of six\nagents each owning one directory moved the score +0.46 and left frame-ruining defects\n*higher* than they started (60 → 47 → 66), because tonemapping, sky and indirect light\nare one coupled system and isolated agents kept breaking each other's assumptions.\nOne sequential pass with a single owner per coupled concern moved it +1.00 and cut\ndefects 66 → 26.\n\nThe most valuable single result came from an agent contradicting its own brief. Every critic for three rounds reported the weapon as \"untextured\". It wasn't — it was specular-dominated, with the diffuse term measured at L=26 against a shipped L=67. Prior rounds had been crushing albedos to fight bright-part complaints, which killed diffuse and made it worse. The fix was the opposite of what was asked for.", "url": "https://wpnews.pro/news/claude-of-duty", "canonical_source": "https://github.com/mshumer/Claude-of-Duty/tree/main", "published_at": "2026-07-26 22:33:06+00:00", "updated_at": "2026-07-26 22:52:32.255803+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "generative-ai"], "entities": ["Three.js", "WebGL2", "Apple"], "alternates": {"html": "https://wpnews.pro/news/claude-of-duty", "markdown": "https://wpnews.pro/news/claude-of-duty.md", "text": "https://wpnews.pro/news/claude-of-duty.txt", "jsonld": "https://wpnews.pro/news/claude-of-duty.jsonld"}}