feat: terrain
This commit is contained in:
parent
f86b4ada13
commit
81474b6bca
13 changed files with 251 additions and 279 deletions
68
AGENTS.md
68
AGENTS.md
|
|
@ -12,9 +12,11 @@ rules live in `.agents/rules/*.md`.
|
|||
|
||||
- **PS1 look is a set of live knobs**, dial-able from full-PS1 to clean. See
|
||||
`RenderConfig`. Nothing about the era is hardcoded into the renderer.
|
||||
- **Software rasterizer on purpose** — the PS1 look *is* rasterizer artifacts
|
||||
(affine texture swim, vertex snap, no mipmaps). Raytracing was considered and
|
||||
cut (it removes the very artifacts we want).
|
||||
- **Software rasterizer on purpose** — the PS1 look leans on rasterizer traits
|
||||
(vertex snap, low-res + dither/banding, no mipmaps). Raytracing was considered
|
||||
and cut (it removes the very artifacts we want). Note: **affine texture "swim"
|
||||
was dropped** — texturing is always perspective-correct now (it warped badly on
|
||||
the big outdoor terrain); the other era knobs stay.
|
||||
- **Minimal architecture.** Scene-graph-lite / plain data + functions.
|
||||
Deliberately **not** ECS or any "Big Game Architecture." Prefer the smallest
|
||||
clear structure; add knobs to experiment rather than abstractions.
|
||||
|
|
@ -50,21 +52,32 @@ rules live in `.agents/rules/*.md`.
|
|||
Bayer dither), `RenderConfig` (the look dials + presets), `Rasterizer`,
|
||||
`Texture` (nearest/bilinear, wrapping, no mipmaps), `Sky` (gradient + sun +
|
||||
procedural clouds).
|
||||
- `scene/` — `Camera` (fps yaw/pitch), `Mesh` (indexed tris), `Sprite`
|
||||
(Y-axis billboard).
|
||||
- `scene/` — `Camera` (fps yaw/pitch; far plane reaches the outdoor peaks),
|
||||
`Mesh` (indexed tris), `Sprite` (Y-axis billboard), `Terrain` (procedural
|
||||
heightfield around the room: flat clearing in the center, rolling hills, tall
|
||||
edge peaks. `Terrain.ground` builds the outdoor mesh with a hole for the room;
|
||||
`Terrain.height` is the shared ground-height sampler for the player).
|
||||
- `app/` — browser glue.
|
||||
- `main.ts` — game loop, input, preset switching, canvas blit, FPS meter.
|
||||
- `assets.ts` — load `/assets/*.png` → `Texture` (zero-copy; ImageData bytes
|
||||
are already the `Color` layout).
|
||||
- `level.ts` — builds the playground: per-texture meshes, `Aabb` colliders,
|
||||
NPC position, sky/cloud config. Geometry density knob `DIVISIONS_PER_UNIT`.
|
||||
- `level.ts` — builds the playground: a flat stone-floored room (three walls,
|
||||
north side open) in the center of a big grassy `Terrain` world (~20x across).
|
||||
Per-texture meshes incl. the outdoor grass `ground`, `Aabb` colliders, NPC
|
||||
position, `Terrain` config + `GROUND_DIVISIONS`/`GROUND_UV`, sky/cloud config.
|
||||
The stone floor is lifted by `FLOOR_LIFT` (a z-bias) so it stays clean over the
|
||||
terrain skirt that laps under the room edges. Room surfaces are single flat
|
||||
quads -- no subdivision needed since texturing is perspective-correct; ground
|
||||
triangle count is `GROUND_DIVISIONS` (fixed grid, so world size sets cell
|
||||
chunkiness, not tri count).
|
||||
- `player.ts` — feet-cylinder player: gravity/jump + circle-vs-AABB/-circle
|
||||
collision.
|
||||
collision; ground height comes from `Terrain.height` (plus standable AABBs).
|
||||
- `index.html` — Vite entry at repo root; holds the `#screen` canvas and the
|
||||
`#fps` meter div (styled inline).
|
||||
- `scripts/gen-assets.ts` — procedurally draws the placeholder textures and
|
||||
writes PNGs (hand-rolled encoder via `node:zlib`). Run via `bun run assets`.
|
||||
- `assets/` — generated `floor/wall/crate/npc` PNGs. Swap for real art anytime;
|
||||
- `assets/` — generated `floor/grass/wall/crate/npc` PNGs (`floor` = room stone,
|
||||
`grass` = outdoor ground). Swap for real art anytime;
|
||||
filenames are the contract.
|
||||
- `server/` — Bun server stub. `shared/` — isomorphic slot.
|
||||
|
||||
|
|
@ -72,15 +85,15 @@ rules live in `.agents/rules/*.md`.
|
|||
|
||||
`Player.update` → build `Camera` → `Camera.viewProjection` →
|
||||
`Sky.render` (fills color + resets depth, replaces a clear) →
|
||||
`Rasterizer.draw` floor, walls, crate (one call per texture) →
|
||||
`Rasterizer.draw` ground, floor, walls, crate (one call per texture) →
|
||||
`Sprite.billboard(npc)` drawn via `Rasterizer.draw` →
|
||||
`Framebuffer.quantize` → `present` (integer-scale, letterboxed blit;
|
||||
`imageSmoothingEnabled` follows `upscaleFilter`).
|
||||
|
||||
Rasterizer specifics: near-plane clip (Sutherland-Hodgman), **1/w z-buffer**,
|
||||
affine↔perspective-correct UV blend (`perspectiveCorrect`), screen-space
|
||||
vertex snap, flat directional lighting, distance fog, **alpha cutout** (discard
|
||||
texel alpha < 128, for sprites), **double-sided** (no backface culling).
|
||||
perspective-correct UVs, screen-space vertex snap, flat directional lighting,
|
||||
distance fog, **alpha cutout** (discard texel alpha < 128, for sprites),
|
||||
**double-sided** (no backface culling).
|
||||
|
||||
## The look — where to tune
|
||||
|
||||
|
|
@ -88,13 +101,19 @@ texel alpha < 128, for sprites), **double-sided** (no backface culling).
|
|||
`standard` (384×216, the startup default), `soft`, `clean`, plus an unbound
|
||||
`ps1` (320×240). In-app keys **1/2/3** switch standard/soft/clean live.
|
||||
Knobs: `internalWidth/Height`, `upscaleFilter`, `colorDepth`, `dither`,
|
||||
`vertexSnap`, `perspectiveCorrect` (0 = full affine swim, 1 = correct),
|
||||
`textureFilter`, `lighting`, `fog`.
|
||||
- **`app/level.ts` `DIVISIONS_PER_UNIT`** (1.3) — geometry triangle density per
|
||||
world unit. Higher = smaller triangles = less affine swim; **below ~0.5 the
|
||||
floor degenerates into a black wedge** (affine sampling collapses onto the
|
||||
texture's dark grout lines). Works with `perspectiveCorrect` — both fight the
|
||||
same affine error from different sides.
|
||||
`vertexSnap`, `textureFilter`, `lighting`, `fog`. (Texturing is always
|
||||
perspective-correct — the affine-swim dial was removed.)
|
||||
- **`app/level.ts` `GROUND_UV`** (0.25) — outdoor ground texture tiles per world
|
||||
unit. Lower = the stone tiles bigger and less busy = less far-distance moire
|
||||
(there are no mipmaps); higher = finer but shimmerier.
|
||||
- **`app/level.ts` `GROUND_DIVISIONS`** (56) — outdoor ground grid resolution and
|
||||
the **main outdoor FPS lever**. The open vista is **transform-bound** on the
|
||||
ground's triangles (no frustum culling — every tri is projected each frame), so
|
||||
cost is ~linear in this: measured ~45 fps at 48, ~28 fps at 64, ~24 fps at 96
|
||||
(headless, `standard`). Lower it for FPS, raise for finer terrain. Draw distance
|
||||
(`fog.far` + `Camera` far plane, pushed out to ~200/260 for this scene) is
|
||||
comparatively cheap since far ground is a thin horizon band. Cranking
|
||||
`TERRAIN.peakHeight`/`outer` costs almost nothing (same tri count).
|
||||
|
||||
## Clouds
|
||||
|
||||
|
|
@ -112,7 +131,8 @@ branching in the cloud shader. Cost scales with sky resolution — fine at
|
|||
## Controls
|
||||
|
||||
WASD move · mouse look (click canvas to pointer-lock) · **Space** jump ·
|
||||
**1/2/3** switch look presets. FPS shown bottom-right.
|
||||
**1/2/3** switch look presets. FPS shown bottom-right. The room's north wall is
|
||||
open — walk out onto the terrain and toward the peaks.
|
||||
|
||||
## Code style (oxlint-enforced — match it)
|
||||
|
||||
|
|
@ -134,9 +154,9 @@ throwaway `bun` script can: `buildLevel()`, decode `assets/*.png` (they're
|
|||
filter-0 RGBA — trivial to inflate), set a `Camera` pose, run
|
||||
`Sky.render` + `Rasterizer.draw` into a `Framebuffer`, encode the color buffer
|
||||
to PNG (same hand-rolled encoder as `scripts/gen-assets.ts`), write it, and read
|
||||
it back. Sampling individual pixels this way diagnosed the affine black-wedge
|
||||
(affine sampled grout, perspective-correct sampled stone) and tuned the clouds.
|
||||
Put temp scripts in the job tmp dir, not the repo.
|
||||
it back. Sampling individual pixels this way tuned the clouds, framed the terrain
|
||||
peaks, and confirmed the ground textures flat (no swim). Put temp scripts in the
|
||||
job tmp dir, not the repo.
|
||||
|
||||
## Roadmap / not yet built
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue