feat: bees and frogs
This commit is contained in:
parent
46e7070b6b
commit
16f205babe
13 changed files with 540 additions and 31 deletions
47
AGENTS.md
47
AGENTS.md
|
|
@ -77,28 +77,44 @@ rules live in `.agents/rules/*.md`.
|
|||
(procedural low-poly rock: a squashed, jittered, part-buried sphere;
|
||||
`Boulder.build` appends into a shared mesh), `Bush` (cluster of small leaf
|
||||
blobs, shares the oak leaf texture/mesh), `Flower` (thin stem + colored bloom;
|
||||
samples a 2x2 color-atlas texture, drawn double-sided).
|
||||
samples a 2x2 color-atlas texture, drawn double-sided), `Mob` (a **roaming**
|
||||
creature — `frog` hops the ground, `bee` hovers/darts — the engine's only
|
||||
moving geometry. Unlike the baked props, a mob's low-poly mesh is built once
|
||||
per kind in **local space**; `Mob.build` bakes the two canonical meshes,
|
||||
`Mob.update` steps the wander AI (leashed to a home anchor, deterministic via
|
||||
an evolving per-mob seed) each frame, and the live `position`/`heading`/`scale`
|
||||
become a per-frame model matrix at draw time).
|
||||
- `app/` — browser glue.
|
||||
- `main.ts` — game loop: input, sim, preset switching, per-frame culling, then
|
||||
the non-blocking pump (`renderer.dispatch`/`done`) + `present` (GPU/CSS
|
||||
upscale) + a multi-line frame HUD (`work + present` critical-path ms, vsync
|
||||
interval, visible chunks / LOD-aware tri count — the real profiler in play).
|
||||
Also owns the **mob sim**: each frame it steps `Mob.update` for every mob,
|
||||
rebuilds the near-player mob colliders into `level.colliders`, and culls the
|
||||
mobs (`visibleMobs`) so only the visible transforms get dispatched.
|
||||
- `renderer.ts` — the render driver. When the page is cross-origin-isolated it
|
||||
runs a pool of `render-worker.ts` threads (`MAX_WORKERS`) over a
|
||||
`SharedArrayBuffer` framebuffer, each owning a disjoint row band, synced by a
|
||||
lock-free `Atomics` barrier; otherwise it renders inline. `dispatch`/`done`
|
||||
are non-blocking so the caller paces on rAF. `?bench=st|mt` A/Bs the paths.
|
||||
- `renderScene.ts` — `renderBand(fb, scene, …, y0, y1)`: the single source of
|
||||
render truth (sky + room + culled chunks + sprite + quantize for a row band).
|
||||
Used full-height by the inline path, per-band by each worker. `Scene` bundles
|
||||
the meshes/textures so it clones to a worker whole.
|
||||
are non-blocking so the caller paces on rAF. Per-frame inputs ride shared
|
||||
arrays: camera/matrix/visible-chunk list, plus the visible **mob transforms**
|
||||
(`mobState`, count in the `MOBVIS` control slot). `?bench=st|mt` A/Bs the paths.
|
||||
- `renderScene.ts` — `renderBand(fb, scene, …, mobDraws, …, y0, y1)`: the single
|
||||
source of render truth (sky + room + culled chunks + sprite + roaming mobs +
|
||||
quantize for a row band). Used full-height by the inline path, per-band by each
|
||||
worker. `Scene` bundles the static meshes/textures (incl. the two canonical mob
|
||||
meshes) so it clones to a worker whole; each mob is drawn double-sided through
|
||||
its own `viewProj × Mat4.compose(...)` model matrix, and `visibleMobs`
|
||||
frustum-culls the moving mobs per frame.
|
||||
- `assets.ts` — load `/assets/*.png` → `Texture` (zero-copy; ImageData bytes
|
||||
are already the `Color` layout).
|
||||
- `level.ts` — builds the playground: a flat stone-floored room (three thick
|
||||
walls via `slab`, north side open) always drawn, in the center of a big grassy
|
||||
`Terrain` world (~20x across). Props are placed first (`placeTrees` /
|
||||
`placeBoulders` / `placeBushes` / `placeFlowers` → instance lists + colliders;
|
||||
`TREE_/BOULDER_/BUSH_/FLOWER_COUNT`/`_SEED`/`_REACH`), then `buildChunks` bakes
|
||||
`TREE_/BOULDER_/BUSH_/FLOWER_COUNT`/`_SEED`/`_REACH`) and the roaming mobs
|
||||
scattered (`placeMobs`; `FROG_/BEE_COUNT`, `MOB_SEED`, `MOB_REACH` — mobs move,
|
||||
so they carry no baked colliders), then `buildChunks` bakes
|
||||
terrain + props into a `CHUNK_GRID` x `CHUNK_GRID` grid of `Chunk`s (each =
|
||||
per-texture meshes grass/bark/leaf/needle/rock/flowers + a tight AABB) that
|
||||
`main` frustum-culls; bushes fold into the leaf mesh, flowers get their own
|
||||
|
|
@ -118,16 +134,19 @@ rules live in `.agents/rules/*.md`.
|
|||
`#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/grass/bark/leaf/needle/rock/flower/wall/crate/npc`
|
||||
- `assets/` — generated `floor/grass/bark/leaf/needle/rock/flower/wall/crate/npc/frog/bee`
|
||||
PNGs (`floor` = room stone, `grass` = outdoor ground, `bark`/`leaf`/`needle` =
|
||||
tree trunk/oak/spruce, `rock` = boulders, `flower` = 2x2 bloom-color atlas).
|
||||
tree trunk/oak/spruce, `rock` = boulders, `flower` = 2x2 bloom-color atlas,
|
||||
`frog`/`bee` = mob skin atlases: frog green + eye tone; bee stripe bands +
|
||||
head-dark + wing-pale regions).
|
||||
Swap for real art anytime;
|
||||
filenames are the contract.
|
||||
- `server/` — Bun server stub. `shared/` — isomorphic slot.
|
||||
|
||||
## Frame pipeline (`app/main.ts` `tick`)
|
||||
|
||||
`Player.update` → build `Camera` → `Camera.viewProjection` → `visibleChunks`
|
||||
`Mob.update` (all mobs) + rebuild near-player mob colliders → `Player.update` →
|
||||
build `Camera` → `Camera.viewProjection` → `visibleChunks` + `visibleMobs`
|
||||
(frustum-cull, once on the main thread) → `renderer.dispatch` (non-blocking) →
|
||||
next rAF: `renderer.done()` ? `present` : skip this vsync. Frame N is presented
|
||||
while N+1 is dispatched; the pump never blocks or async-awaits, so it can't
|
||||
|
|
@ -148,8 +167,9 @@ floor/walls/crate (room, always) → for each visible `Chunk` draw grass, then
|
|||
per chunk via the pure `chunkFar` test (dist² from camera to the chunk AABB vs
|
||||
`lodDistance²`) — either the full rock/bark/leaf/needle + flowers, or the cheap
|
||||
`rockFar/barkFar/needleFar/leafFar` impostor meshes (foliage detail dropped).
|
||||
All backface-culled except double-sided flowers → `Sprite.billboard(npc)`
|
||||
→ `Framebuffer.quantize`. `chunkFar` is pure (camera + baked bounds + config
|
||||
All backface-culled except double-sided flowers → `Sprite.billboard(npc)` →
|
||||
the roaming mobs (each: shared local mesh × its `Mat4.compose` model matrix,
|
||||
double-sided) → `Framebuffer.quantize`. `chunkFar` is pure (camera + baked bounds + config
|
||||
only), so every worker band picks the same LOD for a chunk → no horizontal seam.
|
||||
Multi-threaded: N workers each run `renderBand` over
|
||||
their band of the shared framebuffer in parallel; single-threaded: one call over
|
||||
|
|
@ -306,6 +326,7 @@ job tmp dir, not the repo.
|
|||
## Roadmap / not yet built
|
||||
|
||||
In-browser RenderConfig slider panel; mipmaps; `painter` depth mode; gouraud
|
||||
lighting; more cloud types; more props / a weapon / moving enemies. `shared/`
|
||||
lighting; more cloud types; more props / a weapon; more mob kinds + smarter mob
|
||||
behavior (they wander + block/stand-on today, but don't yet react to the player). `shared/`
|
||||
is nearly empty. The FPS meter is static HTML + `textContent` writes only — no
|
||||
DOM-built UI yet (deliberate).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue