From c0d909eebdb9fc41e4bae52fda9b180ddadd1929 Mon Sep 17 00:00:00 2001 From: Quintine Date: Tue, 11 Aug 2026 19:06:36 +1000 Subject: [PATCH] release: bump game and memory versions to 0.7.0 --- docs/development.md | 373 -------------------------------------------- index.html | 44 +++--- js/sfxbank.js | 2 +- js/sprites.js | 2 +- package.json | 2 +- 5 files changed, 25 insertions(+), 398 deletions(-) delete mode 100644 docs/development.md diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index 26a934b..0000000 --- a/docs/development.md +++ /dev/null @@ -1,373 +0,0 @@ -# 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. - -**Memory version: 0.6.0** - -## 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, and automatic updates 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`. - -## Memory versioning - -The project knowledge ("memories") — this guide and the player-facing documents -under `docs/` — carries its own `X.Y.Z` version, tracked independently of the -game release version in `package.json`: - -- `X` — a major milestone. Changed by the project owner only; a routine update - or bump rule never raises it. -- `Y` — a major feature push: a large content expansion, a major mechanic, or a - significant restructure of the knowledge base. -- `Z` — a small addition, a bug fix, or a single new small feature (a new - gotcha, a corrected formula, a changed workflow, or the documentation sync - that follows a gameplay change). - -Components are independent counters and never roll over: `0.999.999` and -`0.42.677` are perfectly valid, and bumping `Z` on `0.999.999` gives -`0.999.1000`. Only `X` is exempt from automatic increases — it changes by -explicit human edit. - -The current version is the `**Memory version:**` stamp at the top of this -guide; that stamp is the single source of truth. - -When a change to durable knowledge lands (anywhere under `docs/`): - -1. Edit the stamp: bump `Z` for a small addition or fix, `Y` for a major push, - or — for a milestone — the project owner raises `X` by hand. -2. Run `npm test` (docs are not loaded by the game, so the suite is unaffected) - and commit with an imperative message naming the bump, e.g. - `docs: record Iron Lung synergy (memory 0.5.0 -> 0.5.1)`. -3. No `?v=` cache bump is needed — `docs/` is not served through `index.html` - script tags; it only ships inside packages. - -## 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. - -Keep the `**Memory version:**` stamp at the top of this guide current: any -durable-knowledge change bumps it per `## Memory versioning`. diff --git a/index.html b/index.html index 546184a..d78079a 100644 --- a/index.html +++ b/index.html @@ -6,32 +6,32 @@ MEATSLICER - +
- - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/js/sfxbank.js b/js/sfxbank.js index 6136383..34b2991 100644 --- a/js/sfxbank.js +++ b/js/sfxbank.js @@ -111,7 +111,7 @@ const SfxBank = { if (this.buffers.has(name) || this.pending.has(name) || this.failed.has(name)) return; const file = this.FILES[name]; if (!file || !this.ctx || typeof fetch !== 'function') return; - const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=52') + const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=53') .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); }) .then(bytes => this.ctx.decodeAudioData(bytes)) .then(buffer => this.buffers.set(name, this.trim(buffer))) diff --git a/js/sprites.js b/js/sprites.js index 2a5ddc0..4639aef 100644 --- a/js/sprites.js +++ b/js/sprites.js @@ -86,7 +86,7 @@ const Sprites = { const img = new Image(); img.onload = () => { this.imgs[name] = img; if (--pending === 0) G.imagesLoaded = true; }; img.onerror = () => { if (--pending === 0) G.imagesLoaded = true; }; - img.src = 'assets/' + name + '.png?v=52'; + img.src = 'assets/' + name + '.png?v=53'; } }, diff --git a/package.json b/package.json index f65c656..f9d7a30 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "meatslicer", "productName": "MeatSlicer", - "version": "0.6.0", + "version": "0.7.0", "private": true, "description": "A Butcher's Descent - clear, harvest, descend.", "main": "desktop/main.js",