From 0e595d8b17b4fb537927b047346fab92e4f109bd Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:16:20 +1000 Subject: [PATCH] Document development workflows --- docs/README.md | 1 + docs/development.md | 336 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 docs/development.md diff --git a/docs/README.md b/docs/README.md index de11f10..96fbc40 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ MeatSlicer is an endless top-down arena roguelite. You are the Butcher, descendi | [Enemies](enemies.md) | All 11 enemy types, elites, drops, wave and scaling formulas | | [Bosses](bosses.md) | All 9 bosses: rotation, scaling, full attack patterns, counters | | [Debug Console](dev-debug.md) | In-game cheat/inspection toolset (backtick `` ` `` key, `--dev` flag): all 8 tabs, run tainting, stat-rebuild internals | +| [Development Guide](development.md) | Architecture, build/test workflow, Electron packaging, asset and audio pipelines, versioning, releases, and Git practices | | [Changelog](CHANGELOG.md) | Release history by version | ### Quick numbers diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..12f027e --- /dev/null +++ b/docs/development.md @@ -0,0 +1,336 @@ +# Development Guide + +This guide consolidates the durable project knowledge used to develop, test, +package, and maintain MeatSlicer. When this guide and the implementation +disagree, the implementation and configuration files are the source of truth. + +## Architecture + +MeatSlicer is an endless top-down arena roguelite built with plain JavaScript +and HTML5 canvas. It has no browser framework, module system, or bundler. + +`index.html` loads the scripts in `js/` as globals in a fixed order: + +1. `utils.js` +2. `state.js` +3. `input.js` +4. `sfx.js` +5. `sfxbank.js` +6. `audio.js` +7. `sprites.js` +8. `particles.js` +9. `weapons.js` +10. `bullets.js` +11. `pickups.js` +12. `items.js` +13. `perks.js` +14. `enemies.js` +15. `bosses.js` +16. `rooms.js` +17. `player.js` +18. `hud.js` +19. `help.js` +20. `debug.js` +21. `main.js` + +Because the scripts share globals, changing their order can break initialization +or runtime dependencies. + +### Runtime details + +- The canvas uses a fixed 960×640 logical resolution and is CSS-scaled with + pixelated image rendering. +- There is no camera. Rooms use variable `G.arena` inset bounds for full halls, + horizontal or vertical halls, chambers, and pits. +- Persistence uses `localStorage`. Current keys include `meatslicer_best`, the + music and SFX volumes, `meatslicer_autoperk`, `meatslicer_pressure_dial`, and + `meatslicer_hud_alpha`. +- `powerScore()` weights perks at 0.5 and item tiers at 1.0 when scaling enemies. +- `G.pressure` is an adaptive difficulty multiplier, clamped from 0.60 to 2.00. + The title-screen pressure dial ranges from −10 to +10. +- `procOnHit` in `js/enemies.js` is the shared on-hit payload router used by all + weapons. + +### Cache busting + +Asset and script URLs use a `?v=N` cache suffix. When shipping changed assets or +scripts, bump the value together in: + +- `index.html` +- `js/sprites.js` +- `js/sfxbank.js` + +## Repository layout + +| Path | Purpose | +|---|---| +| `index.html` | Browser entry point and script load order | +| `js/` | Game systems and content definitions | +| `css/` | Browser and canvas presentation | +| `assets/` | Shipped sprites, animation sheets, UI art, and sound effects | +| `assets/raw/` | Generation intermediates; ignored and excluded from packages | +| `mp3-music/` | Music and boss tracks | +| `desktop/` | Electron main process, preload, and verification harness | +| `tools/` | Static server and visual asset-generation tools | +| `test/smoke.js` | Headless integration and regression suite | +| `docs/` | Player, content, release, and developer documentation | +| `build/icon.png` | Windows application icon | + +## Build, run, and test + +### Browser + +On Windows: + +```bat +play.bat +``` + +This runs the allowlisted static server in `tools/serve_game.py` and opens +. The equivalent manual command is: + +```sh +python tools/serve_game.py --port 8123 +``` + +Use `play-dev.bat` or append `?dev=1` to enable the debug console. + +### Electron + +```sh +npm install +npm start +``` + +Use `npm start -- --dev` to enable the debug console in Electron. + +The lockfile currently resolves Electron 40.10.2 and electron-builder 25.1.8. +These versions support the development machine's Node 20 environment; review +Node compatibility before upgrading either package. + +### Tests + +Run the complete smoke suite before every commit: + +```sh +npm test +``` + +This is equivalent to `node test/smoke.js` and must finish with +`ALL CHECKS PASSED`. The suite asserts exact balance values and scaling +formulas, so update expectations deliberately when changing game balance. + +For a quick JavaScript syntax check: + +```sh +node --check js/.js +``` + +### Windows distributable + +```sh +npm run dist +``` + +electron-builder writes a versioned zip and an unpacked application under +`dist/`. The version comes from `package.json`. `dist/` and `node_modules/` are +ignored; commit package manifests, not generated dependencies or builds. + +## Electron packaging + +The Electron shell serves the unchanged browser game through a privileged +`app://` protocol. + +### Package contents + +`electron-builder.yml` uses a strict allowlist for the desktop shell, HTML, +CSS, JavaScript, shipped assets, music, documentation, and `package.json`. +`assets/raw/**` must remain explicitly excluded because it contains large +generation intermediates that are not part of the game. + +The application deliberately uses `asar: false`. + +### Custom protocol requirements + +The `app://` protocol is registered as standard, secure, fetch-capable, and +stream-capable. Streaming is required for looping MP3 audio. The handler must: + +- call `decodeURIComponent`, because music filenames contain spaces; +- use `path.join` rather than `path.resolve`, because URL pathnames begin with + `/`; and +- preserve traversal protection before serving a requested path. + +### Window behavior + +- Borderless fullscreen starts at 1280×832 restore geometry. +- F11 toggles fullscreen. +- F12 and Ctrl+Shift+I toggle developer tools. +- Ctrl+R reloads. +- The application menu is removed. +- A single-instance lock prevents duplicate app instances. + +### winCodeSign extraction issue + +electron-builder's winCodeSign archive can fail to extract on Windows when the +current account cannot create symlinks. The local workaround is a pre-seeded +cache under: + +```text +%LOCALAPPDATA%\electron-builder\Cache\winCodeSign\winCodeSign-2.6.0 +``` + +If the cache must be rebuilt, extract the archive while excluding its `darwin` +entries, or enable an environment that permits symlink creation. + +### Packaged-app verification + +The optional harness in `desktop/main.js` drives a run, loads the sound bank, +captures diagnostics, and exits. In PowerShell: + +```powershell +$env:MS_VERIFY_OUT = "$env:TEMP\meatslicer-verify.json" +Start-Process "dist\win-unpacked\MeatSlicer.exe" -Wait +Get-Content $env:MS_VERIFY_OUT +``` + +Set `MS_VERIFY_SHOT` to a PNG path to capture a screenshot as well. The same +variables work with `npx electron .`. A valid result has an empty `log`, no SFX +failures, and an SFX buffer count matching `SfxBank.FILES` and the files on +disk; the exact count can change as audio is added. + +The Windows build is not code-signed, so downloaded copies may trigger a +SmartScreen warning. Code signing, installers, automatic updates, and +macOS/Linux packaging remain future work. + +## Visual asset pipeline + +The project has two visual asset tiers. AI-generated stills are the production +tier; procedural drawings are fallbacks and composition guides. + +### Production sprites + +`tools/gen_assets.py` generates production stills through OpenRouter's image +endpoint and derives animation sheets from them. + +```sh +python tools/gen_assets.py --list +python tools/gen_assets.py --quality medium +python tools/gen_assets.py --force --quality medium +``` + +Authentication is read from `OPENROUTER_API_KEY` in the process environment or +the user-only `~/.config/MeatSlicer/.env`, outside the served game directory. +Never commit credentials. + +The generator: + +- attaches `assets/raw/guides/style_reference.png` as a style reference; +- requests isolated sprites on `#FF00FF` backgrounds; +- flood-keys the border to alpha; +- crops, centers, resizes, and quantizes output to the shared palette; and +- records generation metadata in `assets/raw/openrouter_manifest.json`. + +`render_sheets()` calls `render_actor_sheet(name, use_existing=True)`, ensuring +animation sheets are derived from the production still. + +### Procedural fallback sprites + +`tools/draw_sprites.py` contains procedural fallback drawing functions. It is +useful for guides, runtime fallbacks, and derived effects. + +> **Destructive-tool warning:** Never run `draw_sprites.py` without an explicit, +> narrow name filter. Its main routine writes every matching procedural still +> and rebuilds sheets with `use_existing=False`, which can overwrite production +> AI stills and their derived atlases. + +Prefer `gen_assets.py` for shipped characters. + +### Actor animation atlases + +- Boss frames are 128×128. +- Player frames are 96×96. +- Normal enemy frames are 64×64. +- Atlases use eight columns and eight compass directions. +- Cell lookup is `offset + direction * frames + frame`. +- Source coordinates are `(cell % 8) * frameSize` and + `floor(cell / 8) * frameSize`. +- Actions match `ACTOR_ANIMS` in `js/sprites.js`: idle, move, attack, hit, and + death. +- Player legs use a separate eight-frame strip. + +To add a production animated character: + +1. Add its `Spec` to `SPECS` in `tools/gen_assets.py`. +2. Add its name to `ACTORS` in `tools/draw_sprites.py`. +3. Run `python tools/gen_assets.py --quality medium`. +4. Register the still and sheet in `SPRITE_MANIFEST` as required. +5. Bump the shared cache version before shipping. + +## Audio pipeline + +Sound effects are generated with the ElevenLabs audio tooling. There is no +repository-local generation script, so requests should use a per-file manifest +with the exact target filename, sound description, and style brief. + +Generated files may initially land in the project root, and generated filenames +may replace underscores with hyphens. Move and rename each result into +`assets/sfx/` using the underscore filename expected by the game. + +To wire a new sound: + +1. Generate the clip using its exact manifest and style description. +2. Move and rename it into `assets/sfx/`. +3. Register it in `SfxBank.FILES` in `js/sfxbank.js`. +4. Run `npm test` to verify every registered file exists and is non-empty. +5. Bump the shared cache version before shipping. + +`Sfx.sample()` and `Sfx.ui()` can fall back to procedural audio when a sample is +missing, but registering a missing file in `SfxBank.FILES` fails the smoke test. + +## Versioning and releases + +Versions use `X.Y.Z` with project-specific rules: + +- `X` is the main version and is changed manually by the project owner only. +- `Y` marks a major feature or content expansion. +- `Z` marks a fix, minor addition, balance tweak, or adjustment. +- Components do not roll over automatically at 9; values such as `0.42.677` + are valid. + +The version lives in `package.json` and determines the packaged artifact name. + +When changing the version: + +1. Update `package.json`. +2. Bump the shared `?v=N` cache value in `index.html`, `js/sprites.js`, and + `js/sfxbank.js`. +3. Run `npm test` and commit the source changes. +4. Run `npm run dist`. +5. Verify the packaged executable with `MS_VERIFY_OUT`. + +## Git workflow + +Every completed change should be committed: + +1. Run `git status` and review `git diff`. +2. Stage only intended files. +3. Never commit credentials, local environments, raw generation data, + dependencies, or build output. +4. Run `npm test`. +5. Commit with a concise imperative summary matching the repository history. + +The primary branch is `main`, and `origin` points to the public GitHub +repository. The local repository uses the GitHub account's noreply identity for +new commits. + +## Keeping project knowledge current + +Architecture, commands, workflows, naming conventions, and costly gotchas +should be documented when they become durable project knowledge. Keep one topic +per section, record why a constraint exists, and update stale guidance instead +of adding contradictory instructions. + +Player-facing mechanics belong in the relevant files under `docs/`. Keep those +documents synchronized with gameplay changes. This development guide should be +updated whenever the build, packaging, asset, audio, release, or contribution +workflow changes.