release: bump game and memory versions to 0.7.0
This commit is contained in:
1 parent
717a106119
commit
c0d909eebd
5 files changed
+25
-398
No files matched your search
@@ -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
|
||||
<http://localhost:8123>. 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/<file>.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 <name-substrings> --quality medium
|
||||
python tools/gen_assets.py <name-substrings> --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 <name> --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`.
|
||||
+22
-22
@@ -6,32 +6,32 @@
|
||||
<meta name="theme-color" content="#0a0305">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22><text y=%2252%22 x=%2250%22 text-anchor=%22middle%22 dominant-baseline=%22middle%22 font-size=%2252%22>🔪</text></svg>">
|
||||
<title>MEATSLICER</title>
|
||||
<link rel="stylesheet" href="css/style.css?v=52">
|
||||
<link rel="stylesheet" href="css/style.css?v=53">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<canvas id="game" width="960" height="640"></canvas>
|
||||
</div>
|
||||
<script src="js/utils.js?v=52"></script>
|
||||
<script src="js/state.js?v=52"></script>
|
||||
<script src="js/input.js?v=52"></script>
|
||||
<script src="js/sfx.js?v=52"></script>
|
||||
<script src="js/sfxbank.js?v=52"></script>
|
||||
<script src="js/audio.js?v=52"></script>
|
||||
<script src="js/sprites.js?v=52"></script>
|
||||
<script src="js/particles.js?v=52"></script>
|
||||
<script src="js/weapons.js?v=52"></script>
|
||||
<script src="js/bullets.js?v=52"></script>
|
||||
<script src="js/pickups.js?v=52"></script>
|
||||
<script src="js/items.js?v=52"></script>
|
||||
<script src="js/perks.js?v=52"></script>
|
||||
<script src="js/enemies.js?v=52"></script>
|
||||
<script src="js/bosses.js?v=52"></script>
|
||||
<script src="js/rooms.js?v=52"></script>
|
||||
<script src="js/player.js?v=52"></script>
|
||||
<script src="js/hud.js?v=52"></script>
|
||||
<script src="js/help.js?v=52"></script>
|
||||
<script src="js/debug.js?v=52"></script>
|
||||
<script src="js/main.js?v=52"></script>
|
||||
<script src="js/utils.js?v=53"></script>
|
||||
<script src="js/state.js?v=53"></script>
|
||||
<script src="js/input.js?v=53"></script>
|
||||
<script src="js/sfx.js?v=53"></script>
|
||||
<script src="js/sfxbank.js?v=53"></script>
|
||||
<script src="js/audio.js?v=53"></script>
|
||||
<script src="js/sprites.js?v=53"></script>
|
||||
<script src="js/particles.js?v=53"></script>
|
||||
<script src="js/weapons.js?v=53"></script>
|
||||
<script src="js/bullets.js?v=53"></script>
|
||||
<script src="js/pickups.js?v=53"></script>
|
||||
<script src="js/items.js?v=53"></script>
|
||||
<script src="js/perks.js?v=53"></script>
|
||||
<script src="js/enemies.js?v=53"></script>
|
||||
<script src="js/bosses.js?v=53"></script>
|
||||
<script src="js/rooms.js?v=53"></script>
|
||||
<script src="js/player.js?v=53"></script>
|
||||
<script src="js/hud.js?v=53"></script>
|
||||
<script src="js/help.js?v=53"></script>
|
||||
<script src="js/debug.js?v=53"></script>
|
||||
<script src="js/main.js?v=53"></script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-1
@@ -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)))
|
||||
|
||||
+1
-1
@@ -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';
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user