Technical documentation · how the whole thing works

A complete tank battle game, generated at runtime in your browser.

Claude of Tanks is pure three.js on one WebGL2 context — no game engine, no asset CDN full of megabytes of textures. Vehicles are painted onto canvases when you look at them, battlefields are grown from seeded noise while the loading bar moves, and a measured-performance governor retunes quality to whatever hardware you brought. This page documents every system.

102
Vehicles
8
Battlefields
48
Camouflage patterns
1
WebGL2 context
0
Install, accounts, downloads
01

Overview

The whole game is a single-page three.js application. There is no engine layer, no ECS framework, no physics library — the renderer, the simulation, the AI, the audio synth and every texture on screen are project code. That choice is what makes the rest of this page possible: every system below is inspectable in the repository, and almost everything is generated at runtime from deterministic seeds rather than shipped as assets.

  • One render loop, one context. A single WebGLRenderer (antialias off, stencil off, high-performance power preference) drives garage, battle, kill-cam and the scene studio. All presentation goes through one EffectComposer chain — nothing ever calls renderer.render beside it in live play.
  • Runtime generation. Tank textures are painted on 2D canvases, terrain is sampled from a 9-octave simplex stack, vegetation and buildings are placed by seeded RNG streams, cloud decks are fbm bakes. The deployed page transfers ~3.6 MB.
  • Determinism as a contract. Every generator draws from mulberry32 streams with fixed seeds. The same battlefield, the same paint job and the same wreck char render bit-identically across sessions — which is what lets the project pin screenshot and geometry contracts in CI-style gates.
02

Engine & rendering

The renderer is configured once at boot: ACES filmic tone mapping (exposure 1.16, tuned by pixel-measured playfield luma against a reference band), sRGB output through an OutputPass, PCF shadow maps, and a canvas pixel-ratio cap of 2. The canvas backing store is 1:1 with physical pixels on dpr ≤ 2 displays; the composer's internal resolution is governed separately (see the quality governor), so the final antialiasing pass always rasterizes at native resolution even when the 3D frame renders smaller.

Post chain & antialiasing

