0.2.3: Clementia S2 releases a dead, hidden, uncarriable or pathless rider at once (slot and weight back before the same tick's boarding checks); same-browser match recovery never takes the seat of a window that is reloading or still starting (a tab keeps its own session token; seats are stamped at the server's welcome, the older welcome wins); golden: diy-135 moves (outcome unchanged)
This commit is contained in:
1 parent
821ab827bd
commit
ba5b998a72
19 files changed
+580
-60
No files matched your search
@@ -174,6 +174,11 @@ otherwise every "之后 / 后续的每场作战" card — e.g. 教鞭's 法术
|
||||
A `choice:<effectId>` registry handler overrides the default application (§2.4).
|
||||
|
||||
### 1.3 Disconnects, AI takeover
|
||||
* Local multi-window recovery (#431, DESIGN §28.27) sends optional `hello.noReplace` for a tentative shared token,
|
||||
and `hello.claimAt` for a previously welcomed token. Before binding a different socket, the server rejects a
|
||||
connected seat when no replacement was allowed or the request has a later stamp than the attached holder
|
||||
(`SESSION_IN_USE`). Equal stamps still allow normal reload/reconnect. These hints do not authenticate players
|
||||
and do not change the bearer token or expiration rules below.
|
||||
* Disconnected human: the seat keeps playing its last lineup; drafts auto-resolve at their deadlines, prep auto-readies at
|
||||
the deadline (an open 教鞭 choice is picked at random first, §2.5; then the temp pieces due at that prep are sold/destroyed). Nothing is bought for them. A battle the human was authority of goes to the server
|
||||
(normal / 联防: re-simulated from t = 0) or, on a boss field, to the partner's replica (DESIGN §14). The session stays
|
||||
|
||||
+3
-1
@@ -201,7 +201,9 @@
|
||||
单独选择中文、日语或跟随全局语音,只保存在当前浏览器,日语缺失时沿用中文回退。
|
||||
|
||||
同一浏览器的对局窗口意外关闭后,可以从标题或大厅的「恢复本机对局」选择保存的席位。仍被其他窗口使用的席位
|
||||
不会被接管;服务器重启、对局过期或浏览器无法检查其他窗口时不能恢复。
|
||||
(包括正在刷新、初始化的窗口)会保留给原窗口;刷新只尝试本窗口原来的身份,不会自动换成另一个近期身份。
|
||||
恢复在当前页面重新连接,服务器确认后才保存新身份;发生竞争时,较早获得服务器确认的窗口优先。
|
||||
服务器重启、对局过期或浏览器无法检查其他窗口时不能恢复。
|
||||
|
||||
浏览器提供安装条件时会显示「添加到桌面」。安装后仍需连接游戏服务器,不会把服务器或整套素材离线保存。
|
||||
不支持页面内安装按钮的浏览器可使用其自己的菜单(是否可用由浏览器决定)。
|
||||
|
||||
@@ -535,6 +535,11 @@ flag `float` (近地悬浮, PRTS 术语释义 "算作空中单位") or `levitate
|
||||
walking the ground path). `deploySeq` counts deployments (and identifies one: `seq === u.deploySeq`);
|
||||
`aggroSeq` is the aggro order (= deploySeq, except the summons of the initial deployment, §1).
|
||||
|
||||
克莱门莎 S2's kit stores its cabin in `unit.mem.clemntCabin`. Before boarding each tick, it removes passengers
|
||||
that died, became hidden / uncarryable or cannot path to the next cabin position, removes their carry buff and
|
||||
returns their current `weight` (including modifiers). The same tick's count and weight limits use that cleaned
|
||||
list and budget (DESIGN §28.27); this is local to the kit, not a change to general displacement.
|
||||
|
||||
**Hit areas (`body.js`, user playtest #5 item 10).** A regular enemy is a point: in a grid range when the tile of its
|
||||
position (`round(y)`, `round(x)`) is a range tile, in a radius when its position is (DESIGN §3). A huge enemy (巨型单位:
|
||||
`enemy.hitArea` from data/enemies.json `hitArea` — 假想敌:胄 / 管 / 盐风主教昆图斯 / 阿利斯泰尔 / “萨米的意志”, PRTS
|
||||
|
||||
+12
-2
@@ -101,8 +101,18 @@ room loadouts and their exports.
|
||||
|
||||
The title and lobby offer explicit recovery of a previously saved local match seat. `sp.matches` stores bounded name /
|
||||
room-code metadata indexed by token hash; the existing four-entry `sp.tokens` ring remains the authority. Recovery
|
||||
rechecks BroadcastChannel claims before adopting the selected token, then reloads. It does not identify players by
|
||||
name or IP or displace a live tab. Without BroadcastChannel it refuses shared recovery. Server restart / expiry clears
|
||||
rechecks BroadcastChannel claims before selecting the token in memory and reconnecting in the same document.
|
||||
Only the server's `welcome` (`saveToken`) persists that selection and its first-welcome stamp (`sp.tokenWelcome`,
|
||||
bound to the token hash). Refreshing or reconnecting the same token preserves that stamp. A tab with a session token
|
||||
initializes with that token or null, never another recent token, and answers ownership queries during initialization.
|
||||
An earlier welcome takes precedence; equal stamps prefer an already welcomed live document over a copied session,
|
||||
then tab id breaks ties. A session token without a stamp still outranks a tentative shared-token selection. A pending
|
||||
recovery yields when the original owner returns; a later welcomed holder also gives up its local claim and pending
|
||||
reconnect. `hello.noReplace` prevents tentative recovery from evicting a connected session even if its hello is already
|
||||
in flight; `hello.claimAt` preserves the earlier-welcome priority for established holders' in-flight reconnects. A stale
|
||||
welcome never reaches the store or flushes queued game actions. Nickname persistence also waits for welcome, so a
|
||||
refused recovery can return to the previous identity and nickname. It does not identify players by name or IP.
|
||||
Without BroadcastChannel it refuses shared recovery. Server restart / expiry clears
|
||||
obsolete metadata on the next welcome. A spectator never registers a recoverable player seat.
|
||||
|
||||
`manifest.json` enables browser installation; its original fortress favicon is packaged as actual 192/512 px icons in
|
||||
|
||||
@@ -172,6 +172,9 @@ Profession traits (default behaviours implemented once in the engine, keyed by `
|
||||
Each domain file `server/sim/content/{tokens,bonds,garrisons,items,bands,enemies,bosses,devices,choices}.js` exports `install(battle)` (battle side; called once per Battle by `content/index.js → installContent`) and `registerMeta(registry)` (prep side; called once at server boot by `match/effectsMeta.js`, API documented in `docs/META.md`). Kits live one per file in `content/kits/ops/` (`export default { [baseChessId]: (bb, chess, def) => Kit }`), listed in `content/kits/index.js` (guide: `content/kits/README.md`). Content must be **data-driven from blackboards** (numbers never hard-coded when a blackboard key exists). Every effect gets at least one unit test in `test/content/*.test.js` using the `test/helpers/battleHarness.js` harness (provided by sim-core).
|
||||
|
||||
- kits: all 112 visible chess + hidden ones used by effects (盟约·辅助干员), default skill + talents + trait specifics; tokens. The 72 non-collaboration 6★ DIY operators include 克莱门莎 (§28.1); her launch-day provisional mechanics are local to her kit. 黍 GUA-Y and 乌尔比安 CRU-Y follow the generated module blackboards and existing module-level rules (§28.2).
|
||||
克莱门莎 S2 removes dead, hidden, self-bound / teleport-immune and unreachable passengers before checking new
|
||||
boarding in the same tick. Each released passenger returns its current weight to the cabin's remaining budget
|
||||
and frees its slot (§28.27); neither corpses nor failed carries reserve capacity.
|
||||
- bonds: 23 (battle + meta), exact per-layer formulas from research 02.
|
||||
- garrisons: 43 effect keys (IN_BATTLE via hooks; SERVER_* via meta registry).
|
||||
远牙's granted `garrison_108_a/b` additionally gains on both directions of `dollSwap`, sharing its deployment
|
||||
|
||||
@@ -14,6 +14,13 @@ Platform additions (implemented): `welcome.{resumed, version}`, `welcome.diyKitt
|
||||
|
||||
**Spectator seats** (a remake feature — the official room has none; community report #26, owner's decision 2026-10-04; the spectator-seat subsection of §23): `room.spectate {code}` takes one of a co-op room's `MAX_SPECTATORS` (2) spectator seats, in its lobby or during its match; `room.removeSpectator {playerId}` (host, at any time — the room screen's strip and, once the match runs, the game screen's 观战席 capsule in the top bar, GitHub #120) frees one (`room.closed {reason:'kicked'}` to it, no match frame after it); `room.state.spectators: [{playerId, name, connected}]` (still pushed while the match runs). A spectator is never a player (not in `seats`, not counted for the start, never host, keeps no room alive — `room.closed {empty}` when the last human leaves), receives the room's broadcasts and watches like an eliminated player (`Match.addSpectator`: `b.start` watch / `m.field`, never `m.private`); it may only `g.watch`, leave, and store a `room.loadout` — anything else answers `error SPECTATOR`. Its seat is kept and given back on reconnect like a player's.
|
||||
|
||||
Local recovery arbitration (0.2.3, §28.27): `hello` also accepts optional `noReplace:boolean` and
|
||||
`claimAt:number` (finite, nonnegative ms). A shared-token candidate sends `noReplace:true`; a tab with a saved
|
||||
welcome stamp sends `claimAt`. When that session is already connected to a different socket, `noReplace` or a later
|
||||
stamp than the attached holder causes `SESSION_IN_USE` before the old socket is touched. A same-stamp own reconnect
|
||||
can still replace its previous socket. Omitted hints retain legacy behavior; they are not authentication. Client
|
||||
selection/stamp rules live in design/client.md. In-flight protection requires the updated server as well as client.
|
||||
|
||||
### 8.2 Match
|
||||
C→S (all carry `rid`): `g.infoReady {setupRevision?}` · `g.band {bandId}` · `g.bandSkip` · `g.bandFocus {bandId?}` (the strategy highlighted in the draft screen — what a timed-out turn takes; absent / null clears it; `WRONG_PHASE` outside BAND_DRAFT, `ALREADY` after the pick, `BAD_TARGET` for a band not allowed; §18.2) · `g.buy {slot}` · `g.refresh` · `g.freeze` · `g.levelUp` · `g.sell {uid}` · `g.move {uid, to:{area:'board', row, col}|{area:'hand', idx}, dir?}` · `g.equip {itemUid, targetUid, replaceUid?}` (`replaceUid`: which equipped item a third one replaces, §6.2) · `g.art {itemUid, row, col, dir?}` (`dir` ∈ UP|RIGHT|DOWN|LEFT from the deploy wheel, §3; absent ⇒ `to.dir`, then RIGHT; `g.move` onto the piece's own tile re-orients it; board pieces in `m.private` / `m.field {prep:true}` carry `dir`) · `g.destroy {uid}` (hand / temp items only; equipped items are locked, §6.2) · `g.reward {idx}` · `g.choice {idx, choiceId?}` (with an ID: the recipient's PREP personal choice; without one: the public SP_DRAFT; stale or wrong IDs never fall through to the public draft) · `g.ready {ready}` · `g.emote {id}` · `g.watch {fieldId, playerId?}` (`playerId`: the player tapped — a shared 联防 / boss pair field shows two; what an eliminated viewer follows, §25) · `g.autoplay {on}` · `g.pause {on}` (solo battles only, §14 Solo pause) · `g.unitStats {seq?}` (ROUND_START / SP_DRAFT / PREP, else `WRONG_PHASE`; eliminated: `ELIMINATED`) → push `m.unitStats` (§18.5) · `g.leave`
|
||||
S→C:
|
||||
|
||||
+40
-2
@@ -284,9 +284,9 @@ installability check runs in Chrome's ordinary temporary profile. Native OS inst
|
||||
|
||||
Reproduced with an idle window retaining its own session while a second match window closes. The title/lobby now
|
||||
lists local saved match seats using bounded metadata beside the existing recent-token ring. Explicit selection asks
|
||||
live tabs to claim the chosen token again before adopting it and reloading. An active tab is never deliberately
|
||||
live tabs to claim the chosen token again before adopting it (same-document reconnect since §28.27). An active tab is never deliberately
|
||||
replaced; names/IP are not authentication. Missing BroadcastChannel refuses recovery. A server-generated replacement
|
||||
token or leaving clears obsolete metadata. No server API changes; normal automatic startup selection is preserved.
|
||||
token or leaving clears obsolete metadata. The initial feature changed no server API; §28.27 later adds optional hello guards and keeps startup on the tab's own identity.
|
||||
|
||||
Tests: live tab protection, closed-tab recovery, simultaneous claim arbitration, no channel/unknown id, malformed
|
||||
storage and expired metadata; a real two-window browser restores the identical server playerId and room after closing
|
||||
@@ -417,3 +417,41 @@ clear time and end reason. All other 284 scenarios, including the five other gol
|
||||
The focused checks cover all four tier/elite forms, selectability/control/cooldown boundaries, actual casts,
|
||||
healing chains and enemy splash. Full CI passes: 5879 tests, 5876 passed, 0 failed, 3 skipped; setup, smoke, lint,
|
||||
imports and typecheck pass. This round changes no production UI and does not rerun the opt-in browser suites.
|
||||
|
||||
|
||||
### 28.27 Cabin capacity and local recovery races — #431
|
||||
|
||||
- **Observed:** 克莱门莎 S2 removed the carry buff from dead, hidden, uncarryable or unreachable passengers but
|
||||
retained them in the passenger list and weight budget. Two weight-3 riders blocked a replacement after one died;
|
||||
a dead zero-weight rider still occupied one of the 15 slots; a dead weight-4 rider retained four budget points.
|
||||
- **Now:** after moving the cabin, release invalid passengers and return each one's current weight before either
|
||||
boarding limit is checked that tick. Remaining passengers keep their order and movement. This corrects bookkeeping
|
||||
under the existing S2 rules and official blackboard limits (sources in §28.1); no new gameplay assumption is added.
|
||||
- **Observed (#431):** a recovery attempt with a smaller tab id could claim a token while its owner was refreshing
|
||||
or still initializing. The original page then fell back to another recent identity, or erased its own token.
|
||||
- **Now:** startup with a session token chooses only that token or null and answers ownership queries immediately.
|
||||
The first server welcome for a token stamps its ownership; reload/reconnect preserves that stamp. Earlier welcome
|
||||
wins a conflict, a live document wins an equal-stamp duplicate, and tab id resolves remaining ties. Explicit recovery
|
||||
keeps its choice only in memory and reconnects in the same document; only welcome persists it. An original owner
|
||||
returning cancels the lower-priority choice, including a later welcomed holder's reconnect. Nicknames and IPs remain
|
||||
irrelevant to identity; a refused recovery retains the previous nickname until its own welcome.
|
||||
- **In-flight handshake:** a hello can reach the server after another connection's welcome. Optional `hello.noReplace`
|
||||
makes shared candidates refuse a currently connected seat; `hello.claimAt` lets the server refuse an established
|
||||
holder with a later welcome stamp. Refusal is `SESSION_IN_USE`, before rebinding or closing the incumbent socket.
|
||||
Normal same-stamp reconnects and older clients remain accepted. These are local ownership hints, not authentication;
|
||||
the bearer token remains the authority. A revoked hello's stale welcome is ignored before going online, flushing
|
||||
queued actions or notifying listeners, and retries use a new socket. Reconnect-window duration is unchanged.
|
||||
- **Regression coverage:** `op_clemnt.test.js` adds same-tick replacement for death / hidden / self-bound /
|
||||
teleport immunity / failed path, the 15-slot and weight-4 probes, and a changed current weight. The eight regressions
|
||||
fail before the fix and pass after it. `client-static.test.js` covers live / reload / mid-init, no fallback identity,
|
||||
welcome-only persistence, earlier-welcome priority and a welcome arriving during init. The real-browser recovery
|
||||
test holds a refreshing owner's initialization open while another page clicks recovery and checks both server
|
||||
identities and live sockets; closed-window recovery also checks that welcome arrives without a page reload.
|
||||
`client-identity-handshake.test.js` controls delayed hellos / welcomes through the real client and server handlers,
|
||||
including a later holder's buffered reconnect, same-stamp reconnect, stale-welcome action suppression and validation.
|
||||
- **Golden:** all 285 scenarios regenerated. Only `diy-135` moves: releasing S2 capacity permits more later carries
|
||||
(teleport events 572 → 630), changing movement, attack/heal timing and RNG consumption. Clear time 194.33 → 198.03 s;
|
||||
outcome remains `cleared`, 150/150 kills, zero leaks, one allied death and zero errors. The other 284 scenarios and
|
||||
the `roster`, `bonds`, `fields`, `matches`, `standins` families are unchanged.
|
||||
- **Contracts updated:** design/engine.md, SIM.md (cabin), design/client.md, design/network.md, META.md and PLAYING.md
|
||||
(local identity recovery and handshake).
|
||||
Reference in new issue
Block a user