实现账号绑定、持久登录与跨设备对局恢复
CI / test (ubuntu-latest, node 22) (push) Failing after 33m10s
CI / test (ubuntu-latest, node 24) (push) Failing after 34m41s
CI / tooling (lint, imports, typecheck) (push) Failing after 36m11s
CI / test (windows-latest, node 22) (push) Canceled after 0s
CI / test (windows-latest, node 24) (push) Canceled after 0s

This commit is contained in:
herryyun committed 2026-10-14 23:22:50 +08:00
1 parent 488db49f11
commit 7c25c89992
62 files changed
+6855 -1005

No files matched your search

+31
View File
@@ -73,6 +73,37 @@ Audio: autochess BGM per phase, UI SFX (buy/sell/refresh/level/merge/ready/timer
---
### Required account binding at the callsign field (fork integration)
The title hides 博士代号 until binding, then displays the role nickname as plain text without an input
(`public/js/skland/entry.js`;
`SP_SKLAND=1`, [skland.md](../skland.md)). The primary button shows 绑定账号 until a named role is fetched, then 开始.
Multiple roles start with the default role and can be switched. Unbinding or an empty role list clears the callsign
and restores 绑定账号. The nickname retains its person icon. The scan tab fetches a QR automatically and refreshes it
every 10 s; the first awaiting-confirmation response starts a 60 s window. Refresh keeps the current image visible
until its replacement is ready and updates the image source in place. Manual refresh remains available, with
serial 2 s polling and paused timers outside the scan tab/visible page. Saved callsigns/entered flags and account recovery cannot skip the binding lookup. Skland QR
login supports official/Bilibili channels; phone/password uses the Hypergryph account. Every game-entry HTTP request
uses the existing game session token and a 30-day HttpOnly JWT cookie, without entering the lobby automatically.
The cookie restores the role in a new tab or after a server restart; role choice persists on the server. The callsign
never becomes an identity key: UID plus server region determine a stable game player id, allowing duplicate names.
Account UI, styles and the entry policy stay in independent module files; title, boot and recovery have small hooks
to reduce upstream merge conflicts. An optional server account policy also checks hello and room/game requests
against the JWT-owned role; editing the client's UUID, region or name cannot select another player's binding.
Unbind revokes remembered login. Owner request, 2026-10-14; §31.12 and [skland.md](../skland.md).
The title's 恢复账号对局 picker queries the selected role's live server-side seat, even on a device with no local match
history. It replaces the visible 本机 recovery buttons (including the obsolete lobby shortcut). Opening it or restoring
the login cookie does not disconnect an active device. 开始 or an explicit recovery confirmation asks for the role's
current token, reconnects with the JWT, and enters only after welcome confirms the expected playerId. The existing
socket is then replaced (4001, no auto-reconnect), and its old token cannot take the role back on reload. A duplicated
tab or stale saved entered flag stays at the title unless its own current role token really resumed. Any anonymous
welcome also clears an already-entered shell; the connection banner's 在此页面继续 uses checked account entry.
The picker shows
empty, loading and error states, a takeover notice and a refresh action; an expired/replaced selector cannot create a
new match. Room/match state is still memory-only with the existing disconnect windows; server restart ends it.
The UI and entry adapter remain in public/js/skland/ (§31.13).
## 13. Local-client art (optional, host-side)
`tools/local-extract/extract.py` (Python: UnityPy + lz4 + Pillow; decodes Arknights' custom **LZ4AK** bundle compression via `aklz4.py`) extracts the art the web download does not include (some of it, most official HUD sprites among it, is on the public mirror too — §22.5) from a locally installed client (CrossOver Windows build or PlayCover iOS build) into `public/assets/local/**` and writes `data/local-assets.json` (`{ groups: { '<subdir>': { name: { path, w, h, kind } } } }`). Everything here is **optional**: clients must render correctly when the file or any entry is missing (fall back to the web assets / procedural art). The emotes and the guide pages below are on the public mirror too (§22.5): fetch-assets downloads them (data/assets.json `ui['emoticon/<dir>/<picId>']`, `ui['guide/<key>']`) and the client shows the local copy first, then that one (`data.js artUrls`); docs/DEPLOY.md §6 lists what still needs the client.
+22
View File
@@ -4,6 +4,26 @@ Part of [DESIGN.md](../DESIGN.md) (the index; section numbers are global).
## 8. Network protocol (shared/protocol.js is normative)
**Fork account policy (opt-in `SP_SKLAND=1`, owner request 2026-10-14).** The existing WebSocket messages are
unchanged. Hello may establish anonymous transport for account login, but room/game requests require the role
owned by a persistent, server-verified JWT cookie. The server ignores a client's claimed nickname/UUID for account
identity; it derives stable `playerId` from UID plus provider-reported server region (`channelName`). Nicknames
may repeat, and optional provider `gameId` metadata does not affect identity. The public id uses 128 SHA-256 digest
bits encoded as base64url after `a_` (24 characters), so normal field ids and per-player battle reports fit the
existing protocol limits. A reconnect token alone cannot resume
an account identity. A valid login plus the role's current reconnect token can resume its previous socket; each
game frame checks the current login record and selected role, so logout or a role switch invalidates its authority.
Cookie restoration and read-only recovery lookup never take over another device. An explicit authenticated
enter/resume POST returns the selected role's token; the existing hello then attaches it and resends room/match state.
Only after successful attachment is the account token rotated, so a stale device cannot silently retake the seat.
The client stages the entry token in memory until welcome; local multi-tab claims still protect normal reconnects.
Login lasts 30 days, survives process restart and is revoked on unbind. Room/match data remain in memory.
Recovery selectors are scoped to the selected role and the particular room/match; stale selectors return 409 without
creating a new game. The existing 10-minute / solo 24-hour disconnect windows are unchanged, and an expired seat is
removed through the lobby before its stable playerId can be reused. Account API ownership and private DB handling
are specified in [skland.md](../skland.md) §5/§9. The optional policy hooks leave module-off transport unchanged;
no simulation rule, WebSocket message or protocol-version change is involved.
Transport: one WebSocket per tab at `/ws`, JSON text frames `{ t: '<type>', ...payload }`. Client requests may carry `rid` (request id); the server's direct reply echoes `rid` (`ok` or `error`). Server pushes have no `rid`. Max frame 64 KB inbound (a compressed frame counts its inflated size); rate limit 40 msgs/s per connection (excess dropped with `error RATE`). Frames from 1 KB up are compressed with permessage-deflate (RFC 7692; `server/http/websocket.js WS_DEFLATE`: no server context takeover, a 2 KB server window, zlib memLevel 7 — about 72 KB of zlib state per connection); smaller frames go out plain, and a client that offers no compression, or caps the server window below 2 KB, gets plain frames. A measured solo session (rounds 3–5, `/healthz?traffic=1`): 173 KB of server frames → 60 KB on the wire.
### 8.1 Session & lobby
@@ -137,6 +157,8 @@ voting, refuses stale ids and cancels on disconnect/leave; DESIGN §28.21 / META
Explicit local recovery (§28.20) reuses saved hello tokens after a fresh live-tab claim check. Display names and room
codes are UI metadata only, never authentication. No new network message or nickname/IP matching is introduced.
The account-required fork replaces the visible local recovery entry with authenticated account recovery (§31.13);
the generic local helper and its module-off handshake behavior are retained.
Optional `coins: [[id, balance, cap]]` is an integer skill-currency readout (§29.6), separate from ammo. It includes zero, is stepped between snapshots and clears when omitted. Result-validation names for board operators are resolved from the server's DIY/stand-in selections, never a supplied display name.