From 47c521f9b8d834e6bdcd629e3e2c7536dbab4ee8 Mon Sep 17 00:00:00 2001 From: Errilaz Date: Tue, 4 Aug 2026 03:11:46 +0200 Subject: [PATCH] docs: save memory --- AGENTS.md | 146 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + tsconfig.test.json | 2 +- 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6b5164a --- /dev/null +++ b/AGENTS.md @@ -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`. +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). diff --git a/package.json b/package.json index ea52e39..e3c86ee 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "meat", "private": true, "license": "MIT", + "type": "module", "author": { "name": "Sigitex", "url": "https://sigitex.com" diff --git a/tsconfig.test.json b/tsconfig.test.json index 281b538..2ea68bd 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.base.json", "references": [ { - "path": "./tsconfig.src.json" + "path": "./tsconfig.app.json" } ], "compilerOptions": {