Context AA is deliberately off. The scene renders into a quality-tier MSAA target (up to 4×), resolves once, then runs a single-sampled post chain: half-resolution GTAO with Poisson denoise, threshold bloom with a pre-tonemap emissive shoulder (so fireball cores don't clip flat), a grade S-curve pivoted at 0.33, aerial/haze, and a final display-space SMAA/FXAA pass that steps in native canvas pixels. Passes are merged where possible — each extra pass is a full-screen render.

Lighting & shadows

One sun. A four-cascade CSM rig owns the only shadow-casting directionals; a hemisphere light and an anti-sun sky fill (shadowless) complete the set, plus exactly two pooled PointLights shared by every muzzle flash and explosion — pooling keeps the scene's light count constant, so three.js never has to relink shader programs mid-battle over a changed light count.

  • Near cascades re-render every frame; the two far cascades (hundreds of meters out) re-render round-robin, one per frame — camera motion per frame is a fraction of a far-cascade texel, so it's visually free.
  • The governor can throttle the whole rig to half or third refresh under measured load (setShadowThrottle), with staggered scheduling and a forced full redraw on every transition so a stale map is never presented.
  • Instance-level shadow culling compacts instanced meshes against each cascade's frustum before the depth pass — shadow fill rate is ~17% of total draw calls and earns its own optimization lever.

The sky is a dome with two independent cloud decks — sun-aligned cumulus and cross-wind cirrus — whose textures are seeded fbm canvas bakes, baked lazily (one deck per frame on the loading path) and swapped in over 4×4 placeholders.

03

How tanks are made

Every vehicle in the game exists twice: as a procedural build — geometry constructed from measured real-world dimensions, painted by canvas programs — and, for much of the modern fleet, as a community-sourced GLB that streams in over the procedural stand-in. The procedural build is always the model of record: it spawns instantly, it works on every device tier, and it is what the license-stripped public build falls back to.

Procedural builds

Each tank family lives in a profile module (Abrams, Leopard, Merkava, Soviet heavies, WWII marks…). A profile declares real dimensions — hull length, track gauge, turret ring, gun caliber — and composes the vehicle from a shared construction kit: multi-segment hull plates with authored glacis angles, turret prisms inscribed in certified envelope curves, mantlets, skirts, fenders, and a dense fitting layer (tow hooks, periscopes, stowage, ERA bricks, antennas) that gives silhouettes their read at distance.

  • Running gear is instanced. Road wheels, sprockets, idlers and every track link render as InstancedMesh — a tank's few hundred link pads cost one draw. Per-wheel terrain conform and track scroll write instance matrices; a distance gate drops conform to staggered thirds beyond 160 m.
  • Geometry gates. Sourced reference models double as measurement oracles: automated probes compare the procedural build's silhouette, station positions and plate proportions against the oracle from 14 views, and a family only "graduates" when it passes a dual gate (machine metrology + independent shaded-parity critic). The registry, ledgers and frozen hashes live in docs/geometry-gate/.

Texture painters

No tank texture ships with the game. Each spec owns one shared canvas set — albedo (2048² for the hero tier, 1024² for AI roster fills), a height-derived normal map, roughness with per-patch paint response — painted by deterministic canvas programs: base scheme, panel lines and weld rows (welded-composite hulls skip rivets), chips, streaks, dust, and one of 48 camouflage patterns from three-tone NATO summer to dazzle, flecktarn, digital, winter whitewash and autumn schemes.

Why canvases

A painted 2048² albedo is ~200 ms of main-thread work but zero network bytes and infinitely restylable: the camo picker, the per-battlefield AUTO paint and the biome repaints all redraw the same shared canvas in place — every live instance of that tank updates through one texture.needsUpdate. All painters run as generator programs so loading paths bake them in stage-sized slices instead of one freeze (see Performance).

The sourced-model program

The modern fleet's showcase models come from community artists (credited per-model in ATTRIBUTION.md, license-ledgered per file; non-commercial models are quarantined out of public builds automatically). A sourced model is never dropped raw into the scene — it goes through a staged pipeline that runs in battle-safe idle slots:

fetchparseprep (skeleton clone, name-targeted surgery, texture caps)camoShare ×N (per-sheet composite caches)camoMerge (pattern over baked albedo)mergeHull / mergeTurret / mergeGun (static-kit geometry merges — 30-100 draws → a handful)warmTex (amortized GPU uploads)compileMesh (program submission, 12 ms budget)linkWait (KHR_parallel_shader_compile poll)commit (in-place swap, nothing binds new)

Camo on a GLB is composited in texture space: the pattern tile (mirror-stamped 3×3 so blob constellations never repeat across a hull) is blended with a luminance-normalized grayscale of the artist's albedo, preserving their weathering and AO while unifying the paint job. The procedural stand-in covers the stage until commit, so no frame ever shows a half-textured model.

Wrecks

A kill swaps the vehicle to charred materials derived from its own camo canvas: multiply-and-desaturate char stack, sooty blotches, rising heat streaks, rust bloom and bare-metal scrapes, plus a separate ember emissive map that smolders in seams. Destruction can pop the turret with the real physics arc; the kill-cam replays the killing shell with an x-ray module breakdown.

04

World generation

Battlefields are grown, not loaded. A heightfield samples a 9-octave simplex stack — domain-warped ridges plus detail octaves — then layers gameplay-aware features: a road network with smoothed corridor elevation (precomputed on a 257² field grid), a flattened village rectangle, marsh dips, lakes with auto-leveled shorelines, and per-biome macro forms (dune ridges, flat-top mesas with talus aprons) that fade around spawn pads and drive corridors so play always flows.

  • Terrain renders as an 8×8 grid of chunks, three LOD meshes each with hysteresis switching, under one splat material (canvas-baked masks; slope-driven rock only on real landforms).
  • Vegetation is fully instanced: tree clusters, lone pairs and map-rim stands with per-instance color jitter and vertex-shader wind; billboard impostors past the near partition (promoted in the sniper scope's aim corridor); a camera-centred dense grass carpet that re-parks its instances as you drive, plus midfield tuft chunks.
  • Props & destructibles — cottages and rowhouses with plaster/roof/wood canvas prints, boundary walls, fences, trucks, lampposts, crops, sandbags — live in per-kind instanced pools. Breaking one is two matrix writes: zero-scale the intact slot, activate the broken slot. A rematch restores the whole map in O(1) per prop.
  • Hot-path height queries (LOS ray marches, AI probes, wheel conform in battle) read a lazily-baked 1 m bilinear grid — ≤1 cm from the analytic surface, ~50× cheaper than the noise stack. Anything that seats visible geometry deterministically (spawns, captures) keeps the exact analytic function.

The whole build runs through generator cores that yield between slices — terrain per chunk-row, vegetation per placement pass, props per family — so the loading bar reports real progress and the build never freezes a frame. The synchronous twin drains the same generators for screenshot-contract map switches: both paths produce bit-identical worlds, verified by a fingerprint parity gate.

05

Simulation

Gameplay runs on a fixed-step simulation decoupled from the render loop, with an accumulator that drains cleanly across hitches. Nothing in presentation writes game state.

  • Movement solves hull pose from four corner probes plus per-gear-line terrain samples, with ground-type speed effects (soft marsh bog-down, hard roads), engine/brake model, handbrake, and gun handling: the turret chases input.aimPoint under hull-arc limits while dispersion blooms with movement and traverse, exactly WoT-style.
  • Ballistics & armor — five shell classes (AP, APCR, APFSDS, HEAT, HE) with penetration falloff over distance, impact-angle normalization, ricochet thresholds, spaced-armor and ERA interactions, and hull/turret/module hitboxes. HE resolves splash through terrain occlusion.
  • Spotting is a real concealment simulation: view range vs camouflage factor, foliage transparency along the sight line (bushes attenuate rather than block), firing bloom that scales with caliber, proximity detection through cover, and per-team intel — bots acquire targets through the same spotting state the player gets, never by cheating.
  • AI runs doctrine tiers (easy/normal/hard): cover evaluation and hull-down seeking against the height grid, obstacle avoidance, focus-fire discipline, low-HP retreat toward living teammates, and per-battle seeded personalities so replays are reproducible.

The movement, combat, spotting and equipment models are each pinned by headless self-test suites (28 / 233 / 99 / 166 assertions) that run on every landing.

06

Performance system

The performance architecture has one rule: anything that can hitch a live frame must either happen behind a screen the game owns, or be sliced until it can't. Everything heavy — painting, parsing, compiling, linking, uploading — is attributed by probes (frame-gap recorders, long-task observers, CPU profiles, per-slice pipeline telemetry) and then moved or chunked.

Loading & the warm pipeline

  • Chunked everything. World subsystems, tank family bakes (per painter stage), wreck char bakes, camo repaints, cloud decks, GLB merges — all generator-sliced with a painted frame between slices. The loading bar creeps through subsystems instead of jumping between freezes.
  • The warm pipeline runs behind the pre-battle screen: a silent volley of every effect family at a far map corner (sprite atlases bake, then one live-fx frame binds their pipelines), destroyed-state dances for every fielded family (wreck canvases + burn-hook shader variants compile), a whole-scene compile, then hidden-variant compiles per tank.
  • Shader links never block a reveal. Program compilation is submitted mesh-batched on a 12 ms budget, then the driver's parallel linker gets breathing frames (a bounded KHR_parallel_shader_compile poll) before anything renders. Shadow-depth variants — which only ever build at a real render — are resolved by warm renders of the world in subsets (terrain → +vegetation → +props) at a quarter viewport: same pipelines, 16× less fragment work.
  • A spawn-view warm frame renders one hidden frame from the player's chase pose before the countdown, so the first battle frame binds nothing new — measured zero program links at battle-open.
  • The 5-second countdown (sim frozen, world visible) doubles as an absorption window: a chunked re-warm covers any straggler models that outran the loading screen's bounded drain.

The quality governor

Quality follows measured performance, not device class. A per-frame governor tracks the render delta EMA, a spike-miss ratio, and the display's true cadence (p10 of recent deltas), makes a decision every 2.5 s of visible frames, and walks a relief ladder in both directions:

LeverWhat it doesEngages
Dynamic resolutionComposer scale steps 1.0 → floor (0.75 preset / 0.8 on dpr-1) in 0.09 steps; final AA stays nativeFirst — every pixel ratio
Trim 1 — shadow rateAll cascades to half refresh, staggeredScale at floor, 2 overloaded windows
Trim 2 — ambient occlusionGTAO off (the most expensive single pass)Next 2 overloaded windows
Trim 3 — shadow rateCascades to third refreshNext 2 overloaded windows
Adaptive tierWhole preset steps down, persisted across sessionsLast resort, ladder exhausted ~10 s
  • Framerate-decline detection. The governor earns an fps baseline during clean windows (rises fast, sags reluctantly) and treats a window that loses ≥20% of it — below a 72 fps smooth-enough ceiling — as overloaded, even when frames sit under the absolute budget. This catches the two blind spots of budget-only governors: a uniform 45–52 fps crawl with zero spikes, and a 120 Hz display sagging to 70.
  • Recovery walks the same ladder back — trims release first (they're visually loudest), resolution re-earns to 1.0 — with exponential flap backoff so an intermittently-loaded machine never oscillates.
  • Capture contexts reset everything: screenshot and metrology contracts always render untrimmed at full scale, and cascade redraws are forced, so the certified pixel contracts never see the governor.
  • A boot-time device diagnostic probes basic/lit/lit+shadow rendering and self-rescues (e.g. shadows-off with an exposure band reclaim) on GL stacks that fail — with a collapsing on-screen report. GPU-string heuristics only pick the starting tier; measurement takes it from there.
07

Mobile

Touch devices resolve the mobile tier (UA + touch points + GPU limits) and get a Blitz-style battle HUD: left drive joystick with a texture-triangle D-pad, swipe-to-aim over the right half, fire/scope/brake cluster, pinch-to-zoom into sniper, consumables re-parked as a thumb column, and a top-right ammo tray with ≥44 px targets. Portrait works with a landscape recommendation; safe-area insets are respected everywhere; browser pinch-zoom is defeated in depth. The mobile tier halves texture bakes and shadow budgets, and the same governor runs on top — verified end-to-end on iOS Safari (real WebKit, dpr 3) in the simulator, where the shadow-sampling rescue and tier detection were exercised for real.

08

Audio

The audio engine is WebAudio with two complete backends: a baked sample set (dense layered PCM synthesized offline, fetched and decoded after the first user gesture) and a live synthesis fallback that can produce every combat sound from oscillators and shaped noise if any sample fails — gunfire by caliber class, penetration clang, ricochet whine, HE crumps, engines, tracks, ambience and UI. Distance drives low-pass filtering and whump emphasis; repeats get playback-rate jitter so nothing sounds cloned.

09

UI & game modes

  • Garage — pedestal showroom with a damped drag-orbit camera (fixed canonical framing so switching tanks never slides the eye), camo picker with live repaints, battlefield picker with biome-AUTO paint preview, era-filtered carousel, tech tree, equipment loadouts and per-tank stats.
  • Battle — WoT-style HUD: shell tray with reload ring, minimap with real top-down capture underlay, damage panel with module states, kill feed, hit indicators, spotting eye, and an in-world 5-second countdown before control releases.
  • Kill-cam & end screen — slow-motion tracer replay of the killing shell with x-ray module breakdown, then a cinematic results screen (credits/XP economy, tallies, kill list, team panels) that adapts down to phone landscape.
  • Scene studio (press F8 in the garage) — a full staging tool: place any vehicles on any battlefield, pose turrets, trigger effects (fires, smoke, tracers, wrecks), drive cinematic cameras and export stills — it's how every marketing shot on the home page was made.
10

QA & determinism

The project treats rendering like an API with contracts:

  • Self-tests pin the simulation math (526 assertions across movement, combat, spotting, equipment) and run headless in Node.
  • Screenshot contracts stage deterministic battles and hash-compare renders; the geometry-gate program freezes graduated vehicles against vertex oracles and re-certifies on any change.
  • Probe suites drive the real game in headless Chrome: a journey probe (every first-use interaction), a play-session recorder (minutes of driving/fighting with frame-gap, long-task, shader-link and readback logging), a governor end-to-end (deterministic overload via frame burners), boot/battle-load stage telemetry, and world-build parity fingerprints.
  • Everything seeded. Worlds, paint, AI personalities, wreck chars and cloud decks derive from fixed mulberry32 streams — the foundation that makes every contract above possible.
Source of truth

Deeper internal docs live in the repository: docs/ARCHITECTURE.md (system contracts), docs/ATTRIBUTION.md (every sourced model and license), and docs/geometry-gate/ (the vehicle metrology program's registry and ledgers).