mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-20 15:48:33 +02:00
151 lines
6.7 KiB
Markdown
151 lines
6.7 KiB
Markdown
# Deterministic PvP capture harness
|
||
|
||
`src/scripts/pvp-capture.js` creates a clean, reproducible multiplayer match for trailers, ads,
|
||
camera work, and gameplay review. It supports the built-in Escape Race, Last Alive, Score Attack,
|
||
and Co-op Escape rulesets. Each take uses the authoritative `MatchRoom` and `MatchEngine`, drives
|
||
deterministic bots through the installed Socket.IO client, and records the existing browser render
|
||
kit with the installed Playwright Chromium.
|
||
|
||
This is intentionally not a production feature or an admin endpoint. The harness:
|
||
|
||
- starts a separate HTTP/Socket.IO process bound to `127.0.0.1` on an ephemeral port;
|
||
- accepts no remote target URL and cannot attach to a normal Wowngeon instance;
|
||
- creates one in-memory `free` match with zero entry fee, pot, house fee, or payout;
|
||
- imports no application, database, wallet, queue, provider, or payout service;
|
||
- requires `NODE_ENV=development` or `NODE_ENV=test` and the explicit
|
||
`--confirm-local-free-only` flag;
|
||
- refuses to run when a payment, credits, payout, crypto-race, mainnet-payout, or wallet endpoint
|
||
is enabled/configured; and
|
||
- refuses inherited crypto, game-mode, difficulty, dungeon, monster, treasure, or network-tuning
|
||
overrides so a committed seed cannot silently mean different footage in another shell; and
|
||
- loads only repository assets. CDN/3D renderers are unavailable in this tool.
|
||
|
||
Those guards make setting an environment variable in the live service insufficient to enable the
|
||
harness. Someone must deliberately start this separate loopback-only CLI with its confirmation
|
||
flag. On a staging machine, run it as a separate development/test process; it never uses the
|
||
staging app or staging database.
|
||
|
||
## Record a video
|
||
|
||
From `src/`:
|
||
|
||
```bash
|
||
NODE_ENV=development npm run capture:pvp -- \
|
||
--confirm-local-free-only \
|
||
--ruleset race \
|
||
--players 4 \
|
||
--mode tiles \
|
||
--output /tmp/wowngeon-pvp-capture.webm \
|
||
--screenshot /tmp/wowngeon-pvp-final.png
|
||
```
|
||
|
||
The repository already declares `playwright-core`; the command uses its existing local Chromium
|
||
and FFmpeg bundle. It never downloads a browser or package. If that browser bundle is missing, the
|
||
CLI fails with a direct explanation. The deterministic control harness remains usable without a
|
||
browser:
|
||
|
||
```bash
|
||
NODE_ENV=test npm run capture:pvp -- \
|
||
--confirm-local-free-only \
|
||
--no-video \
|
||
--ticks 120 \
|
||
--trace /tmp/wowngeon-pvp-trace.json
|
||
```
|
||
|
||
Run `npm run capture:pvp -- --help` for every option. `--ruleset` accepts `race`, `last-alive`,
|
||
`score-attack`, or `coop-escape`; this is separate from `--mode`, whose offline renderer choices
|
||
are `tiles`, `ascii`, and `iso`. Every ruleset accepts 2–8 bots in this harness.
|
||
|
||
The default 1280×720 spectator camera anchors on `bot-1`; choose another player with `--focus
|
||
bot-N`. In the default `--camera action` mode, deaths, treasure pickups, and exits temporarily take
|
||
focus before the camera returns to that anchor. `--camera focus` disables those cuts. Camera zoom
|
||
adjusts to the active pack: it pushes in for close action and pulls back as players spread out.
|
||
Portrait viewports begin with a tighter follow crop so 9:16 footage fills the frame. A separate
|
||
screen-space roster keeps all 2–8 player names and states legible without stacking labels on one
|
||
dungeon tile.
|
||
|
||
## Capture each built-in mode
|
||
|
||
Use the same safety acknowledgement for every take:
|
||
|
||
| `--ruleset` | Bot objective | Result card |
|
||
|---|---|---|
|
||
| `race` | Treasure route plus staggered sprints to the exit | Competitive race winner |
|
||
| `last-alive` | Seek and strike the nearest active rival | Last survivor |
|
||
| `score-attack` | Build progress/treasure score, then bank an exit | Top score and points |
|
||
| `coop-escape` | Staggered team pathing to the exit | Team escape count, never a fake competitive winner |
|
||
|
||
For example, record a Last Alive take:
|
||
|
||
```bash
|
||
NODE_ENV=development npm run capture:pvp -- \
|
||
--confirm-local-free-only \
|
||
--ruleset last-alive \
|
||
--players 6 \
|
||
--camera action \
|
||
--viewport 1920x1080 \
|
||
--output /tmp/wowngeon-last-alive-ad.webm \
|
||
--trace /tmp/wowngeon-last-alive-ad.json
|
||
```
|
||
|
||
Swap only `--ruleset` and output names to capture Score Attack or Co-op Escape. Headlines, active
|
||
player terminology, roster states, footer copy, and final results all follow the selected ruleset.
|
||
Score Attack closes the local exhibition deadline as soon as every bot has died or banked an exit,
|
||
avoiding a motionless tail without changing the authoritative scoring/finalization path.
|
||
|
||
For a short 9:16 ad take, the curated seed produces a complete six-racer arc in about nine seconds.
|
||
Follow the eventual winner so the finish remains legible on a phone screen:
|
||
|
||
```bash
|
||
NODE_ENV=development npm run capture:pvp -- \
|
||
--confirm-local-free-only \
|
||
--ruleset race \
|
||
--players 6 \
|
||
--focus bot-4 \
|
||
--viewport 1080x1920 \
|
||
--output /tmp/wowngeon-pvp-ad-vertical.webm \
|
||
--screenshot /tmp/wowngeon-pvp-ad-vertical.png
|
||
```
|
||
|
||
Use the emitted JSON trace as the authoritative replay record. The video is marketing footage; it
|
||
does not create a leaderboard row, credit, payment, ticket, or payout.
|
||
|
||
For a crowded 16:9 arena take, the same seed supports the built-in eight-player maximum:
|
||
|
||
```bash
|
||
NODE_ENV=development npm run capture:pvp -- \
|
||
--confirm-local-free-only \
|
||
--ruleset race \
|
||
--players 8 \
|
||
--focus bot-4 \
|
||
--viewport 1920x1080 \
|
||
--output /tmp/wowngeon-pvp-ad-8player.webm \
|
||
--screenshot /tmp/wowngeon-pvp-ad-8player.png \
|
||
--trace /tmp/wowngeon-pvp-ad-8player.json
|
||
```
|
||
|
||
The recorded eight-player proof and its no-video control both ended with `bot-4` at tick 49 and
|
||
the same final state hash. This checks that browser recording does not alter the authoritative race.
|
||
|
||
## Reproducibility
|
||
|
||
The default seed is fixed. A custom seed must be exactly 64 hexadecimal characters:
|
||
|
||
```bash
|
||
NODE_ENV=development npm run capture:pvp -- \
|
||
--confirm-local-free-only \
|
||
--seed beb270f3806a97e9ef73c8f83a6eae19a92f90ab38af9ad8b365cb74c41b2702
|
||
```
|
||
|
||
Each bot receives a numbered tick and the same authoritative state over Socket.IO. The server waits
|
||
for every bot response, queues inputs in stable bot order, and lets the engine resolve them using its
|
||
committed per-tick action priority before advancing. Bot goals are ruleset-aware but contain no
|
||
randomness beyond the committed dungeon seed. Missing or malformed input aborts the run instead of
|
||
silently changing the replay. Every run writes a versioned JSON trace beside the video containing
|
||
the ruleset, presentation metadata, seed, exact inputs, events, camera configuration, state hashes,
|
||
placements, scores, and mode-correct outcome.
|
||
|
||
The gameplay trace is deterministic, and records that gameplay environment overrides were refused.
|
||
Encoded WebM bytes can differ across Chromium/FFmpeg versions
|
||
or host timing, so use the JSON state hashes—not the video checksum—to compare replays.
|