Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7da590a
Phase 0: scaffold camping-world (vite+three), FP controller, screensh…
cursoragent Jun 11, 2026
53265c6
Phase 1: asset pipeline — Poly Haven fetch + gltf-transform optimizat…
cursoragent Jun 11, 2026
ea0fb40
Iteration 2 prep: measured HDRI sun angles, kloppenheim sky, mid/far …
cursoragent Jun 11, 2026
ee919d0
Iteration 3→4: fix grass backface lighting, HDR background mips+clamp…
cursoragent Jun 11, 2026
bbb8ff7
Iterations 4-6: autumn sky, shadow debug tooling, HDR clamp 32 for su…
cursoragent Jun 11, 2026
07bec0d
Iteration 7: exposure 0.62, terrain bowl rim, criss-cross woodpile, b…
cursoragent Jun 11, 2026
8bafb17
Fix post stack: N8AO gammaCorrection off + drop redundant RenderPass …
cursoragent Jun 11, 2026
1ea24b6
Iter 8 prep: photoscanned stone fire pit + charred logs, loose firewo…
cursoragent Jun 11, 2026
5374175
Iter 9 prep: load stone_fire_pit (was silently missing), open shadows…
cursoragent Jun 11, 2026
65f0278
Campsite rework: ash bed in pit, chunky split-log firewood from dead_…
cursoragent Jun 11, 2026
5e4bfd0
Iter 10: open shadows (env 0.95, exp 0.66, lift), lower charred logs,…
cursoragent Jun 11, 2026
71c4736
Iter 11: env fill 1.5 (backlit sides must read), softer vignette, lig…
cursoragent Jun 11, 2026
84e192c
Iter 12: meadow runs to treeline (r95 + far card boost), matte foliag…
cursoragent Jun 12, 2026
dbf2c0b
Retry model loads (transient dev-server fetch failures)
cursoragent Jun 12, 2026
6267c52
Iter 13: far ground blends to matte dry-grass tone, dimmer leaf litte…
cursoragent Jun 12, 2026
aed624b
Iter 14: raised outer ridge + 3-row treeline walls off HDRI haze band…
cursoragent Jun 12, 2026
c36dfb4
Iter 15: denser treeline ring (240), warm-tinted pit stones, deeper a…
cursoragent Jun 12, 2026
a821051
Iter 16: kill grazing-angle env sheen on terrain+grass (root cause of…
cursoragent Jun 12, 2026
b3be21a
Iter 17: kill DIRECT sun specular on terrain+grass (grazing Fresnel f…
cursoragent Jun 12, 2026
5f3da33
Iter 18: rebalance post-sheen-kill — grass value back up, gentler far…
cursoragent Jun 12, 2026
006209c
Iter 19: measurement-sized brightness correction — grass cards +25%, …
cursoragent Jun 12, 2026
8b29fec
Iter 19: bake 2.0x card value (A/B-sized, sub-linear response), dusty…
cursoragent Jun 12, 2026
fafb0bf
Iter 20 (final): pit stones env fill 1.5 — shadow side reads stone, n…
cursoragent Jun 12, 2026
ce89144
Fix movement test: deterministic fixed-step sim (SwiftShader <1fps br…
cursoragent Jun 12, 2026
358468a
README: run/controls/pipeline/self-play docs + Poly Haven credits, he…
cursoragent Jun 12, 2026
b7d7b71
docs: web-friendly JPG evidence shots for direct raw links
cursoragent Jun 12, 2026
dd19ef3
Add committed production build (play/) so the game runs from a raw CD…
cursoragent Jun 12, 2026
eb4a731
Deploy Pages from this branch too: live site unchanged, adds camping …
cursoragent Jun 12, 2026
1632703
README: add click-to-play CDN links for the committed play/ build; re…
cursoragent Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions camping-world/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
raw/
dist/
shots-local/
*.log
114 changes: 114 additions & 0 deletions camping-world/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Camping World — photoreal Three.js scene

A first-person camping clearing built with Three.js, aiming for a "reads as
real" golden-hour look (RDR2-style mood): rolling dry meadow, photoscanned
trees and deadwood, a stone fire pit with ash and charcoal, and ~130k
instanced grass cards swaying in the wind.

![wide vista](docs/iter-20-v3-wide-vista.png)

## Play it in the browser (no install, no merge)

The production build is committed at [`play/`](play/) and served straight
off this branch through the githack CDN:

- **Play now (pinned, fast):**
<https://rawcdn.githack.com/ilikevibecoding/My-projects/dd19ef3f8d535839b9ad1ed1f9b64f840d6da209/camping-world/play/index.html>
- **Always-latest (tracks this branch, slower first load):**
<https://raw.githack.com/ilikevibecoding/My-projects/cursor/bc-5fd2158d-847b-44d3-b77f-90ba063719c5-2695/camping-world/play/index.html>

githack shows a one-time "External Content Notice" — click **Open the page**
and the game loads. First visit downloads ~160 MB of photoscanned assets, so
give it a moment on the loading screen.

## Run it

```bash
cd camping-world
npm install
npm run dev # http://localhost:5173
```

Click the screen to lock the pointer, then:

| Input | Action |
| ------------ | ----------------- |
| `W A S D` | walk |
| `Shift` | sprint |
| mouse | look |
| `Esc` | release pointer |

The player walks at 2.2 m/s (4.8 m/s sprint) at a 1.7 m eye height, follows
the terrain, head-bobs subtly, and collides with trunks, boulders and camp
props.

## How it's built

- **Terrain** — seeded-noise heightfield cupped into a bowl so the rim +
treeline close the horizon. Three photoscanned ground sets (grass / leaf
litter / mud) splat-blended in a custom `onBeforeCompile` shader with
two-scale de-tiling and macro tint noise.
- **Sky & light** — Poly Haven `autumn_field_puresky` HDRI (IBL + background,
HDR-clamped and mipmapped) plus a measured-azimuth DirectionalLight sun
(4096² PCF shadows) and exp2 aerial haze.
- **Vegetation** — optimized photoscan trees in three LOD tiers (hero ≤60 m,
mid at clearing edge, instanced far ring closing the horizon), instanced
shrubs/ferns/nettles understory, and 130k wind-animated grass cards
baked from photoscanned clumps into an atlas (`scripts/bake-grass.mjs`).
- **Campsite** — photoscanned stone fire pit with a lumpy ash mound and
charcoal chunks, split-log firewood heap, fallen-log seat, chopping stump
with hatchet, crate + lantern.
- **Post** — SMAA, N8AO, bloom, warm-shadow split-tone grade, vignette, grain
(pmndrs `postprocessing` + `n8ao`).

### The one weird lesson

A pale washed-out band across the mid-distance survived four iterations of
albedo/fog darkening. Root cause (found by pixel-measuring a grass-stripped
render): **grazing-angle specular** — at horizontal sightlines Fresnel → 1 and
the terrain + grass cards mirror the bright sky/sun. Matte ground needs its
`directSpecular`/`indirectSpecular` killed in-shader; no albedo value can
out-darken a sky reflection.

## Asset pipeline (already run; outputs are committed)

```bash
npm run fetch-assets # downloads Poly Haven models/textures/HDRIs
npm run optimize-assets # gltf-transform: weld → simplify per-LOD → WebP → meshopt
node scripts/bake-grass.mjs # renders grass clumps into the card atlas
```

## Self-play harness (how the look was iterated)

```bash
npm run shots -- --iter 21 # all 6 fixed views → PNG + stats JSON
npm run shots -- --iter x --views 3 # single view
npm run shots -- --iter x --query "nograss=1" # layer isolation
node scripts/test-movement.mjs # deterministic FP-controller tests
```

Screenshots are captured with system Chrome + SwiftShader (software WebGL2),
so they run on GPU-less CI boxes. `src/debug/harness.js` defines the fixed
viewpoints; `window.__READY` gates capture; renderer stats accumulate across
all composer passes.

Useful debug query params: `?shot=1` (freeze wind + fixed cams), `?px=0.5`
(render scale), `?hdri=…`, `?sunel/?sunaz/?sunint/?env/?fog/?exp` (lighting
overrides), `?nograss/?noveg/?nogeo/?nopost` (layer isolation),
`?grassboost=N` (card brightness A/B).

## Credits

All assets are CC0 from [Poly Haven](https://polyhaven.com):

- **HDRIs** — autumn_field_puresky (used), belfast_sunset_puresky,
kloppenheim_02_puresky (A/B alternates).
- **Ground textures** — aerial_grass_rock, forest_leaves_03,
brown_mud_leaves_01, forrest_ground_01.
- **Models** — island_tree_01/02, tree_small_02, searsia_lucida, fir_sapling,
fir_sapling_medium, shrub_01/02/03, fern_02, nettle_plant, dandelion_01,
grass_medium_01/02, grass_bermuda_01, dead_tree_trunk, dead_tree_trunk_02,
tree_stump_01, dry_branches_medium_01, bark_debris_01, stone_fire_pit,
boulder_01, rock_07, rock_moss_set_01, namaqualand_boulder_02,
sand_rocks_small_01, hatchet, wooden_axe, wooden_lantern_01,
wooden_crate_01.
15 changes: 15 additions & 0 deletions camping-world/bake.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>grass card baker</title>
<link rel="icon" href="data:," />
<style>
html, body { margin: 0; background: #333; }
canvas { display: block; }
</style>
</head>
<body>
<script type="module" src="/src/debug/bake.js"></script>
</body>
</html>
Binary file added camping-world/docs/00-graybox-vista.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added camping-world/docs/15-pale-band-vista.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added camping-world/docs/19-grass-eye-level.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added camping-world/docs/20-final-campsite.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added camping-world/docs/20-final-firepit.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added camping-world/docs/20-final-vista.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added camping-world/docs/iter-20-v3-wide-vista.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 92 additions & 0 deletions camping-world/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Camp — a photoreal three.js campsite</title>
<link rel="icon" href="data:," />
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
font-family: Georgia, 'Times New Roman', serif;
}
#app {
position: fixed;
inset: 0;
}
#app canvas {
display: block;
}
#overlay {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 18px;
background: rgba(8, 6, 4, 0.55);
backdrop-filter: blur(6px);
color: #e8ddc8;
cursor: pointer;
transition: opacity 0.4s ease;
z-index: 10;
text-align: center;
}
#overlay.hidden {
opacity: 0;
pointer-events: none;
}
#overlay h1 {
font-size: 42px;
letter-spacing: 0.18em;
font-weight: 400;
margin: 0;
text-transform: uppercase;
}
#overlay p {
margin: 0;
font-size: 15px;
letter-spacing: 0.08em;
color: #c9bda4;
}
#overlay .keys {
font-size: 13px;
letter-spacing: 0.12em;
color: #9d937e;
text-transform: uppercase;
}
#loading {
position: fixed;
left: 50%;
bottom: 12%;
transform: translateX(-50%);
color: #c9bda4;
font-size: 13px;
letter-spacing: 0.2em;
text-transform: uppercase;
z-index: 11;
transition: opacity 0.3s ease;
}
#loading.hidden {
opacity: 0;
}
</style>
</head>
<body>
<div id="app"></div>
<div id="overlay" class="hidden">
<h1>Camp</h1>
<p>An evening in the pines</p>
<p class="keys">Click to enter &mdash; WASD walk &middot; Shift sprint &middot; Mouse look &middot; Esc release</p>
</div>
<div id="loading">Loading the wilderness&hellip;</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
15 changes: 15 additions & 0 deletions camping-world/inspect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>model inspector</title>
<link rel="icon" href="data:," />
<style>
html, body { margin: 0; height: 100%; overflow: hidden; background: #222; }
canvas { display: block; }
</style>
</head>
<body>
<script type="module" src="/src/debug/inspect.js"></script>
</body>
</html>
Loading