Felt World — tech stack and physics tests

How the game is built, how its physics was tested in each world, and what was not tested. Play the game · How the felt look is made · Source on GitHub

1. Is a game engine used?

No. There is no game engine (no Unity, Unreal, Godot, Babylon.js or PlayCanvas) and no physics engine (no Rapier, cannon.js or Ammo.js).

The game uses three.js 0.170.0. three.js is a rendering library: it gives a scene graph, cameras, materials, and WebGL drawing. It does not give a game loop, collision, physics, audio, input, or world streaming. The game code writes all of those itself, in one JavaScript module inside feltworld/index.html (3,231 lines, 178,562 bytes).

2. Tech stack

PartWhat is used
DeliveryOne HTML file. No build step, no bundler, no package manager. Any static web server serves it. GitHub Pages hosts the play link.
Renderingthree.js 0.170.0 from the jsDelivr CDN through an import map (three and three/addons/). WebGL.
LookToon materials with a soft gradient ramp. Running-stitch outlines (inverted hull, fragments discarded in a dash pattern). A post-process ShaderMaterial: wool-fibre fuzz, felt grain, soft seams at depth edges, a stitched fabric border. A sky ShaderMaterial. Details: How the felt look is made.
TexturesDrawn at start on 2D <canvas> elements: felt noise, knitted roads with a stitched centre line, quilted fields. No image files.
WorldPure functions of (x, z): height, biome masks (town, fields, meadow, forest, mountain, ocean), and a warped road grid. Chunks are built one per frame, nearest first, from a seeded random generator per chunk, and are removed past a radius.
Props and crowdsInstancedMesh batches of scaled primitives with a colour per instance. Traffic, pedestrians, sheep, rabbits, fish, jellyfish, rays, sea turtles, crabs and whales are crowds on instanced meshes that respawn round the player.
PhysicsHand-written. See section 3.
AudioWeb Audio API only: a look-ahead note scheduler, a generative radio with several songs, note-phrase chimes, engine hum, wind (band-pass noise), and a low-pass filter on the master bus under water. No audio files.
UIHTML and CSS over the canvas: title card, clock and time-of-day buttons, speed, mode, depth readout, collected counts, minimap, help, Pause and Music buttons, touch joystick and buttons.
FontsFredoka and Patrick Hand from Google Fonts.
QualityThree tiers. Low: pixel ratio 0.75, no shadows, 40 cars, 30 pedestrians. Medium: 1.0, 1024 px shadows, 70 cars, 50 pedestrians. High: 1.5, 2048 px shadows, 110 cars, 80 pedestrians, a larger chunk radius. The tier is saved in localStorage.
Testingfeltworld/physics-check.js, an ES module that runs inside the live game. Claude drove it in Chrome through the Claude in Chrome extension. node --check on the extracted module did syntax checks after each edit.
AuthorClaude Code with the Opus 5.5 model, from a sequence of requests. See the README.

3. How the physics works

The physics is arcade physics. It does not simulate forces between bodies. It keeps every body on or above the correct surface and out of every solid thing.

Ground

groundAt(x, z) returns the height of the ground as it is drawn: it interpolates the triangles of the chunk's terrain mesh, then takes the highest road deck at that point. Everything on land uses it: the player car, the walker, traffic, pedestrians, sheep, rabbits. Crabs, fish, jellyfish and rays use it for the sea floor. A drawn road flattens the ground under it fully, so a road deck cannot float above the terrain.

Solid things

Each chunk has a list of colliders: circles (trees, rocks, lamps) and rotated boxes (houses, fences, the shipwreck, the chest). Every collider has a height range: a top, and for tree crowns and windmill sails also a bottom (bot). collide(p, rad, down, tall) pushes a round body of radius rad and height tall out of every collider whose height range it meets, and out of every traffic vehicle (a moving rotated box). The bird can fly under a tree crown but not through it.

Moving bodies against each other

These bodies collide: the player car, the submarine, the walker, the player turtle, the bird, pedestrians, sheep, rabbits, sea turtles, rays, whales and crabs. Kelp, coral, flowers and small bushes are passable on purpose. Sheep are passable for the player on purpose.

Traffic

4. How the physics was tested in all worlds

The reference (the "oracle")

The test does not trust the game's own height code. For each sample it casts a ray straight down against the drawn terrain and road meshes (drawnGround). A body passes only if it is within a tolerance of what the player sees.

How it runs

The test imports the running game's debug handle (window.__game), teleports the player to each world, moves the game with step(1/60, n), and samples every visible agent. It also sends real KeyboardEvents (W, A, D, E, F, T, Q) through the same input path a player uses.

What each world checks

