A browser camping sim set in a Japanese river valley. The thing I want to show is
that there are no asset files at all — no images, no meshes, no audio. The repo
has zero binaries. Everything is built at load time.
Materials — one procedural texture library feeding MeshPhysicalMaterial.
Woven thread structure for the fabrics (warp/weft with per-thread tension jitter),
rolled-steel flow for the blackened firepit, radial grain for split logs. Normals
are Sobel-derived from the height field, not hand-authored. Wear is driven by real
curvature (length(fwidth(N)) / length(fwidth(worldPos)), thresholded in 1/m) so
fillets and rims wear but flat bodies don’t.
Terrain — heightfield with triplanar splat blending, hex-tiling (3-tap
barycentric, translation only so derivatives stay valid) to kill the repeat.
Fire — volumetric raymarch, colour from blackbody radiation rather than a
palette. It’s also the light source at night, with the flicker driving the
PointLight intensity.
Audio — Web Audio only, no samples. Fire crackle is a Poisson process with
per-event filter/decay variation. The river is four independently modulated noise
layers. Autumn insects only chirp after sunset (and no higurashi — those are a
summer cicada).
Three.js r185 + Rapier, ~130 source files, two runtime dependencies.
Playable (desktop only, WASD + mouse):
A few things that cost me time, in case they’re useful
onBeforeCompile hands you the shader with #include unexpanded. So
string-replacing the expanded chunk silently no-ops. My stochastic tiling was
dead for three review rounds before I hooked shaderSource and counted call
sites — the function was defined, never called. Pull the chunk out of
THREE.ShaderChunk, patch that, and replace the #include itself.
Half-res AO reading a full-res NEAREST depth buffer. The half-res pixel centre
lands exactly on a full-res texel boundary, so which side you get depends on float
rounding. Reconstructed normals tilted ~25° on a surface at grazing incidence,
which produced perfectly regular vertical black stripes on the tent. Three of us
misdiagnosed it as geometry or materials first. Offsetting every depth fetch by a
quarter texel fixed it.
Shadow bias vs. thin geometry. Cascade 0 had normalBias at 43.5 mm, so
anything thinner than ~9 cm cast no shadow at all — firepit legs, pegs, guylines.
Everything read as floating. Tightening lambda and moving to a per-fragment
slope-scaled depth bias brought it to ~9 mm.
MediaRecorder codec strings. 'video/mp4;codecs=h264' returns false;
'video/mp4;codecs=avc1.42E01E' returns true. Same codec, and I wrote off mp4
support for a while because of it.
Honest self-assessment
I had three separate LLM critics blind-judge the screenshots — source concealed,
asked to sort each into “photograph / AAA product screenshot / hobby project.”
Three rounds, all twenty shots came back hobby project every time. The recurring
complaints were visible instance repetition, large flat surfaces, and light not
interacting with the scene.
They did consistently identify the location as Japan though, from the maple leaf
shape, the susuki grass, the planted cedar stands, and the raised square firepit
(which exists because Japanese campgrounds ban ground fires).
So the reading is right, the rendering isn’t there yet. Feedback very welcome —
particularly on the vegetation and on getting large fabric surfaces to hold up.


