docs: save memory

This commit is contained in:
Dan Finch 2026-08-04 03:11:46 +02:00
parent 2c31bd82bf
commit 47c521f9b8
3 changed files with 148 additions and 1 deletions

146
AGENTS.md Normal file
View file

@ -0,0 +1,146 @@
# meat
A from-scratch browser **first-person-shooter game engine** with a configurable
**PS1 aesthetic**. Everything is hand-written: a **software rasterizer** draws
textured triangles into a low-res CPU framebuffer, then Canvas2D blits it
upscaled. No WebGL/WebGPU, no game-engine dependencies.
This file is the durable project brief (checked in, auto-loaded). The per-topic
rules live in `.agents/rules/*.md`.
## Guiding decisions (the "why", not derivable from code)
- **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).
- **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.
- **2D assets only.** Sprites/billboards (PS1-style), **no 3D model loading**.
- **Engine is headless.** `engine/` has no DOM types and could run server-side;
all browser glue (canvas, input, image decode) lives in `app/`.
## Stack & tooling
- **Bun** runtime + `bun test`. **TypeScript 7** (native `tsc`), strict,
`moduleResolution: bundler`. **Vite 8** serves/builds the client.
- **oxc** toolchain: `oxlint` + `oxfmt` (no eslint/prettier).
- commitlint + husky (Conventional Commits), OpenSpec change workflow,
forge-sync (Forgejo). Templates generated by `regime` from a `sigitex:` source.
## Commands
- `bun start` — Vite dev server; open the printed URL. Edits hot-reload.
- `bun run build` — production build to `dist/`.
- `bun run assets` — regenerate the placeholder PNGs in `/assets`.
- `bunx tsc --build tsconfig.app.json` — **typecheck the app+engine graph. Use
this**, not `bun run check` (see Caveats).
- `bunx oxlint engine app` — lint.
- `bun test` — tests (none yet).
- `bun run serve` — Bun server (`server/server.ts`, a stub for now).
## Layout
- `engine/` — headless engine, consumed by `app/` via tsconfig project ref.
- `math/``Vec2`, `Vec3`, `Mat4` (column-major, OpenGL-style; verified).
- `render/``Color` (packed RGBA, little-endian = canvas ImageData order),
`Framebuffer` (Uint32 color + Float32 1/w depth; `quantize` = color-depth +
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).
- `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`.
- `player.ts` — feet-cylinder player: gravity/jump + circle-vs-AABB/-circle
collision.
- `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;
filenames are the contract.
- `server/` — Bun server stub. `shared/` — isomorphic slot.
## Frame pipeline (`app/main.ts` `frame`)
`Player.update` → build `Camera``Camera.viewProjection`
`Sky.render` (fills color + resets depth, replaces a clear) →
`Rasterizer.draw` 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).
## The look — where to tune
- **`engine/render/RenderConfig.ts`** — per-frame render dials + presets:
`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.
## Clouds
Procedural, moving, in the sky pass (`engine/render/Sky.ts`). Two styles picked
by a `CloudLayer` discriminated union `kind`:
- **`basicCumulus`** — flat hard-thresholded white puffs, 1 noise lookup/pixel.
- **`fancyCumulus`** — domain-warped + heightfield-shaded fake volume,
~5 lookups/pixel (pricier; watch the FPS meter).
Both are exported presets in `app/level.ts`; the active one is set in
`buildLevel`'s `sky.clouds`. Add new cloud types by extending the union and
branching in the cloud shader. Cost scales with sky resolution — fine at
`standard`, heavy at `clean` (mitigate: fewer fbm octaves or half-res sky).
## Controls
WASD move · mouse look (click canvas to pointer-lock) · **Space** jump ·
**1/2/3** switch look presets. FPS shown bottom-right.
## Code style (oxlint-enforced — match it)
No semicolons, double quotes, 2-space indent, trailing commas. **`type`, never
`interface`.** Function declarations (arrows allowed). Uppercase hex (`0xFF`).
Curly braces required on all `if`. No `void` operator. `T[]` not `Array<T>`.
Prefer `globalThis` over `window`. Domain behavior lives in a `type` +
matching `namespace` (see `Vec3`, `Color`, `Rasterizer`).
## Caveats
- No mipmaps, so distant textures shimmer (period-correct); far cloud/geometry
is hidden by fog.
## Debugging the renderer without a browser (key workflow)
The engine is pure, so you can **render headless to a PNG and inspect it**. A
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.
## 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/`
is nearly empty. The FPS meter is static HTML + `textContent` writes only — no
DOM-built UI yet (deliberate).

View file

@ -2,6 +2,7 @@
"name": "meat",
"private": true,
"license": "MIT",
"type": "module",
"author": {
"name": "Sigitex",
"url": "https://sigitex.com"

View file

@ -2,7 +2,7 @@
"extends": "./tsconfig.base.json",
"references": [
{
"path": "./tsconfig.src.json"
"path": "./tsconfig.app.json"
}
],
"compilerOptions": {