WorldChecksTolerance
TownTraffic on the road; pedestrians on the ground; no two traffic vehicles inside each other (oriented-box separating-axis test); the player car on the ground; nothing inside a house, tree, lamp, fence or vehicle. Real input: drive with W + A for 8 s; press E to get out; walk with W, then W + D, for 8 s (the walker must cover more than 30 m); walk into a house and into a tree / rock / lamp; drive into a house.On ground: −0.15 m to +0.35 m. Player car: −0.15 m to +0.45 m (it counts as airborne above 0.4 m).
ForestDrive into a tree; land agents on the ground and out of solids for 5 s.As town.
Mountain roadThe test searches for the road lane with the largest gap between the road deck and the plain terrain, parks the car there, then holds W for 10 s. The car must stay on the road and move more than 50 m.As town.
FieldsSheep and rabbits on the ground and out of trees and houses, for 20 s.Sheep −0.3 m to +0.35 m; rabbits −0.3 m to +0.95 m (they hop).
Deep sea (−70 m) and shallows (−14 m)Fish, jellyfish, rays, sea turtles and whales stay above the sea floor and below the surface; a whale's nose and tail (±9 m) stay above the floor; crabs stand on the floor; no animal inside a seabed rock, the shipwreck or the chest; the submarine stays between the floor and the surface. 10 s each.Crabs ±0.35 m. Inside a solid: more than 0.05 m fails.
Sea, real inputDrive the submarine (W) into a seabed rock; press T and swim the turtle into the same rock; press T, then hold Q + W for 4 s to dive.The body must stop at the rock surface.
AirPress F on a meadow road, dive with W for 3 s, glide for 2 s: the bird stays above the ground and the water. The bird glides into a house at mid-wall height and into a tree crown.The bird must stop at the solid.

Proof that the test can fail: fault injection

m.faults() breaks the physics on purpose in 9 ways and checks that the matching check goes red. This shows that no check passes by construction.

  1. A traffic car 2 m under the road.
  2. A traffic car moved into another car.
  3. The player car put inside a house.
  4. The player car 3 m under the ground.
  5. The bird put inside a house.
  6. A sheep put inside a tree.
  7. A fish lifted out of the sea.
  8. A crab 2 m under the sea floor.
  9. A whale dropped onto the sea floor.

Traffic soak

m.soak('town') runs 90 s of traffic (5,400 steps) and counts vehicles inside each other, cars stopped for more than 20 s (with the reason each car gives), U-turns started, finished, and longer than 10 s, and deadlocks: it follows each stopped car's reason ("follow 12", "cross 7", "junction 3") to the car it waits for, and a path that comes back to a car already on it is a deadlock. The old count of "cars stopped for more than 20 s" missed the crossing deadlock, because the gridlock breaker removes stuck cars that the camera does not see.

Body against body

m.overlaps(biome) runs 20 s in a town, a field or the sea and counts pairs more than 0.3 m inside each other: traffic (the whole body, as circles along its length) against trees, lamps and houses; traffic against pedestrians and sheep; pedestrian against pedestrian; sheep and rabbits against each other; every pair of whales, sea turtles, rays, jellyfish and crabs; and the submarine against each of them.

5. Results

TestLowMediumHigh
run()150 checks, 0 fails, 59,349 samples150 checks, 0 fails, 88,729 samples150 checks, 0 fails, 130,905 samples
faults()9 of 9 caught9 of 9 caught9 of 9 caught
overlaps() town / field / sea0 bad in 1,683 / 1,698 / 6,3520 bad in 3,107 / 3,295 / 6,8720 bad in 5,416 / 5,975 / 10,869
soak('town'), 90 s0 overlaps, 0 deadlocks, 0 stopped > 20 s; 5,029 close pairs; 52 of 52 U-turns finished0 overlaps, 0 deadlocks, 0 stopped > 20 s; 27,187 close pairs; 40 of 41 U-turns finished0 overlaps, 0 deadlocks, 0 stopped > 20 s; 64,862 close pairs; 56 of 57 U-turns finished
Test time (all five)58 s74 s102 s

An unfinished U-turn is one still turning when the 90 s ended. No U-turn took more than 10 s.

Sea animals, every frame. overlaps() samples every 0.5 s. A separate run measured every sea pair on every frame: 3 runs of 40 s (2,400 frames) each on Medium, 12,430 to 18,732 close pairs per run. The worst overlap was 0.18 m, under the 0.3 m limit.

What the upright-cylinder check found first. The sea push used to treat each animal as a sphere, while the check treats it as an upright cylinder. The check failed on Low and on Medium. On Low, one 20 s run had 346 bad jellyfish-against-jellyfish samples, 3.51 m deep at worst. On Medium, the worst was a whale against a jellyfish, 4.27 m deep. The fixes: the game uses the check's cylinders; the push starts before two bodies meet vertically; a new animal appears only where it is clear; a ray lifts over a crab and over the top of a rock instead of a sideways jump of about 5 m.

Before the physics pass, an earlier version of the same test found these failures: 700 of 700 traffic samples failed "on road", and the worst car was 10.8 m below the road. The overlap check found 68 to 104 traffic overlaps.

Cost of the physics pass

Median time per frame (step + render) on High quality, measured in a hidden Chrome tab. These times are from before the crossing, pedestrian and sea-animal changes of 27 September 2026, and were not measured again:

PlaceBeforeAfter
Town6.2 ms7.5 ms
Fields5.8 ms7.3 ms
Deep sea6.7 ms8.0 ms

6. What was not tested

Known limits (not overlaps)