0.2.3: prepare follow-up fixes and client features (#407, #408, #410, #413, #431, #435, #436, #440, #444)
Add special-target activation without bypassing attack cadence, cooperative card preferences and emotes, skill sound banks, per-operator voice choices, text sizing, install prompts, closed-tab recovery and unanimous setup rerolls. Retain late deployment elapsed time and resolve echo/counter pulses without recursive hook overflow. Independent final review remains pending. Golden: roster-016, roster-035, roster-036, roster-045; boss-boss_1-pair, boss-boss_3-pair, boss-boss_5-pair, boss-boss_7-pair, hidden-boss_9-pair; solo-HARD-9-boosted: Lemuen attack cadence. diy-132: Necras extra activation targets. coop2-NORMAL-14-standins: cooperative purchase preference and simulation fixes. hidden-boss_10-solo: echo pulse FIFO resolution. See DESIGN 28.13-28.24 for assumptions, sources and contributor credit.
This commit is contained in:
1 parent
314564c139
commit
f3658a0dd4
104 files changed
+3674
-741
No files matched your search
+11
-1
@@ -221,7 +221,8 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
|
||||
disconnect, settlementSucceed, settlementFail, settlementTeam, settlementBossSign,
|
||||
goodEvaluation, load, start, matchSucceed, matchFail, matchCancel, joinRoom },
|
||||
battle: { deploy, tokenDeploy, charDie, enemyDie, enemyDieHeavy, enemyHit, heal, leak, win, lose, killCoin },
|
||||
units: { [charId|tokenId|enemyId]: { attack?, hit?, skill?, skills?: {[skillIndex]: url}, die?, born?,
|
||||
units: { [charId|tokenId|enemyId]: { attack?, hit?, skill?, skills?: {[skillIndex]: url}, attacks?: {[skillIndex]: url}, hits?: {[skillIndex]: url}, die?, born?,
|
||||
attackMix?: {[skillIndex]: {p?, vol?}}, hitMix?: {[skillIndex]: {p?, vol?}},
|
||||
mix?: { [attack|hit|die|born]: { p?, vol? } } } }
|
||||
}
|
||||
},
|
||||
@@ -452,3 +453,12 @@ The project's code is GPL-3.0-or-later (`LICENSE`); none of the items below is c
|
||||
- **Novecento Wide:** © Jan Tonellato / Synthview. Free licence.
|
||||
- Both are mirrored from TimWangZi/The-font-of-Arknights.
|
||||
- Noto Sans SC and Noto Serif SC (SIL OFL) are loaded from Google Fonts, not self-hosted.
|
||||
|
||||
### Skill attack banks (0.2.3, PR #410)
|
||||
|
||||
`pickModeAttacks` / `pickModeHits` select a uniform d/h/s bank from the official audio table (attack/combat abilities
|
||||
only). A skill's activation sound supplies its mode letter; without one, d/h/s = slots 1/2/3 is [ASSUMED]. Competing
|
||||
banks prefer START over ON, then plain/numeric order; this is a deterministic approximation, not proof of every
|
||||
operator's runtime ability graph. Mixed-letter banks are skipped. Preserve `mixOf` for each selected bank separately;
|
||||
`attacks`/`hits` and `attackMix`/`hitMix` use matching skill keys. Regenerate the current manifest instead of importing
|
||||
an older PR's full JSON, preserving JP voices and current operators. See DESIGN §28.17.
|
||||
@@ -898,3 +898,15 @@ round was over. The official 1 s `broadcastBeginDelay` is not modelled.
|
||||
* A merge completed after the prep (SETTLE effects) keeps its reward offer for the next prep; its elite goes where a prep
|
||||
merge's would — onto the tile of a consumed deployed copy (PRTS 卫戍协议/帮助 "若消耗已部署至作战区的干员,则发送至作战区
|
||||
对应位置"), else to the hand, overflowing into temp (temp pieces that arrive after the prep wait through the next prep).
|
||||
|
||||
## Opening rerolls and bot reactions (0.2.3)
|
||||
|
||||
`match/setupVote.js` owns the INFO_CHECK vote and transactional opening replacement; `Lobby.rerollSetup` checks host
|
||||
permission for `room.rerollSetup {setupRevision}` and `room.cancelReroll {voteId}`. Participants send
|
||||
`g.rerollVote {voteId, agree}`; `g.infoReady {setupRevision?}` accepts an omitted revision only before the first reroll.
|
||||
`m.public` includes `setupRevision` and `rerollVote` (null or `{id, proposerId, voters, agreed}`). Old vote/revision
|
||||
requests cannot affect a new opening. See DESIGN §28.21 for cancellation and timer behavior.
|
||||
|
||||
`botEmotes.js` is isolated from bot decisions and RNG. It sends the existing `m.emote` only in mixed matches, under
|
||||
normal cooldown/whitelist rules; `SP_BOT_EMOTES=0` disables it. The cooperation score in `bot.js` and these cosmetic
|
||||
choices are explicitly [ASSUMED]; DESIGN §28.15–16 records the source and validation.
|
||||
@@ -194,3 +194,17 @@
|
||||
**什么算一局**:打到结算页的对局,以及中途退出(「放弃模拟」)前至少清完 1 个回合的对局,计入以上所有统计——和官方一样,中途退出按淘汰处理,通过的回合数就是退出时已经通过的回合。**进去就退、没清完第一个回合就退的对局不计入统计**(不算一局,不影响胜率和策略通过率),但会留在「对局记录」里,标着「中途退出」和「不计入统计」。已经被淘汰后再退出的对局算(你输了这一局)。下面这些不会记录:观战;被服务器结束的对局(服务器重启、同盟解散);关掉页面后没有再回来的对局。
|
||||
|
||||
**存储与隐私**:最多保留最近 1000 局,所有记录加起来不超过约 0.8 MB(超出时最旧的先丢)。只有最新的 30 局保留完整的阵容、盟约和队友信息,更早的压缩成你自己的一行汇总。同盟对局的记录里有**队友的代号**,它们保存在本机,也会出现在你导出的文件里;不会发给任何人。想删掉就点「清空」(会先确认),或清除这个网站的数据。读不出来的存档(损坏的数据)会另存为备份并重新开始记录,不会让页面出错;由更新版本导出的文件会被拒绝导入,也不会被旧版本覆盖。
|
||||
|
||||
### 0.2.3 的便捷设置
|
||||
|
||||
设置中的「文字大小」有小、中、大、特大四档,默认小;棋盘尺寸不随文字放大。干员调配详情和已选自选干员卡可
|
||||
单独选择中文、日语或跟随全局语音,只保存在当前浏览器,日语缺失时沿用中文回退。
|
||||
|
||||
同一浏览器的对局窗口意外关闭后,可以从标题或大厅的「恢复本机对局」选择保存的席位。仍被其他窗口使用的席位
|
||||
不会被接管;服务器重启、对局过期或浏览器无法检查其他窗口时不能恢复。
|
||||
|
||||
浏览器提供安装条件时会显示「添加到桌面」。安装后仍需连接游戏服务器,不会把服务器或整套素材离线保存。
|
||||
不支持页面内安装按钮的浏览器可使用其自己的菜单(是否可用由浏览器决定)。
|
||||
|
||||
确认开局信息时,房主可发起「重刷本局」。全体真人同意后重抽开局,保留房间和个人配置;投票暂停确认倒计时,
|
||||
拒绝、取消、断线或退出则保留原开局和剩余时间。只有一名真人时立即刷新。随机结果可能重复。
|
||||
+8
-3
@@ -947,13 +947,13 @@ flag alters movement or 失衡.
|
||||
**and** about to attack/heal **and** an enemy (heal skills: an injured ally) inside the **initial** range
|
||||
(`unit.baseRangeKeys`: its own grid + its permanent rangeExtend — "攻击范围扩大" modules/talents as persist never-expiring
|
||||
`rangeExtend` buffs; no skill range, no temporary extend, no extra keys) or blocked by the melee unit — or, checked
|
||||
**every tick**, an enemy (flyers included) inside a content trigger range (`unit.skill.addTriggerRange(fn)`,
|
||||
**every tick**, an enemy (flyers included) inside a content trigger range (`unit.skill.addTriggerRange(fn, { attackOnly: false })`,
|
||||
`fn(battle, unit)` → list of ally units (their current range), tile-key arrays or `{ keys, profile }` (tile keys with
|
||||
the enemy profile the effect selects by — `canHitFly` false: ground enemies only); returns an unregister fn; not for
|
||||
heal skills: 海嗣 "攻击范围视为自身攻击范围的延伸", 流形, 谬因 S2's beam, and the areas a skill acts through around its
|
||||
owner's standing summons — 麦哲伦 S1 (her drones' ranges), 令 S3 (each summon's x-5, ground), 电弧 S2 (赛柯's range,
|
||||
ground) / S3 (桑特拉's range): the owner's larger-range rule of 2026-10-06, kits/shared/summoner.js
|
||||
`summonTriggerArea`); `SKILL_RANGE` — "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其不可选中)时释放技能": any
|
||||
`summonTriggerArea`). With `{ attackOnly: true }`, the extra range is checked only at the normal attack opportunity, not each tick: 蕾缪安’s own wanted targets and 死芒’s own living summons’ blocked targets (S1/S2); normal selectability and the skill’s actual effect range remain intact (§28.13). `SKILL_RANGE` — "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其不可选中)时释放技能": any
|
||||
living enemy (stealthed, untargetable, flying included) on `trigger.customRangeGrid` (= the skill's rangeGrid), every
|
||||
tick, no attack needed; `trigger.allies` (+ `hpAtMost`, default 1) — set by a kit, or by the data for an ally row (黍 S3's
|
||||
official `TRY_SEARCH_ALLY_SKILL`, "技能范围内存在可治疗的我方单位时释放技能": tools/build-data.mjs `TRIGGER_ALLY_RULES`,
|
||||
@@ -1011,7 +1011,7 @@ flag alters movement or 失衡.
|
||||
`skillEnd` fires. `onAttack` ctx carries `noAmmo` (set it to true: this attack spends no bullet, no `ammoUsed` — 流明's
|
||||
free heals).
|
||||
- Runtime helpers on `unit.skill`: `activate(reason, {free})`, `end(reason)`, `stop()`, `addAmmo(n)`, `extend(s)`,
|
||||
`addCharge(n)`, `gainSp(n, reason)`, `addTriggerRange(fn)`, `setTrigger(rule, grid)` (a kit's own rule change mid-battle —
|
||||
`addCharge(n)`, `gainSp(n, reason)`, `addTriggerRange(fn, { attackOnly?: boolean })`, `setTrigger(rule, grid)` (a kit's own rule change mid-battle —
|
||||
薇薇安娜 S3's ACTIVE_RANGE on 3-2 after its first cast; 0.2.0); fields `sp, spCost (= floor(base×spCostMul + spCostFlat)), spCostMul, charges,
|
||||
maxCharges, active, timeLeft, ammoLeft, ammoMax, activations, kind, rule, bb` (`ammoMax`: the most bullets the running ammo skill
|
||||
has held — set at activation, raised by skillStart additions and `addAmmo` above it; 0 when inactive).
|
||||
@@ -1547,3 +1547,8 @@ window — `push` / `pull`; a wall-shortened slide keeps its row time, a wall at
|
||||
platforms/mounds are ground obstacles that elevate operators; the generic kit maps 凋亡 to `apoptosis` (侵蚀 has no element key
|
||||
yet — enemy content must pick one); 抵抗 covers the control statuses of `RESIST_STATUSES` (the official term lists 晕眩/寒冷/
|
||||
冻结/恐惧/诱导…, the rest follow the operator kits that grant it) and caps at 0.95; tactical points prefer enemy path tiles.
|
||||
|
||||
|
||||
“余音” owns a same-call pulse queue (DESIGN §28.24): reflected hits enqueue their earned pulses instead of recursively
|
||||
entering another pulse. This retains the lethal-hit pulse and prevents valid high hit-count chains from tripping the
|
||||
general hook guard. The queue is emptied in `finally`; it introduces no timer or simulation RNG.
|
||||
@@ -89,3 +89,35 @@ are verified, and cleans up / restores focus on both success and failure. Failur
|
||||
Push/pull slides preserve the target's pre-hit facing through the waiting and moving phases (`keepFacing`, §28.9).
|
||||
Explicit false and unmarked raw displacements face the travel direction. Normal snapshot facing resumes on landing.
|
||||
[ASSUMED] unrecorded push sources and reverse-facing hook pulls use the same display rule as the cited examples.
|
||||
|
||||
### Local preferences and recovery (§28.14, §28.18–20)
|
||||
|
||||
Settings persist `textSize` (sm/md/lg/xl; default sm) and `voiceOverrides` (charId → cn/jp; default empty) alongside the
|
||||
existing global `voiceLang`. CSS text uses `--t`; the layout rem, camera and board geometry stay unchanged. Larger text
|
||||
and translated controls wrap within the settings modal. The normal roster detail and chosen DIY cards expose each
|
||||
operator's voice preference, shared across that charId's forms. An absent override follows the global preference;
|
||||
missing Japanese lines retain the existing Chinese fallback. Overrides are local listening preferences, absent from
|
||||
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
|
||||
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
|
||||
`public/icons/`, including lite packages. The install button appears only for a live browser install offer and hides
|
||||
in standalone/fullscreen windows or after installation. Every offer is consumed before awaiting the prompt, including
|
||||
cancellation or failure. No service worker, offline cache or persistent server is introduced; HTTPS or localhost and
|
||||
browser support still govern installation.
|
||||
|
||||
### Skill-mode sound and delayed models (§28.17, §28.22)
|
||||
|
||||
The audio manifest can carry `attacks` / `hits` plus independent `attackMix` / `hitMix` by skill index. Only an active
|
||||
skill uses a mode bank. Impacts remember the mode at the time of the attack; an end event cancels a queued start for
|
||||
an unknown unit, and a late-join snapshot restores the current mode. A mode without a bank falls back to its normal
|
||||
sound. A selected mode with no mix uses its own default volume/probability, not the ordinary bank's mix.
|
||||
|
||||
A model that arrives after an actual deployment continues Start at the elapsed game time; after the clip's duration it
|
||||
shows the current base pose. A subsequent attack/death supersedes that pending entrance. No extra deployment sound
|
||||
or per-phase animation schedule is added by this change.
|
||||
@@ -189,3 +189,16 @@ and marks. Leaving clears that source's state without clearing another still-dep
|
||||
|
||||
凯尔希·思衡托 S2 uses its mixed attack/heal target condition for automatic activation: either an injured, healable ally
|
||||
or a selectable enemy in its expanded running range; operation cooldown and other controls still apply (§28.10).
|
||||
|
||||
### Additional DEFAULT trigger targets (§28.13)
|
||||
|
||||
`addTriggerRange(fn, { attackOnly: true })` extends the DEFAULT target check at the owner's next attack opportunity.
|
||||
It does not create a per-tick trigger. 蕾缪安 S1–S3 use only their own wanted targets; 死芒 S1/S2 use targets blocked by
|
||||
living summons owned by that operator. Sleeping or otherwise unselectable enemies do not count. This only changes
|
||||
the condition for activation: 死芒 S2 still links inside its own effect range. Other registered summon areas retain
|
||||
the existing per-tick behavior. The optional flag defaults to false for compatibility.
|
||||
|
||||
|
||||
0.2.3 echo/counter interaction (DESIGN §28.24): recursively earned “余音” pulses are drained FIFO by that echo in the
|
||||
same call, retaining the earned attack value and the lethal-hit pulse. [ASSUMED] This local order avoids exhausting
|
||||
the hook-depth guard on scaled hit-count HP; the guard remains unchanged for other recursive handlers.
|
||||
@@ -117,3 +117,25 @@ Official rule (bwiki 盟约, 更新公告 5114): before a match the player canno
|
||||
**自选编队 (0.2.0):** the overlay's third tab fills the four DIY slots (5阶 ×2, 6阶 ×2) with an owned 6★ outside the chess pool (any of its three skills, any module of its elite form but a 集成战略 one) or a prototype (its locked skill / module); only operators with a kit are offered (`welcome.diyKitted`). Per-browser `sp.pref.diy` = `{ v, picks }`, synced as `room.diy { picks }` by the same sync engine; 导出 / 导入 as `{ kind: 'stronghold.diy', v, exportedAt, count, picks }` (`ui/diyModel.js`, `screens/diy.js`). In the match a slotted piece is the operator for its player (name, art, class, bonds from its factions, no 特质, the pick's skill and module; the slot's tier, price and merge), sold only in that player's shop from the slot's 调度中心 level with its own stock; the shop / reward / detail cards and the own pieces carry a 「自选」 badge (`ui/gameLogic/diy.js`); the 0.2.0 自选编队 design subsection has the rules and sources.
|
||||
|
||||
---
|
||||
|
||||
### Cooperative buying, reactions and opening rerolls (§28.15–16, §28.21)
|
||||
|
||||
[ASSUMED] Bot purchasing applies one soft 18-point penalty to non-elite, non-DIY tier V/VI shared-pool cards whose
|
||||
active bonds overlap another living teammate's opening strategy. Human and AI teammates are treated equally; it
|
||||
neither reserves stock nor prevents a stronger purchase. Existing strategy presets and rehearsal counts are unchanged.
|
||||
|
||||
[ASSUMED] AI reactions are cosmetic social choices, enabled by default and disabled with `SP_BOT_EMOTES=0`. Only
|
||||
mixed human/AI matches emit them, through the existing whitelist and cooldown. A human emote gets at most one eligible
|
||||
bot reply. Settlement uses that round's results (kills then damage, stable seat tie-break), once per round; the third
|
||||
chess merge reacts once and resets at settlement. Gifts are classified by trusted source playerId, never display name.
|
||||
These hooks consume no gameplay RNG and schedule no game actions.
|
||||
|
||||
[ASSUMED] During INFO_CHECK the host may request an opening reroll; all connected human participants must explicitly
|
||||
agree, including autoplay humans. Bots and spectators do not vote; one human refreshes immediately. A vote pauses the
|
||||
confirmation timer and blocks Ready. Rejection, host cancellation, disconnect or leaving cancels it and restores the
|
||||
remaining time / prior Ready state. Success preserves the room, seats, settings and connections, redraws the complete
|
||||
opening, resets Ready, and starts the full original timer (single-human matches remain untimed). Fixed stage/bans
|
||||
remain fixed and random results can repeat. A separate seed-derived stream leaves shop/draft RNG untouched; requests
|
||||
have a 300 ms anti-burst guard, revision and vote identities. All new setup data and player DIY stocks are prepared
|
||||
before mutation; data preparation failure leaves the old opening intact. This is a remake convenience, not an official
|
||||
mode rule. Unrelated result-screen changes from PR #408 are not included.
|
||||
+11
-1
@@ -15,7 +15,7 @@ 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.
|
||||
|
||||
### 8.2 Match
|
||||
C→S (all carry `rid`): `g.infoReady` · `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`
|
||||
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:
|
||||
- `m.public` — full public state on every change (throttled ≤ 10/s): `{ phase, round, lastRound, deadline, serverNow, modeId, difficulty, stageId, factions, disabledBonds, bannedChess, bossId, hiddenBossId?, teamLp?, bossHp?:{hp,max}, overtimeAt? /* FA / HC: ms epoch the overtime drain starts, §4 */, paused /* solo pause, §14 */, draft?:{order,turn,picks,skipsLeft,turnDeadline,turnSeconds /* one turn's length, 0 untimed; deadline = turnDeadline (§18.2) */,untimed}, sp?:{family,cards /* bounty cards carry descRaw?, §20.6 */,turn,picks}, players:[{playerId, seat, name, isBot, connected, alive, lp, bandId, shopLevel, boardCount, ready, bonds:[{bondId,count,active,tier,layers,harmony? /* 调和's +1 is in count, §21.26 */}], fieldId, status, pendingLp? /* COMBAT / 联防 of a normal round: the LP the player's own battle will cost at settlement so far, min(lpCapPerRound, counted leaks) — in 联防 min(lpCapPerRound, uniteLeft) — omitted when 0 and in every other phase (§17.5 live LP) */, uniteLeft? /* 联防: the leaker's enemies still standing — not spawned yet + alive + counted leaks, uncapped, live (§20.6) */}] , fields:[{fieldId, kind, players:[playerId...], progress?:{killed, resolved, total, done} /* §14 顶栏胶囊: resolved = the field's own scheduled enemies knocked down or leaked, null while unknown */}] }` · `m.public.uniteResult` — SETTLE after a 联防 only, its outcome as data: `{ through /* the leakers' enemies that still got through, uncapped */, helpers:[playerId...], leakers:[playerId...], losses:{playerId: the LP settlement charged this round — every alive player} }`; absent when no 联防 resolved (the client's result box then reads the round's own battle; GitHub #235, PR #112)
|
||||
- `m.private` — the recipient's full PlayerState view (§6.2 minus server-only data) on every change; in a boss round's prep also `bossMate { playerId, side, units }` — the pair partner's board on its half of the boss field (UnitInfo, mirrored on the right half), re-sent when the partner moves (0.2.0, community report item 51).
|
||||
@@ -117,3 +117,13 @@ Piece = { uid, kind: 'chess'|'item'|'token', id, golden: bool, tier, items: [ {
|
||||
- Emotes: 6 themes × 6 battle emotes (research 09 §4.2), image-only bubbles beside the sender avatar, 1 s cooldown, 3 s bubble; `g.emote {id}` with the official emoji id.
|
||||
|
||||
---
|
||||
|
||||
### Opening reroll messages and local seat recovery
|
||||
|
||||
C→S adds host-only `room.rerollSetup {setupRevision}` / `room.cancelReroll {voteId}` and participant
|
||||
`g.rerollVote {voteId, agree}`. `m.public` carries `setupRevision` and nullable `rerollVote {id, proposerId, voters, agreed}`.
|
||||
Ready must reference the current revision; omission means revision 0 for old clients. The server blocks Ready while
|
||||
voting, refuses stale ids and cancels on disconnect/leave; DESIGN §28.21 / META.md describe the complete contract.
|
||||
|
||||
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.
|
||||
@@ -187,3 +187,160 @@ assertion is corrected to require actual healing, and development builds retain
|
||||
新增真实子进程继承 stderr 的回归,以及关闭失败/信号退出的边界检查;验收要求原异常文件单独重跑并确认正常结束。首轮异常与复测结果分别保留在本轮验收记录中。
|
||||
|
||||
同一轮的版本重连测试在刻意停服时,Chrome 可能报告连接拒绝、连接重置或未连接的 socket。测试只在主动停服至恢复握手的窗口内排除这些已知传输日志;正常运行期间仍检查它们,脚本异常始终保留,所有版本指纹、重连及换局刷新断言不变。
|
||||
|
||||
### 28.13 Special targets respect DEFAULT attack timing — #438–440
|
||||
|
||||
Seen: 死芒's normal attack reaches enemies blocked by his own summons, but S1/S2 did not open for those targets.
|
||||
The earlier 蕾缪安 fix also registered a per-tick trigger, allowing an early cast before her attack countdown elapsed.
|
||||
The [PRTS mode help](https://prts.wiki/w/卫戍协议/帮助) describes DEFAULT activation at the next ordinary attack.
|
||||
`addTriggerRange` now accepts `attackOnly`; both operators use it, while existing summon-area triggers retain their
|
||||
per-tick behavior. Only live owned summons / wanted marks contribute selectable targets. S2's actual link range is
|
||||
unchanged. PR #438 by @YinqiMargatroid addresses the same wanted-target symptom; the existing registration API is
|
||||
extended instead of introducing a second targeting flag. SIM.md and design/engine.md contain the current contract.
|
||||
|
||||
Tests: real summoned-bone blocking, S1/S2 and united fields, attack countdown, sleep/ownership, wanted-target S1–S3,
|
||||
plus existing 令 / 麦哲伦 trigger regressions. The combined operator checks pass 27 tests; the additional area-trigger
|
||||
and champagne controls also pass. Golden movement is recorded below.
|
||||
|
||||
### 28.14 Interface text size — #435 / PR #446
|
||||
|
||||
Adapted from @7eLaryn: four persisted text sizes use `--t` without changing the rem layout root. All shipped language
|
||||
packs retain their current translations. Added the missing explanatory note and fixed English XL settings overflow:
|
||||
segmented controls and header actions wrap within their available width. Long briefing alliance labels size and wrap
|
||||
with their cards instead of overlapping neighboring names. Default sm preserves the previous sizes.
|
||||
|
||||
Tests: settings / text-unit checks, phone landscape growth and unchanged board/canvas/shop geometry, reload persistence,
|
||||
and XL settings in Chinese, English, Japanese, Korean and Traditional Chinese. The first multilingual check exposed
|
||||
English overflow; the corrected browser suite passes. Current behavior is in design/client.md and PLAYING.md.
|
||||
|
||||
### 28.15 Cooperative card purchasing — PR #407
|
||||
|
||||
Adapted from @IceCodeNew. [ASSUMED] A tier V/VI shared-pool card matching another living teammate's active opening
|
||||
bond receives one 18-point buying-score penalty. It is a soft preference, not reserved stock; normal/elite and DIY
|
||||
stock semantics remain unchanged. Humans and bots receive the same cooperation. This does not add persona presets.
|
||||
|
||||
Tests: 14 targeted bot tests; 40-seed solo:HARD and coop4:HARD before/after benchmark is part of integration evidence.
|
||||
The comparison includes the special-target timing changes of §28.13, so it must not be interpreted as an isolated
|
||||
measurement of the purchasing heuristic. Current rule: design/match.md §6.6 and its follow-up contract.
|
||||
|
||||
### 28.16 AI teammate emotes — PR #444
|
||||
|
||||
Event wiring from @DDDarkstar, with corrected dispatch semantics. [ASSUMED] Cosmetic reactions are enabled only in
|
||||
mixed human/AI matches, under the existing emote whitelist and cooldown; `SP_BOT_EMOTES=0` disables all hooks.
|
||||
Responses skip cooling bots and stop after one reply. MVP uses this round's kills and damage, with stable order for
|
||||
ties; settlement reacts once per round. Only the third chess merge reacts, then resets for the next round. A gift uses
|
||||
its source playerId rather than a display-name heuristic. No RNG, gameplay decisions or timers are introduced.
|
||||
|
||||
Tests cover whitelist/cooldown, no human impersonation or recursive AI reply, pure-AI silence, round statistics,
|
||||
settlement/merge limits, gift identities, the disable switch and actual emote/acquisition hooks. README, META.md and
|
||||
design/match.md document the switch and boundary.
|
||||
|
||||
### 28.17 Active skill attack and impact sounds — PR #410
|
||||
|
||||
Adapted from @Convey123's updated PR. The current official `audio_data.json` gives 司霆惊蛰 mode attack banks and
|
||||
赤刃明霄陈 S3 separate swing/impact banks. The generator adds per-slot `attacks` / `hits`, each with its own optional
|
||||
`attackMix` / `hitMix`. It preserves the selected bank's chance and volume rather than applying the normal attack's
|
||||
mix. A late impact keeps the mode at launch; an end before spawn cancels a pending start. Snapshot restoration and
|
||||
ordinary-mode fallback remain supported. JP voices and current roster assets survive regeneration.
|
||||
|
||||
[ASSUMED] Where no activation sound names the mode, slot 1/2/3 is inferred as d/h/s. Bank selection prefers START to
|
||||
ON and then plain/numeric order; this approximates an ability graph and is not independently established for every
|
||||
operator. Mixed-letter banks are rejected. Instant effects that emit no normal attack event remain outside this
|
||||
mechanism. ASSETS.md and design/client.md specify the manifest/runtime contract.
|
||||
|
||||
Tests: 85 audio, manifest, official-mix and JP-voice checks, including independent mode gain, delayed impacts,
|
||||
end-before-spawn, late join and field reset. Regeneration is additive; no existing manifest leaf was removed. Four
|
||||
unresolved upstream candidate icon/avatar leaves still make strict asset fetching report failure; they are not new
|
||||
sound failures or deletions of existing entries. Packaging must retain this limitation visibly.
|
||||
|
||||
### 28.18 Per-operator voice language — #436
|
||||
|
||||
Roster details and selected DIY cards offer 中文 / 日本語 / 跟随全局. Settings persist a sanitized charId map locally;
|
||||
normal/elite/DIY forms of that charId share the choice. Existing profiles migrate to an empty map, so global language
|
||||
continues to apply. Deleting an override restores the current global choice. Missing Japanese slots / files keep the
|
||||
existing Chinese fallback. No room protocol or exported combat-loadout data changes.
|
||||
|
||||
Tests cover malformed/inherited entries, persistence, changing the global default, deleting an override and fallback;
|
||||
a real phone-sized browser changes the control, reloads and resets it. Current contract: design/client.md / PLAYING.md.
|
||||
|
||||
### 28.19 Browser installation — PR #413
|
||||
|
||||
Based on @Cromemadnd's proposal, with a one-use install controller. Buttons appear only after a browser install offer;
|
||||
standalone/fullscreen and installed sessions hide them. Accept, dismiss, failure and duplicate clicks all consume the
|
||||
same offer once. See [the prompt API](https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent/prompt)
|
||||
and [installability requirements](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable).
|
||||
The existing original fortress favicon supplies correctly sized 192/512 px icons, shipped outside optional game art.
|
||||
No service worker or offline cache is installed; the server is still required.
|
||||
|
||||
Tests: offer lifecycle/disposal, actual PNG dimensions, real Chrome manifest and installability checks, and a synthetic
|
||||
dismissed offer through the actual button. Initial incognito rejection was a test-environment limitation; the real
|
||||
installability check runs in Chrome's ordinary temporary profile. Native OS installation itself is not performed.
|
||||
|
||||
### 28.20 Recover a closed local match window — #431
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
the match tab. The existing identity/static suite also passes. Current contract: design/client.md and network.md.
|
||||
|
||||
### 28.21 Unanimous opening reroll — #77 / PR #408
|
||||
|
||||
Adapted from @Siornya, excluding the unrelated result-screen redesign. [ASSUMED] This remake feature is host-proposed,
|
||||
requires every connected human's consent, and pauses confirmation while voting. Bots/spectators do not vote; autoplay
|
||||
humans still do. Single-human matches refresh immediately. Refusal, cancellation, disconnect or leave restores the
|
||||
old setup and remaining confirmation time. Success preserves room/seats/settings, resets Ready and restarts the full
|
||||
normal timer; fixed stages/bans remain fixed. The random result may repeat.
|
||||
|
||||
A dedicated seed-derived reroll stream does not advance the original shop/draft/meta streams. A 300 ms anti-burst guard,
|
||||
setup revision and vote id reject stale actions. Opening data and every player's DIY stock are prepared before any
|
||||
mutation; a forced failure on the second player's stock leaves all previous state intact. Current contracts are in
|
||||
META.md, design/match.md, design/network.md and PLAYING.md; all four translation packs have the new messages.
|
||||
|
||||
Tests: 13 match checks including real four-player sockets, permission/spectator checks, cancellation/disconnect,
|
||||
reconnect, last-second and queued-Ready timers, outdated messages, one human with bots, fixed rules, fuzzed requests
|
||||
and transactional failure. Final browser/integration validation supplements these targeted checks.
|
||||
|
||||
### 28.22 Late-loading deployment animation — part of PR #416
|
||||
|
||||
@Cromemadnd's proposal exposed a real missing case: onDeploy arrived before the model, so Start was never played.
|
||||
The view now retains elapsed game time until the model arrives and continues the existing clip at that point. A clip
|
||||
already over is not restarted; an intervening attack/death cancels the pending entrance. Existing Front/Back transfer
|
||||
and single-source deployment audio remain unchanged. The proposed repeated phase/purchase sound sequence is not part
|
||||
of this fix: its timing and duplication need separate visual evidence.
|
||||
|
||||
Tests: delayed load, simulation rate, load after clip end, attack/death supersession, destroyed view and existing
|
||||
UnitView behavior, 48 checks passing. No simulation/golden change from this visual fix.
|
||||
|
||||
### 28.23 Follow-up golden movement
|
||||
|
||||
Recomputed all 285 scenarios from the integrated code; never copied PR baselines. The DEFAULT cadence correction moves
|
||||
`roster-016`, `roster-035`, `roster-036`, `roster-045`; paired fields `boss-boss_1-pair`, `boss-boss_3-pair`,
|
||||
`boss-boss_5-pair`, `boss-boss_7-pair`, `hidden-boss_9-pair`; and match `solo-HARD-9-boosted`. 死芒's added activation
|
||||
condition moves `diy-132`. `coop2-NORMAL-14-standins` includes both cooperative purchasing and the corrected simulation.
|
||||
The pulse queue correction of §28.24 additionally moves `hidden-boss_10-solo`. The bonds and standins families are unchanged. Cosmetic emotes, preferences, installation and opening rerolls that
|
||||
are not requested do not intentionally alter those games. The validation record lists any outcome flips and measured
|
||||
benchmark changes; the independent final review remains pending.
|
||||
|
||||
### 28.24 Echo / counter recursion found by the cooperative benchmark
|
||||
|
||||
The 40-seed benchmark exposed existing hook-depth errors in co-op seeds 3 and 29, plus seed 14 after the purchasing
|
||||
change. In all three, “余音” damage pulses and 刺玫 / 泡泡 counters synchronously called each other. A scaled echo can
|
||||
have 67 hit-count HP; the generic 32-hook guard cut off effects before the legitimate chain finished. The same chain
|
||||
could also enter another hit while zero HP was waiting for death bookkeeping.
|
||||
|
||||
[PRTS “余音”](https://prts.wiki/w/“余音”) establishes the hit-count and damage-pulse behavior. [ASSUMED] The exact nested
|
||||
ordering is not documented: each echo now queues recursively earned pulses and resolves them FIFO in the same call,
|
||||
after the current pulse finishes. Every earned pulse retains its attack value, including the lethal hit's pulse. No
|
||||
extra tick, RNG draw, global hook-depth increase or arbitrary pulse suppression is introduced. Death prevents further
|
||||
counter targeting normally. This local scheduling choice needs final fidelity review.
|
||||
|
||||
Tests use real elite 刺玫 and 泡泡 against 67-hit echoes: all 67 pulses resolve, the echo dies, no engine error is logged,
|
||||
and the queue clears. Both tests fail against the previous implementation. Together with the existing enemy/boss and
|
||||
刺玫 healing suites, 273 tests pass. The benchmark now writes actual diagnostic messages beside nonzero error counts,
|
||||
so failures cannot be mistaken for ordinary performance results. Recomputed golden and final benchmark results are
|
||||
recorded with the candidate; do not reuse the earlier, erroneous benchmark as a clean result.
|
||||
Reference in new issue
Block a user