From 582eebcfcfd15ff5cb804ddcac83384ef1e0d024 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Wed, 15 Jul 2026 18:24:29 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20make=20the=20registry=20true=20?= =?UTF-8?q?=E2=80=94=20one=20false=20=E2=9C=85,=20a=20number=20collision,?= =?UTF-8?q?=20and=20README=20rot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An audit of every SH-* ticket against the code found the docs wrong in BOTH directions. Nothing here changes behaviour; it stops the docs lying. SH-043 was the registry's only false โœ…. It claimed "shipped โ€” src/strings.ts"; that file has NEVER existed and there is no STRINGS reference in src/ (verified, not inferred). docs/aaa-round-1.md had the honest version the whole time. Now ๐ŸŸก with a part-by-part table: shake slider + colourblind cues + captions shipped; the localization table and rebindable keys have NOT. SH-027 v2 is closed โ€” its header said "gated on EN-033" for weeks after EN-033 landed. The gate was imaginary; the remaining gap (pitch) is now closed by EN-039 + PR #22. SH-046 was a NUMBER COLLISION, and mine: I wrote the boot sequence as "SH-046" in code while the registry already used SH-046 for character fidelity. The boot work is renumbered SH-049 across boot.ts / main.ts / menu.ts / combat.ts โ€” comments only, no behaviour. SH-047 (body turns to face movement) and SH-048 (crouch) existed only as code comments and are now registered, SH-048 explicitly ๐ŸŸก with its capsule-resize gap stated rather than implied. README: it said Esc QUITS (it pauses โ€” and until yesterday it really did quit, from anywhere, including out of the settings screen), "five alien kinds / two weapons" (seven and four), and "World data is hardcoded in TS until JSON.parse is fixed" โ€” the world has loaded from JSON at runtime since the baker was deleted. Controls table now documents aim/sprint/crouch/dodge, which shipped undocumented. CLAUDE.md contradicted itself on the same JSON.parse point (ยง144 records it fixed; ยง200 told you to work around it) and repeated the wrong counts; both corrected, and it now says plainly that only ONE arena is playable. Claude-Session: https://claude.ai/code/session_01J1UWgMcrTNvwWeXcJ1T3Fp --- CLAUDE.md | 15 ++++--- README.md | 38 +++++++++++------ docs/tickets.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++- src/boot.ts | 2 +- src/combat.ts | 2 +- src/main.ts | 6 +-- src/menu.ts | 2 +- 7 files changed, 151 insertions(+), 25 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 40e7033..a545555 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,9 +6,11 @@ Notes for AI coding assistants working on this repo. The full picture is in `README.md`. This is a Perry-compiled TypeScript **third-person shooter** (over-the-shoulder orbit camera โ€” TPS, not -FPS) built on the Bloom engine. Five alien kinds, three waves, two -weapons, textured arena, skeletal-animated enemies, ambient music โ€” -runnable with `perry compile src/main.ts -o main && ./main`. +FPS) built on the Bloom engine. Seven alien kinds (two RANGED), three +waves, four weapons, ONE playable arena (see SH-040 โ€” the manifest lists +only `arena_02`; the pipeline takes more, the content does not exist), +textured arena, skeletal-animated enemies, ambient music โ€” runnable with +`perry compile src/main.ts -o main && ./main`. ## Where things live @@ -197,8 +199,11 @@ editor, hit Play, and it is there โ€” no bake step. for hot-loop state. `.push()` can produce arrays whose `.length` reports as the literal initial size. - Return `{ ok: boolean, error: string }` instead of throwing. -- Prefer hardcoded constants over JSON-loaded config until the Perry - JSON.parse bug is resolved. +- JSON-loaded config is fine โ€” the Perry `JSON.parse` bug that forced + hardcoded constants is FIXED (verified on 0.5.1208; see the world + pipeline section above, which reads the whole level from JSON at + runtime). What still binds is WHERE you parse: at load, never on a + per-frame path. - Data crossing the FFI on a hot path is numbers, not delimited strings (perry-quirks #5). Strings that do cross are drawn whole, never parsed. diff --git a/README.md b/README.md index 5f36f43..4cdbc4d 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,22 @@ Controls: | Input | Action | |------------------|-------------------------------------------| -| WASD | Move | +| WASD | Move (the body turns to face where it moves) | | Mouse | Look | | Space | Jump | | Left mouse | Fire (rifle = full-auto, blaster = tap) | -| 1 / 2 | Switch weapon (rifle / blaster) | +| Right mouse | Aim down sights (hold, or toggle โ€” settings) | +| Shift | Sprint (hold, or toggle โ€” settings) | +| C | Crouch (hold) | +| Ctrl | Dodge | +| 1 / 2 / 3 / 4 | Switch weapon (rifle / blaster / chaingun / cannon) | +| Q | Cycle weapon | | R | Reload / restart after death | | Tab | Toggle cursor capture | | F3 | Profiler overlay (per-pass CPU/GPU ยตs) | -| F12 | Screenshot to `shooter_.png` | -| Esc | Quit | +| F5โ€“F9 | Render-pass debug toggles / path tracing | +| F12 | Screenshot (engine-native; see EN-038) | +| Esc | Pause (menu) โ€” **not** quit; QUIT is a menu row | ### On an iPhone @@ -54,8 +60,10 @@ actually rests on. Requires a paid Apple developer account (Perry's signing flow is App Store Connect API-only). See `CLAUDE.md` for the provisioning details. -Survive three waves โ€” dretches, mantises, marauders, dragoons, and a -tyrant. Ammo crates respawn at the four arena corners. +Survive three waves โ€” dretches, mantises, marauders, dragoons, a tyrant, and the +advanced marauder and dragoon (seven kinds; the two advanced ones are RANGED, so +closing the distance stops being the universal answer). Ammo crates respawn at +the four arena corners. ## Prerequisites @@ -135,12 +143,18 @@ Short version of findings accumulated while building this: are plain inverses of the resulting world matrices. Conjugating every joint individually (a reasonable-looking alternative) conflicts with bloom's Y-axis yaw that the renderer applies on top of skin matrices. -- **Perry 0.5.158 has three codegen bugs** we worked around: reachable - `throw new Error` segfaults at startup; object-shorthand returns - (`return { ok, errors }`) can corrupt boolean fields; `JSON.parse` - arrays have no populated `.length`. World data is hardcoded in TS - until JSON.parse is fixed. Details in - [docs/perry-quirks.md](docs/perry-quirks.md). +- **Perry has a running list of codegen bugs** we work around โ€” and the list + has moved, so treat any specific version number here as history rather than + as current advice. Fixed since: reachable `throw new Error` segfaulting at + startup, and `JSON.parse` arrays reporting no `.length`. **World data is NOT + hardcoded any more** โ€” `src/world-runtime.ts` reads + `assets/worlds/*.world.json` at startup via the engine's `loadWorld`, there is + no `src/generated/`, and there is no bake step. Still live: object-shorthand + returns can corrupt fields; never `split()`/`parseFloat()` an FFI string on a + per-frame path; some small numeric functions in imported modules silently + compile to a constant. The authority is + [docs/perry-quirks.md](docs/perry-quirks.md) โ€” this bullet is a pointer, not a + second copy of it. - **Bloom's deferred pipeline had a debug-green hardcode** in `fs_main_3d` that made all 3D geometry render solid green under Perry-compiled builds on macOS. Fixed upstream in engine commit diff --git a/docs/tickets.md b/docs/tickets.md index 318860a..105701a 100644 --- a/docs/tickets.md +++ b/docs/tickets.md @@ -261,7 +261,16 @@ is what makes 30 seconds of footage read as AAA. --- -## SH-027 โ€” Real weapon models โœ… *(shipped โ€” Unvanquished MD3 โ†’ glTF; v2 hand-bone attach still gated on EN-033)* +## SH-027 โ€” Real weapon models โœ… *(shipped; v2 complete 2026-07-15 โ€” hand-bone attach + the gun tilts with aim)* + +> **v2 closed 2026-07-15.** This header claimed v2 was "still gated on EN-033" +> long after EN-033 landed โ€” the weapon has ridden the `tag_weapon` hand joint +> via `jointWorld()` since then, with the fixed offset only a fallback. The last +> real gap was pitch: `drawModelRotated` takes a single Y rotation, so the barrel +> stayed level while you aimed up or down. **EN-039** (`drawModelTransform`, +> engine PR #100) landed and this now feeds it the same forward vector the +> muzzle, tracer and shot ray use โ€” so the gun and the bullet agree by +> construction. Shooter PR #22. **Why:** both weapons are **grey `drawCube` primitives** (`main.ts:2795-2836`). This is the single most visible "placeholder" @@ -842,7 +851,23 @@ frame time still โ‰ฅ 35 fps on the dev box at 4K/TSR. --- -## SH-043 โ€” Accessibility & localization scaffolding โœ… *(shipped โ€” `src/strings.ts`, shake slider, colourblind-safe hit cues)* +## SH-043 โ€” Accessibility & localization scaffolding ๐ŸŸก *(partial โ€” see below; the โœ… was wrong)* + +> **Corrected 2026-07-15.** This header read +> *"โœ… shipped โ€” `src/strings.ts`, shake slider, colourblind-safe hit cues"*. +> **`src/strings.ts` has never existed** and there is not one `STRINGS` +> reference in `src/` โ€” verified, not inferred. `docs/aaa-round-1.md` had the +> honest version all along ("Partial. Localization table not done."). This was +> the registry's only false โœ…, and it is exactly the kind that costs someone a +> day: you go looking for a file the docs promise and it isn't there. +> +> | Part | Status | +> |---|---| +> | Camera-shake slider | โœ… shipped, wired to `FEEL.setShakeScale` | +> | Colourblind-safe hit cues | โœ… shipped โ€” and **unconditional**: the white flash + tick and the damage arc REPLACED the colour-only versions. The `COLORBLIND CUES` settings row was therefore switching nothing and was removed 2026-07-15. | +> | Telegraph captions | โœ… shipped 2026-07-15 โ€” the dragoon's roar IS the dodge window; a player who cannot hear it was told nothing. | +> | **Localization table (`strings.ts`)** | โŒ **NOT DONE** โ€” every user-facing literal is still inline. | +> | **Rebindable keys** | โŒ **NOT DONE** โ€” the bind list is fixed. | **Why:** all-or-nothing camera shake, red-only damage cues, hardcoded English literals, fixed keybinds. Cheap now, expensive to retrofit. @@ -1254,3 +1279,85 @@ glTF, and redistributable in a GPL repo**. **Reconsider first, honestly:** with the uplift landed, the battlesuit now reads as real armour. It may no longer be the weakest thing in frame. Look at it before spending on a replacement. + +--- + +## SH-047 โ€” The body turns to face where it moves โœ… *(shipped 2026-07-15)* + +**Why:** the player model always faced camera-forward, so strafing read as the +character sliding sideways while staring ahead โ€” and the forward walk clip's feet +pointed the wrong way relative to the travel. + +**What shipped.** The facing offset is `atan2(moveX, -moveZ)` โ€” literally the +movement direction relative to the camera. The requested angles fall straight out +of it because WASD already quantises movement: A/D โ†’ 90ยฐ, the W-diagonals โ†’ 45ยฐ, +W or idle โ†’ forward. Eased along the shortest arc (so S+A โ†’ S+D turns the near +way), and kept as an offset RELATIVE to the camera so looking around does not drag +the body with it. Aiming down sights forces forward โ€” you are pointing the gun, +not walking. + +Because the body faces its travel, the forward walk/run/crouch clips are always +the correct clip: the directional strafe clips the model ships (`run_left`, +`crouch_right`, โ€ฆ) are not needed. + +**Verified** with a forced strafe + the HUD readout: D moved the player to world ++X with the body in right-facing profile; W+D moved it on a true 45ยฐ diagonal. + +--- + +## SH-048 โ€” Crouch ๐ŸŸก *(shipped 2026-07-15 โ€” speed + pose; capsule NOT resized)* + +**Why:** the only speed states were walk and sprint. Crouch is the third โ€” the +deliberate, quiet one. + +**What shipped.** Hold **C** (Ctrl was already dodge). `CROUCH_MUL` 0.45 takes +4.5 โ†’ ~2.0 m/s, and the crouch clips play (`crouch` idle / `crouch_forward` +moving โ€” one clip covers every heading thanks to SH-047). Crouch **wins over +sprint**: you cannot do both, and holding crouch is the more deliberate intent. + +**Outstanding, stated plainly:** the physics capsule is **not** resized, so crouch +does not shrink the hitbox and does not let you fit under low geometry. Harmless +today โ€” the arena has none โ€” but it means crouch is not yet a *tactical* choice, +only a movement one. Needs `setCharacterShape`. No touch or gamepad binding yet. + +**Verified** through the real C key (not a forced flag): 4.50 โ†’ **2.03 m/s**, +`crouch=1`, mutually exclusive with sprint. + +--- + +## SH-049 โ€” Front end: splash, loading screen, main menu โœ… *(shipped 2026-07-14)* + +> **Numbering note.** This work was written as "SH-046" in the code and collided +> with the existing SH-046 (character fidelity) โ€” two different tickets, one +> number. Renumbered to SH-049 on 2026-07-15; the code comments now match. + +**Why:** boot was a **black, frozen window for ~8 seconds**, then a snap to a +"press any key" title. That freeze is the first thing a player ever experiences +and it read as a hang. + +**What shipped** (`src/boot.ts`, `MENU_MAIN` in `menu.ts`): +- **Splash** โ€” owns its own loop, so it is the one part of boot that animates + smoothly. Skippable; a splash you cannot skip is one you resent by the third + launch. +- **Loading screen with REAL progress.** There is no async loader (EN-032), so + nothing pretends: `bootStage()` is called between groups of synchronous loads + and draws a frame each time. Weights are **measured milliseconds**, not + guesses โ€” the bar would otherwise lurch, which reads as a broken loader. +- **Main menu** โ€” PLAY / LEVEL SELECT / SETTINGS / QUIT over the live arena, + reusing `menu.ts`'s existing focus model rather than being a second screen to + keep working. Settings and level select were previously reachable only from + *pause*, i.e. only after starting a run. +- The gameplay HUD is now gated on being in a run. It used to draw the health bar + and ammo over the menu โ€” the tell that the "title screen" was just the game + with input switched off. + +**What this measured, and it is the useful part:** boot is ~7.9 s of loading and +**`the aliens` is 5.5 s of it (61%)** โ€” `initEnemyPool()` loads a ModelAnimation +per enemy SLOT, not per kind. That is the one number to attack if boot time is +ever worth fixing. + +**Also fixed here:** an unconditional `if (isKeyPressed(Key.ESCAPE)) break;` at +the bottom of the frame loop โ€” a dev-quit binding that predated the pause menu. +It fired on every ESC regardless of state, so Escaping out of the settings screen +*killed the process*. ESC now means pause in a run and back in a menu; QUIT is a +menu row. diff --git a/src/boot.ts b/src/boot.ts index 9f70428..a79600a 100644 --- a/src/boot.ts +++ b/src/boot.ts @@ -1,4 +1,4 @@ -// SH-046 โ€” the boot sequence: splash, loading screen, and the fade into the menu. +// SH-049 โ€” the boot sequence: splash, loading screen, and the fade into the menu. // // The game used to open a black window, freeze for several seconds while every // GLB, sound and shader loaded on the main thread, and then snap to a title diff --git a/src/combat.ts b/src/combat.ts index 2bb1b14..ba22045 100644 --- a/src/combat.ts +++ b/src/combat.ts @@ -156,7 +156,7 @@ export function resetRun(): void { SCORE.beginWave(0); } -/// SH-046 โ€” leave the main menu and drop into the arena. This was inline in the +/// SH-049 โ€” leave the main menu and drop into the arena. This was inline in the /// old "press any key" title screen; the main menu's PLAY row calls it now. /// /// The two selectWeapon() calls look redundant and are not: selecting a second diff --git a/src/main.ts b/src/main.ts index 414c6c8..aa7c15b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -174,7 +174,7 @@ const cliDbgOff = cliArg('--dbg-off'); FEEL.setShakeScale(SET.get(SET.SET_SHAKE)); initMenus(); -// ---- SH-046: the boot sequence --------------------------------------------- +// ---- SH-049: the boot sequence --------------------------------------------- // Everything below this line until `bootOutro()` is the game LOADING, and until // now it did that behind a black, frozen window for several seconds. The splash // runs first (it owns its own loop, so it is the one part of the boot that can @@ -1034,7 +1034,7 @@ let aitestDone = false; // // MUST be false in shipped builds: it suppresses every enemy wave. // ---- MENUTEST harness (temporary diagnostic) -------------------------------- -// SH-046 verification. Drives the front-end through the REAL input path +// SH-049 verification. Drives the front-end through the REAL input path // (injectKeyDown โ€” OS-synthesised keys do not reach this game) and records what // the menu actually did: // @@ -1774,7 +1774,7 @@ while (!windowShouldClose() && !aitestDone && !animDbgDone) { // startRun() bypasses the menu's ACT_PLAY handler, and that handler is // the only place that sets cursorLocked โ€” without this the fire path // (gated on cursorLocked) never triggers and the harness silently - // captures a fight with no shots. Broken since SH-046 put a real menu + // captures a fight with no shots. Broken since SH-049 put a real menu // in front of the game. cursorLocked = true; SCORE.resetScore(); diff --git a/src/menu.ts b/src/menu.ts index 56238bb..3e76b92 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -30,7 +30,7 @@ export const MENU_NONE = 0; export const MENU_PAUSE = 1; export const MENU_SETTINGS = 2; export const MENU_LEVELS = 3; -/// SH-046 โ€” the front door. Same rows, same focus model, same draw code as the +/// SH-049 โ€” the front door. Same rows, same focus model, same draw code as the /// pause menu: a main menu that is a SEPARATE screen is a second thing to keep /// working, and it always rots. export const MENU_MAIN = 4;