162 lines
14 KiB
Markdown
162 lines
14 KiB
Markdown
# DESIGN §0, §1, §2, §11, §12 — Scope, stack, repository layout, quality bar
|
||
|
||
Part of [DESIGN.md](../DESIGN.md) (the index; section numbers are global).
|
||
|
||
## 0. Product scope (v1)
|
||
|
||
A faithful, polished, **online co-op** browser remake of Arknights' seasonal auto-chess TD mode 「卫戍协议:盟约」(下半, `act2autochess`).
|
||
|
||
In scope:
|
||
- Title → lobby (nickname, create room / join by 4-letter code or `?room=CODE` link, host picks difficulty, up to 4 players, **AI teammates** can fill seats, ready/start). Solo (独立模拟) and co-op (同盟模拟, 1–4 humans + optional AI).
|
||
- Difficulties: 标准 FUNNY, 险境 NORMAL, 绝境 HARD, 终极 ABYSS (solo and multi variants from `modeDataDict`). Training/tutorial mode is out of scope.
|
||
- Pre-game: INFO_CHECK briefing (disabled bonds, banned operators, enemy factions, stage) → BAND_CHECK strategy draft (40 bands, random order, 1 skip in co-op) → BATTLE_CHECK.
|
||
- Rounds 1–14 (+ hidden 15): prep (shop, hand, board, items, freeze, refresh, level-up, sell, merge + reward pick, ready), 机变 draft rounds, simultaneous auto-combat on each player's own field, 联防 unite phase, LP loss/elimination, Final Assault boss round with merged LP and shared boss HP pool, Hidden Core, settlement with titles.
|
||
- Full content: 112 visible chess (+ elites), their default skills and talents, 23 bonds with layers, all 43 特质 (garrison) effect keys, 56 equipment + Arts, 40 bands, 机变 cards, enemy factions/special enemies, 10 bosses, stage devices/terrain (crates, blowers, mire, smog, deep sea, infection).
|
||
- Rendering with the real Spine battle chibis (PixiJS 7 + pixi-spine 4), procedural tiles, VFX, damage numbers, real BGM/SFX, emotes, broadcast ticker.
|
||
- Reconnect, AI take-over of disconnected players, robust validation of every client intent.
|
||
|
||
Out of scope v1: matchmaking queue, training/tutorial, DIY (甄选) slots (the 4 DIY chess are removed from the shared pool — 0.2.0 plays them as 自选编队: the picks of the 干员调配 overlay's 自选编队 tab, each sold only in its player's shop; DATA.md §18, shared/diy.js, server/match/player/diy.js), trophies/progression persistence, reporting.
|
||
|
||
---
|
||
|
||
## 1. Tech stack
|
||
|
||
- **Node.js ≥ 22** (CI: 22 and 24), ESM (`"type": "module"`), single dependency `ws@8`. No bundler, no TypeScript. JSDoc types where helpful.
|
||
- **Server-authoritative simulation.** Clients send *intents*; the server validates, mutates state and pushes state/snapshots.
|
||
- **Client:** static files, native ES modules. Vendored libs in `public/vendor/`: `pixi.min.js` (PixiJS **7.4.2** UMD, global `PIXI`), `pixi-spine.js` (**4.0.6** UMD, `PIXI.spine`), `preact.module.js` + `hooks.module.js` + `htm.module.js` (Preact 10 + htm, no build step). No CDN at runtime (LAN play must work offline).
|
||
- **Shared code** in `shared/` is imported by both server and browser (pure ESM, no Node APIs).
|
||
- Tests: `node --test` (`test/**/*.test.js`). Browser E2E: `puppeteer-core` driving the system Chrome (dev dependency, optional; puppeteer-core 25 is ESM-only and needs Node ≥ 22.12, so the suites load it with `await import`).
|
||
|
||
Run: `npm install && npm run assets && npm start` → `http://localhost:3000`. Friends on LAN use `http://<host-ip>:3000`. Internet play: a tunnel (e.g. `cloudflared tunnel --url http://localhost:3000`) or any Node host (Dockerfile provided).
|
||
|
||
---
|
||
|
||
## 2. Repository layout & ownership
|
||
|
||
```
|
||
server/
|
||
index.js process entry: startServer() wires server/http/ (plain node:http + ws); boots when run directly
|
||
http/
|
||
config.js ROOT, served dirs, env (PORT, HOST, TRUST_PROXY, DEBUG), options handed to net.js / lobby.js
|
||
websocket.js session wiring (SessionRegistry → Lobby → Network), WebSocket upgrade at /ws (64 KB frames)
|
||
static.js mounts / → public, /data/, /shared/, /sim/ (.js only), the /data.js browser stand-in, path guards
|
||
media.js /media/… extension-less audio → public/assets/audio
|
||
files.js one file → response: MIME, gzip (.skel/.atlas/.json/.js/.css …), ETag / 304, cache policy, ranges
|
||
buildTag.js build tag of the served browser runtime (/healthz build, public/js/ui/buildGuard.js)
|
||
routes.js request listener: 414 / 400 / 405 guards, GET /healthz, then static files
|
||
common.js security headers, URL split, error page, JSON replies, bare 400 for unparseable requests
|
||
boot.js boot banner (Local / LAN / tunnel URLs), port-in-use hint, graceful SIGINT / SIGTERM
|
||
net.js session registry, send helpers, per-connection rate limit, message validation (uses shared/protocol.js)
|
||
lobby.js rooms (4-letter codes), seats, host, AI seats, ready/start, reconnect tokens, room→Match wiring
|
||
data.js loads data/*.json once, builds indexes (getChess, getBond, …); frozen objects
|
||
match/
|
||
Match.js match state machine, timers, round loop, co-op orchestration, broadcasting views — the class:
|
||
the lobby⇄match interface (header), constructor + method install
|
||
match/ Match's methods by concern (platform, infra, messaging, views, watch, intents, pause, phases,
|
||
spDraft, prep, combat, clientCombat, reports, unitePhase, bossRounds, settle; shared constants
|
||
in common.js), installed on Match.prototype
|
||
PlayerState.js per-player economy/shop/hand/board/items/bonds/LP state + all prep-intent handlers — the class:
|
||
constructor + method install
|
||
player/ PlayerState's methods by concern (basics, pieces, acquire, economy, placement, items, prep,
|
||
round, views; common.js), installed on PlayerState.prototype
|
||
pool.js SharedPool (copies per base chess), banned/disabled bonds, odds & rolls
|
||
board.js placement legality, deploy cap, hand/temp management, merge detection & execution
|
||
bondsMeta.js bond member counting (BOARD / BOARD_AND_DECK / golden), activation tiers, persistent layers
|
||
effectsMeta.js registry + dispatcher of prep-phase ("SERVER_*") effects: garrisons, bands, items, bonds
|
||
choices.js 机变 draft: card generation per family, pick order, timeouts, application
|
||
waves.js match enemy factions, per-round spawn list generation, stat scaling, bounties
|
||
unite.js 联防 helper selection, leaked-enemy aggregation, LP attribution
|
||
finalAssault.js R14/R15 pairing, merged LP, shared boss HP pool, overtime drain
|
||
results.js settlement stats, titles (评语)
|
||
bot.js AI player (plays prep phases; also auto-play for disconnected humans)
|
||
sim/
|
||
Battle.js one field simulation (normal / unite / boss); public API §5 — the class: constructor + method install
|
||
battle/ Battle's methods by concern (players, lifecycle, hooks, spawns, deploy, blocking, status, combat,
|
||
queries, summons, tiles, displacement, economy, events), installed on Battle.prototype
|
||
constants.js TICK, conversions, tuning knobs
|
||
rng.js seeded PRNG (mulberry32) + helpers
|
||
grid.js field grid, tile queries, passability, official 4-direction SPFA flow field + line-of-sight smoothing, road-over-floor preference only where it crosses fewer floor tiles (§21.15); DEPLOY_REFUSED_TILES (深水区, §21.3)
|
||
units.js Unit / Operator / Enemy / Token classes, stat aggregation
|
||
buffs.js Buff model, status catalogue (stun, freeze, cold, sleep, slow, fragile, stealth, …)
|
||
damage.js damage & heal pipeline, element gauges
|
||
targeting.js target selection helpers, range tests, priorities
|
||
body.js hit areas of huge enemies: the one helper behind every range test on enemies (§3, §19.4)
|
||
projectiles.js projectile flight/impact
|
||
skills.js skill runtime: SP, triggers, durations, ammo, charges; interprets SkillSpec
|
||
ai.js enemy movement/attack AI, operator attack loop
|
||
fear.js 恐惧 movement of enemies (reachable-tile fan, random checkpoints, self-fear flutter; §20.4)
|
||
snapshot.js compact serialization for clients
|
||
content/
|
||
index.js installs all content into a Battle (kits, bonds, garrisons, items, bands, enemies, devices)
|
||
kits/index.js operator kit registry keyed by base chessId (guide: kits/README.md)
|
||
kits/ops/*.js one operator kit (skills + talents) per file, <chessId>-<codename>.js
|
||
kits/shared/*.js helpers the kits share (shared/tier1.js: the general ones)
|
||
tokens.js summon/token definitions
|
||
bonds.js battle side of the 23 bonds
|
||
garrisons.js battle side (IN_BATTLE) garrison effect keys
|
||
items.js battle side of equipment + Arts
|
||
bands.js battle side of band (strategy) effects
|
||
enemies.js enemy ability specs + generic special-type behaviours: dispatch hooks, KITS, STATS_ONLY
|
||
enemies/ helpers.js, archetypes.js, one kit file per special type (invisible, times, element, dot,
|
||
reflection, fly, special) and leaders.js — each with its part of KITS
|
||
bosses.js boss scripts (boss_1 … boss_10)
|
||
devices.js stage devices & special terrain
|
||
shared/
|
||
constants.js enums, phases, areas, error codes, UI-relevant constants
|
||
protocol.js message catalogue + validators (§8)
|
||
format.js rich-text description → HTML/plain; number formatting
|
||
rules.js pure helpers shared by UI & server (e.g. price display, sell value, bond tier from count)
|
||
data/ generated by tools/build-data.mjs — committed; see docs/DATA.md
|
||
public/
|
||
index.html single page; loads /js/main.js as module
|
||
css/ theme.css (tokens, fonts), components.css, screens/*.css
|
||
js/
|
||
main.js boot, router between screens, global store
|
||
net.js WebSocket client, reconnect, request/response helpers
|
||
store.js tiny observable store (state from server + local UI state)
|
||
data.js fetches /data/*.json, same indexes as server/data.js
|
||
audio.js BGM/SFX manager (Web Audio), volume settings
|
||
assets.js asset URLs, image cache, Spine loader with LRU + fallback
|
||
screens/ title.js, lobby.js, room.js, briefing.js, bandDraft.js, game.js (entry; game/*.js plates, marks, early events), result.js
|
||
ui/ hud.js, bondStrip.js, bondPopup.js, shopBar.js, teamPanel.js, detailPanel.js,
|
||
tooltip.js, choiceOverlay.js, rewardOverlay.js, ticker.js, emotes.js, settings.js, toasts.js,
|
||
gameLogic.js (barrel; gameLogic/*.js placement, bonds, phases, shop, loadout)
|
||
render/ app.js (field view; app/*.js helpers), projection.js, tiles.js, units.js, spine.js, fx.js (effects; fx/*.js), interp.js, drag.js, pick.js, promote.js (API §9)
|
||
vendor/ pixi, pixi-spine, preact, hooks, htm (copied from node_modules by tools/vendor.mjs)
|
||
assets/ downloaded art/audio (git-ignored), see docs/ASSETS.md
|
||
fonts/ self-hosted fonts
|
||
tools/
|
||
build-data.mjs official data + research JSON → data/*.json
|
||
fetch-assets.mjs downloads/optimizes assets → public/assets + data/assets.json
|
||
vendor.mjs copies vendor libs from node_modules → public/vendor
|
||
test/ node:test suites; test/e2e/ browser + bot tests
|
||
docs/ DESIGN.md (the index of this document: the rules in design/, the per-release revisions in
|
||
history/), ARCHITECTURE.md, DATA.md, SIM.md, META.md, I18N.md, ASSETS.md, BALANCE.md,
|
||
DEPLOY.md, PLAYING.md, WINDOWS.md, research/
|
||
(the wire protocol is normative in shared/protocol.js itself)
|
||
LICENSE GPL-3.0-or-later (the project's code); NOTICE.md: scope, non-commercial game assets, the Spine
|
||
Runtimes linking permission; THIRD-PARTY-NOTICES.md: libraries, fonts, data sources
|
||
```
|
||
|
||
Ownership rule for parallel agents: **only edit files assigned to you**; if you need a change in someone else's file, write it in your final report instead (the integrator applies it). Shared files (`shared/*`, `docs/DESIGN.md`) are owned by the architect.
|
||
|
||
---
|
||
|
||
## 11. Quality bar & testing
|
||
|
||
- **No crash paths**: every server handler guarded; a thrown error inside a Battle tick for one field must not kill the match (log, force-end that battle as timeout, continue). Invariants asserted in tests: no NaN/Infinity in any unit field; hp ∈ [0, maxHp]; positions inside rect; battles terminate within `timeLimit + 1 s` (boss: terminate by pool/force); pool copy counts never negative and never exceed caps; funds never negative; hand/temp sizes respected.
|
||
- Unit tests per module; content tests per effect; **full-match bot tests**: 1/2/4-player matches of every difficulty with fixed seeds run to RESULT headlessly (fast-forward: no real-time pacing) with zero errors; protocol fuzz (random/invalid intents) never crashes and never corrupts state.
|
||
- **Browser E2E** (puppeteer-core + system Chrome): open 2 tabs, create/join room, add AI, play through prep/combat of several rounds, zero console errors, screenshots saved to `test/e2e/out/` for visual review.
|
||
- Performance: a normal battle tick with 60 enemies + 10 ops < 0.5 ms; 4 parallel fields at 2× real time < 15% CPU of one core; the heavy 2-player boss field (18 ops, ~130 enemies alive, `test/sim/robustness.test.js`) < 0.5 ms per tick on a development machine, the best of three runs of the same battle — 1.0 ms when `process.env.CI` is set (GitHub's shared windows runners measured 0.51–0.58 ms and failed the 0.5 ms bar on PR #10, #12, #14 and master's own push; a real regression still shows locally).
|
||
|
||
---
|
||
|
||
## 12. Implementation phases
|
||
|
||
1. **Foundation**: build-data, fetch-assets/vendor, server platform (http/ws/lobby), client shell (screens up to room).
|
||
2. **Core**: sim engine + harness; match/meta; render engine; game UI.
|
||
3. **Content**: kits, bonds, garrisons, items, bands, enemies/bosses/devices, choices.
|
||
4. **Integration & QA**: wire everything, bots, E2E, balance pass, bug-hunt workflows until dry, visual polish.
|
||
|
||
---
|