feedback1 final: merge the 0.1.1 gaps (变形同构体 pairings, 调和 +1 row, bands tied to a switched-off bond, the client-result layer bound)
This commit is contained in:
commit
3b4d44bda8
26 files changed
+1332
-65
No files matched your search
@@ -302,6 +302,7 @@ everything needed to resolve a unit for `(chessId, skillIndex, moduleId)` (`simd
|
||||
| `effectId`, `effectName`, `desc`, `descRaw` | `"aceffect_band_21"`, `"重点监护"`, `"【重点监护】开始作战时…"` | |
|
||||
| `buffs[]`, `params` | `[{"key":"prep_finish_char_bond_add_layer","bb":{"layer":2},"bbStr":{}}]` | |
|
||||
| `victorCount`, `rewardModulus`, `unlockDesc` | `3`, `1`, `null` | |
|
||||
| `bondIds` | `["lateranoShip"]` (潘格尼尼) / `[]` | the bonds the strategy is built around (`shared/bandBonds.js`: the bond names in <…> of its text — the official note "在<X>部分干员缺席时体验可能不完整" among them — and the bond ids / bond pools of its blackboards, `pool` → choices.json `pools[pool].bond`); 9 bands are tied. The bot never picks, and the strategy draft marks 本局禁用, a band whose bond the mode switches off (DESIGN §21.26) |
|
||||
|
||||
## 7. `effects.json` — `{ [effectId]: Effect }` (361: every effect of the season)
|
||||
|
||||
|
||||
+19
-7
@@ -367,7 +367,7 @@ Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (inco
|
||||
|
||||
### 6.3 Bonds & layers (bondsMeta.js)
|
||||
|
||||
- `count` = number of **distinct base chess** on the board carrying the bond (`BOARD`); `BOARD_AND_DECK` bonds also count the hand; 绝技 counts elites on board; 调和 adds +1 to each core bond count, etc. (research 02).
|
||||
- `count` = number of **distinct base chess** on the board carrying the bond (`BOARD`); `BOARD_AND_DECK` bonds also count the hand; 绝技 counts elites on board; 调和 adds +1 to each core bond count that has a real member on the board (the views mark such an entry `harmony: 1`, §21.26), etc. (research 02).
|
||||
- 变形同构体: an operator wearing it together with an item that has a `giveBondId` is a member of that bond — counted like any member (BOARD: on the board only; once per operator), in the battle, and in the client's member list (tagged 同构) and bond chips (§21.11).
|
||||
- `tier` from thresholds; `active = tier ≥ 1`.
|
||||
- `layers[bondId]` persist all match; gains come from garrisons/bands/items/choices/bond effects; IN_BATTLE gains are collected from `BattleResult.layerGains` at SETTLE (disabled in unite/boss/hidden).
|
||||
@@ -384,7 +384,7 @@ Per match: pick stage (weighted among the mode's `config.modes[m].stages`, weigh
|
||||
|
||||
### 6.6 AI players (bot.js)
|
||||
|
||||
Deterministic-ish heuristic player used for (a) AI teammates, (b) auto-play for disconnected humans (only auto-ready + auto-picks, not shopping, unless "AI 托管" is toggled), (c) tests. Strategy (§21.6): pick a band (weighted by starting LP); each prep: take offers, sell dead bench weight, fill the board, complete affordable merges, level on the curve, then buy only merge progress / lineup upgrades worth more than the refreshes their price would buy (shop odds of completing held pairs), freeze for an unaffordable third copy (坎诺特 keeps 5 funds back); build around a committed focus core bond (a teammate's main bond on its strip counts against it; bonds the mode switches off never) + a second bond; value dealers by DPS vs the round's DEF / RES (an attack on every enemy in range counts double — 阵法术师 / 轰击术师), with the range the unit is deployed with; place by the exposure model + rehearsal on the server's deploy map (no 深水区; a tactician's 援军 within `PlayerState.summonRange`; `g.move` judges legality); items by effect (META §1.5: 突变细胞 on the least valuable normal single below 6阶, a cell left in temp gets a hand slot made for it); 机变 bounties by expected payout − expected LP loss over the offered cards; resolve temp — on a human seat under AI 托管 never by destroying an item while a bench operator can be sold for the slot —; ready. A bot's whole prep — shop decisions and layout planning (`botPrepBeginSteps`, 25–70 ms late in a 4-bot match), the layout rehearsal and `botPrepEndSteps` — runs in wall-clock slices of `botSliceMs` (8 ms) per scheduler callback on a real host (step generators that yield between whole actions, never with a transient board; the same actions in the same order, so decisions are unchanged; at once in virtual time).
|
||||
Deterministic-ish heuristic player used for (a) AI teammates, (b) auto-play for disconnected humans (only auto-ready + auto-picks, not shopping, unless "AI 托管" is toggled), (c) tests. Strategy (§21.6): pick a band (weighted by starting LP; never one built around a bond the mode switches off — `gd.bandBondIds`, §21.26); each prep: take offers, sell dead bench weight, fill the board, complete affordable merges, level on the curve, then buy only merge progress / lineup upgrades worth more than the refreshes their price would buy (shop odds of completing held pairs), freeze for an unaffordable third copy (坎诺特 keeps 5 funds back); build around a committed focus core bond (a teammate's main bond on its strip counts against it; bonds the mode switches off never) + a second bond; value dealers by DPS vs the round's DEF / RES (an attack on every enemy in range counts double — 阵法术师 / 轰击术师), with the range the unit is deployed with; place by the exposure model + rehearsal on the server's deploy map (no 深水区; a tactician's 援军 within `PlayerState.summonRange`; `g.move` judges legality); items by effect (META §1.5: 突变细胞 on the least valuable normal single below 6阶, a cell left in temp gets a hand slot made for it); 机变 bounties by expected payout − expected LP loss over the offered cards; resolve temp — on a human seat under AI 托管 never by destroying an item while a bench operator can be sold for the slot —; ready. A bot's whole prep — shop decisions and layout planning (`botPrepBeginSteps`, 25–70 ms late in a 4-bot match), the layout rehearsal and `botPrepEndSteps` — runs in wall-clock slices of `botSliceMs` (8 ms) per scheduler callback on a real host (step generators that yield between whole actions, never with a transient board; the same actions in the same order, so decisions are unchanged; at once in virtual time).
|
||||
|
||||
---
|
||||
|
||||
@@ -415,7 +415,7 @@ Platform additions (implemented): `welcome.{resumed, version}`, `room.state.inMa
|
||||
### 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}` · `g.ready {ready}` · `g.emote {id}` · `g.watch {fieldId}` · `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}], 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...]}] }`
|
||||
- `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...]}] }`
|
||||
- `m.private` — the recipient's full PlayerState view (§6.2 minus server-only data) on every change.
|
||||
- `m.field` — field meta when watching starts: `{ fieldId, kind, rect, stageId, units: [UnitInfo] }` (the units on the field, knocked-out operators waiting to redeploy included — `Battle.fieldMeta`, §18.3)
|
||||
- NOTE: game time in `b.snap`/`b.ev` is carried as **`gt`** (the frame's `t` is the message type).
|
||||
@@ -438,7 +438,7 @@ S→C:
|
||||
temp: [ Piece | null ], // length 5
|
||||
board: [ Piece & { row, col, dir } ], // dir ∈ UP|RIGHT|DOWN|LEFT (§3; bench pieces carry none)
|
||||
deployCap, deployCount,
|
||||
bonds: [ { bondId, count, active, tier, layers, thresholds, countsHand } ], // sorted: active first, then layers desc
|
||||
bonds: [ { bondId, count, active, tier, layers, harmony? /* 调和's +1 is in count, §21.26 */, thresholds, countsHand } ], // sorted: active first, then layers desc
|
||||
effects: [ { id, name, desc, iconKind: 'band'|'choice'|'team'|'item'|'garrison', iconId, counter?, counterText? /* a bounty: 还剩 N 场作战, §20.6 */ } ],
|
||||
nextEnemies: [ { enemyKey, count, tag } ], // preview of the upcoming round's wave (after waves.js generation)
|
||||
stats: { dmgDealt, kills, leaks, gold, refreshes, merges } }
|
||||
@@ -550,7 +550,7 @@ Audio: autochess BGM per phase, UI SFX (buy/sell/refresh/level/merge/ready/timer
|
||||
- `m.public.fields[]` gains `progress: { killed, total, done }`; `m.public.combatMode`; `m.public.paused: boolean` (always present; solo pause below).
|
||||
- `b.snap` / `b.ev` are **not sent for combat** in client mode. `g.watch` rules: see Spectating.
|
||||
|
||||
**Result validation (server, `fields.js validateClientResult`).** A client result is accepted only if structurally valid and plausible for its spec: every spec player reported and nobody else; finite numbers; `killed ≤ total ≤ 4·spawns + 100`; every leaked `enemyKey` is in the spec's spawns (multiset bound) or a key the spawned enemies' data mentions (content summons/splits, bounded by the total) — boss fields excepted (their leaks cost team LP through `b.progress`); `counted: false` only for enemies that never count (data `notCountInTotal`, schedule `countInTotal: false`, boss/part entries, content spawns); 联防: leaks + `unspawned` per (enemy, leaker) ≤ what that leaker sent in (nobody's survivors are billed to a teammate); per-bond `layerGains ≤ min(60 + 4·round, 999 − the bond's starting layers)` (§20.12), none when the spec disables gains, bonds must exist and be named by the player's bond snapshot, band, effects, units or items; `coins ≤ Σ bounty coins of the spawns`; `perfect` consistent with the counted leaks; unit states only for the player's own board uids with hpPct ∈ [0, 1]. The accepted result is **rebuilt** from whitelisted fields (leak mods/tags/sources from the spec — a content-spawned enemy (split, summon) keeps the mods of the schedule entry they equal, i.e. its parent's round multipliers and bounty id, so its 联防 re-entry and bounty match the server run; unit names from server data). Otherwise (or on any exception) the server re-simulates (normal/联防); a boss field is **handed over** instead (below). `SP_VERIFY=all|sample|off` (default `off`): `all` re-simulates every accepted normal/联防 result before accepting it (the server result wins on a digest mismatch); `sample` checks ~1 battle in 8 in a later callback and logs mismatches. `Match.verifyStats = { checked, mismatches, rejected, takeovers }`. Desync between display-only clients and the authority is tolerated: the authoritative outcome is what counts and is broadcast at settlement.
|
||||
**Result validation (server, `fields.js validateClientResult`).** A client result is accepted only if structurally valid and plausible for its spec: every spec player reported and nobody else; finite numbers; `killed ≤ total ≤ 4·spawns + 100`; every leaked `enemyKey` is in the spec's spawns (multiset bound) or a key the spawned enemies' data mentions (content summons/splits, bounded by the total) — boss fields excepted (their leaks cost team LP through `b.progress`); `counted: false` only for enemies that never count (data `notCountInTotal`, schedule `countInTotal: false`, boss/part entries, content spawns); 联防: leaks + `unspawned` per (enemy, leaker) ≤ what that leaker sent in (nobody's survivors are billed to a teammate); per-bond `layerGains ≤ min(60 + 4·round, 999 − the bond's starting layers)` (§20.12) — the flat part raised by what the player's layer 特质 can add to that bond (their per-battle caps; an uncapped one leaves only the 999 room, §21.26) —, none when the spec disables gains, bonds must exist and be named by the player's bond snapshot, band, effects, units or items; `coins ≤ Σ bounty coins of the spawns`; `perfect` consistent with the counted leaks; unit states only for the player's own board uids with hpPct ∈ [0, 1]. The accepted result is **rebuilt** from whitelisted fields (leak mods/tags/sources from the spec — a content-spawned enemy (split, summon) keeps the mods of the schedule entry they equal, i.e. its parent's round multipliers and bounty id, so its 联防 re-entry and bounty match the server run; unit names from server data). Otherwise (or on any exception) the server re-simulates (normal/联防); a boss field is **handed over** instead (below). `SP_VERIFY=all|sample|off` (default `off`): `all` re-simulates every accepted normal/联防 result before accepting it (the server result wins on a digest mismatch); `sample` checks ~1 battle in 8 in a later callback and logs mismatches. `Match.verifyStats = { checked, mismatches, rejected, takeovers }`. Desync between display-only clients and the authority is tolerated: the authoritative outcome is what counts and is broadcast at settlement.
|
||||
|
||||
**Boss rounds.** The shared pool (`SharedBossPool`, co-op size = `bloodPoint[difficulty]`, see corrections) lives on the server and is fed by the authorities' `b.progress` damage deltas (attributed per player through `by`; BOSS_HIT tickers from each player's damage to that round's pool, §21.10) and by server-run fields; a server clock (250 ms) applies the overtime drain (−`bossOvertimeDrainPerSec` team LP per whole **real** second past `bossOvertimeAfter` real s, measured on the field clock — `gamedata.js bossOvertimeDue`, §4), the silence watchdog and the end checks: pool 0 → `b.end cleared` (victory), team LP 0 → `b.end forced` (defeat) — the first one the server registers decides the verdict (`Match._finalEnding`) and after a forced end no report (an in-flight `b.progress`, the final `b.result`) credits the pool (§20.5); results are awaited ≤ 6 s (× timerScale) after a forced end. `b.pool` is the live channel for the pool and the team LP: a boss `b.progress` (4 Hz per field) and the team-LP losses never re-mark the whole public state — `m.public` (`bossHp`, `teamLp`, `fields[].progress`) and the players' merged-LP shares (`_syncTeamLp`) refresh at ~1 Hz (`Match._bossPublic`, `BOSS_PUBLIC_MS`), and at once when the round ends. A boss field's `b.result` ends it only when the shared pool is empty or after that `b.end` — except a `'cleared'` result whose cumulative report covers what the pool still holds (within its 1 HP floor) while the plausibility budget (one field credits ≤ the pool per `BOSS_MIN_CLEAR_GS` 5 game s) has not caught up: it is **held** (`f.heldResult`; 999-layer boards kill a leader in 2–4 game s, §20.14) — the boss clock keeps crediting the report at the budget's rate, the silence watchdog and handovers skip the field, a re-sent copy is ignored, and the pool's end (`_endFinal`) completes the field with it; a forged report gains nothing a `b.progress` could not already get. Any other result — `'forced'` / `'timeout'` at t = 0, `'cleared'` while the pool still holds more than the report covers — and any result failing validation would stop the pair's fight (and, with every field done, end the Final Assault as a defeat), so the field is **handed over** (`Match._bossHandover`) to the partner's display replica (promoted with `b.start authoritative: true`) or, without one, to the server (fast-forwarded to the field clock); the sender gets `b.end takeover` and is never that field's authority again (`f.demoted`). There is no synthetic zero result for a running boss field: a stats-only stand-in (`fields.js syntheticResult`, carrying the damage already credited, never charging LP) only fills a result that cannot be had otherwise — the Final Assault already ending (no result, or an invalid one, within the grace) or a server-run battle whose `result()` threw.
|
||||
|
||||
@@ -1002,7 +1002,7 @@ Still [ASSUMED] (each in its section): the class rows covering every MANUAL skil
|
||||
|
||||
- **Asked**: the co-op leaders as hard to kill as in the official game. Research 11 (`docs/research/11-limits-official.md`) found two official caps in the client (2.7.71 il2cpp) that the remake lacked.
|
||||
- **Official** [DATA]: `AutoChessBattleConst.MAX_GARRISON_STACK = 999` — `AddBondCount` stores `min(L + n, 999)`, per bond. `MAX_BATTLE_DAMAGE = 300000` — in step mode (every boss battle outside training) `AutoChessStepModeManager._OnBossEnemyTakeDamage` runs `modifier.Cancel()` when `ceil(value) ≥ 300000` on an `IsBossEnemy` unit; `IsBossEnemy` (disassembled, research 11 §2.3) = enemyId ∈ `autoChessData.bossInfoDict[*].enemyId` = data/bosses.json `enemyKey`. [COMM] bonds stay at 999 while fed; 「一次超过30w会被卡掉」: players avoid extra multipliers at 999 layers. 限伤 is a deliberate constant, **not** an engine fixed-point overflow — the boss-HP workstream's earlier reading (2³¹, "not modelled", §20.10) is superseded.
|
||||
- **Now — layers**: the single implementation is `shared/constants.js BOND_LAYER_CAP` + `layerGainRoom(before, n)` = min(n, 999 − before), never negative (the boss-HP workstream's duplicate `sim/constants.js BOND_LAYER_CAP / layerRoom` was removed in the merge). Every writer uses it: `PlayerState.addLayers` (特质, items, bands, 机变 cards, bonds), the SETTLE of `BattleResult.layerGains`, `Battle.addLayers` (the live copy, clamped after the `layerGain` hook, like the client), and the dev tools' direct writes (`matchrun --layers N`, `balance.mjs applyBoard`). A gain at the cap adds 0: no onLayers, hook or 'layer' event, so the per-N milestones (远见, 奇迹, 维多利亚 …) stop with the count. Client results: per-bond `layerGains ≤ min(60 + 4·round, 999 − starting layers)`. `invariants.js` flags a bond above 999; the UI shows the server's capped count.
|
||||
- **Now — layers**: the single implementation is `shared/constants.js BOND_LAYER_CAP` + `layerGainRoom(before, n)` = min(n, 999 − before), never negative (the boss-HP workstream's duplicate `sim/constants.js BOND_LAYER_CAP / layerRoom` was removed in the merge). Every writer uses it: `PlayerState.addLayers` (特质, items, bands, 机变 cards, bonds), the SETTLE of `BattleResult.layerGains`, `Battle.addLayers` (the live copy, clamped after the `layerGain` hook, like the client), and the dev tools' direct writes (`matchrun --layers N`, `balance.mjs applyBoard`). A gain at the cap adds 0: no onLayers, hook or 'layer' event, so the per-N milestones (远见, 奇迹, 维多利亚 …) stop with the count. Client results: per-bond `layerGains ≤ min(60 + 4·round, 999 − starting layers)` (+ the lineup's layer 特质, §21.26). `invariants.js` flags a bond above 999; the UI shows the server's capped count.
|
||||
- **Now — 限伤**: kinds `'boss'` / `'hidden'` only; a leader is `isBoss` (the tag-'boss' units: those 10 keys and their mirrored copies — parts, 刺胄之弹, drones and escorts never count). A hit with `ceil(final) ≥ 300000` is cancelled: checked in `dealDamage` after DEF/RES and every multiplier, before HP shields, and in `loseHp` (a part's 传递, a drone's death). A cancelled hit causes no HP or pool loss, no credit or stats, no damage number, no `damaged` / `fatal` / kill; the attack, its SP, the `hit` hook and separate element 损伤 hits stay. Element bursts and DoT ticks are checked, gauge fills are not. The sim emits fx `hitCap {id, n}`, drawn as nothing (deterministic in Node and browsers; SP_VERIFY digests agree).
|
||||
- **The line vs the pool**: 【死亡集群】 drone deaths cost 0.02 × the pool max (§20.10, §20.13) — at most 144 000 today (boss_8 终极 7.2M), so it lands; a pool above 14 999 950 would cancel every drone death. An alive-scaled pool (§20.9, open) can only shrink, but any larger pool must re-check it; a real-kit test pins the line.
|
||||
- **[ASSUMED]**: the scene server clamps prep gains the same way; a cancelled hit shows nothing; a loss passed on to a leader is one hit; the order hit-step blocks → 限伤 → HP shields — 阿利斯泰尔's 莫非王土 vest (`boss:vest`, 5000): a hit of 300000–304999 is cancelled and the barrier stays; 假想敌:再生's blocks on 假想敌:管 (hidden h08_03) negate a hit past the line before the limit sees it.
|
||||
@@ -1230,7 +1230,7 @@ Eighteen workstreams (WA–WR, each on its own branch from master 7f13473) were
|
||||
- **Seen**: with 变形同构体 (缪尔赛思's 特质 garrison_76 / band 变形者集群) and the matching item the wearer gets the bond's effect, but "那个盟约的人口数量没有把转职之后的干员算进去". **Partly real**: the count that activates the bond already included the wearer everywhere (`bondsMeta.pieceBonds` for the prep count, m.private / m.public, the battle input; `support.unitBonds` in the battle — checked on real matches for all 36 pairings). The client did not: the popup's member list and the wearer's card read only the records' bonds (在场 3/6 next to 成员 2/10, no chip).
|
||||
- **Official**: band 变形者集群 "与特定装备一同装备时装备者将视为特定盟约成员"; eff_acarm073 "携带者获得额外盟约(由另一件携带装备而定)"; the item's talent lists the 14 pairings; a member counts on the board only (BOARD) and once per operator.
|
||||
- **Now**: `gameLogic.grantedBonds` = the server's rule (a test pins the three together); the popup lists wearers tagged 同构, the card shows the granted bond as a dashed 同构 chip; a 同构 row opens the wearer's card with its item ids; a card without an own piece lists the carried items read-only under 装备. UnitInfo `items?` (sim `unitInfo`, `Match.prepFieldMeta`) carries an ally operator's item ids, so teammates' popups and cards show wearers too.
|
||||
- [ASSUMED], unchanged: operator talents that test "【拉特兰】/【卡西米尔】/【叙拉古】… 干员" read the record's bonds (kits tier1 内线 ammo pick, tier5 inFaction, tier6 hasBond, tokens.js 卡西米尔 sword) — no source says whether a converted operator counts for them; 调和's +1 raises 在场 for core bonds without a member row.
|
||||
- [ASSUMED], unchanged: operator talents that test "【拉特兰】/【卡西米尔】/【叙拉古】… 干员" read the record's bonds (kits tier1 内线 ammo pick, tier5 inFaction, tier6 hasBond, tokens.js 卡西米尔 sword) — no source says whether a converted operator counts for them; 调和's +1 raises 在场 for core bonds without a member row (explained since §21.26: 含调和 +1 and a 调和 row).
|
||||
- Tests: `test/ui/morph-bonds.test.js`, `test/ui/watch-bonds.test.js`, server-side locks in the match suite.
|
||||
|
||||
### 21.12 源石溶剂 is damage; 信仰搅拌机 S3 counters any damage (D1) — `sim/damage.js periodicDamage / isHpLoss / applyHpLoss`, `items/battle.js periodic_damage`, kits tier1 / 2 / 4 / 5, `content/enemies.js`, `bonds/addon/battle.js`, `choices.js`
|
||||
@@ -1407,3 +1407,15 @@ Not done (noted, no primary source or out of scope): whether 华法琳 S2's 流
|
||||
|
||||
- **The prep dispatcher's equipped items** (pointed out in PR #2, whose 突变细胞 rule is superseded by §21.1): step 5 iterated `holder.items` live while handlers move and destroy pieces — normal 博士投影 destroys itself at the round start (the splice skipped the next item on that holder), 突变细胞 transforms its holder (the next item ran with a holder that was gone). It now walks a snapshot of the owned chess and of each holder's items and runs an item only while it is still equipped on its still-owned holder; a piece gained or equipped meanwhile waits for the next dispatch (effectsMeta header, META §2.2). No shipped item has a second hook on those events, so play is unchanged; a synthetic spy handler pins the rule (`test/match/feedback1-dispatch-snapshot.test.js`). QA after the integration: the items were read per holder as the walk reached it, so an item equipped meanwhile onto a holder not yet walked ran in the same dispatch and an item moved to a later holder ran twice (latent: no shipped item equips or moves items on those hooks); every [holder, item] pair is now taken before the first item runs, and two more synthetic cases pin it.
|
||||
- **fetch-assets never shrinks the committed manifest by accident** (pointed out in PR #7, whose `data/assets.json`, regenerated on a machine where some downloads failed, was 42 audio entries short — all 42 still resolve upstream): a rebuilt manifest leaves out every entry whose files are missing on that machine. When it would lose an entry of the current `data/assets.json`, the run keeps the current file, prints the entries it would drop (also the report's `droppedEntries` / `manifestWritten`) and exits 1; `--allow-shrink` (or `--prune`) writes it (ASSETS.md; `test/fetch-assets-shrink.test.js`).
|
||||
|
||||
### 21.26 Gaps found while triaging GitHub issues #1 / #8 (v0.1.1)
|
||||
|
||||
Where: `ui/gameLogic.js morphPairings / HARMONY_BOND / harmonyMembers`, `ui/detailPanel.js MorphPairings / MorphGrantLine / ItemDetail / ChessDetail / BondChips`, `ui/bondStrip.js BondPopup / BondStrip`, `match/bondsMeta.js computeBonds / bondList`, `shared/bandBonds.js` (→ bands.json `bondIds`), `match/gamedata.js bandBondIds`, `match/bot.js botPickBand`, `screens/bandDraft.js BandOffTag / BandOffNote`, `ui/gameLogic.js bandOffBonds`, `tools/botbench.mjs` (`--band`), `match/fields.js layerAllowanceOf`, `public/dev/game-mock.js` (variants `funny` / `morph` / `harmony`). Three gaps the user decided to close in 0.1.1 (no rule changes), and a false rejection of honest client results found on the way.
|
||||
|
||||
- **变形同构体 shows its pairings** (issue #1: players read the "转职球" as doing nothing). Its text says "具体对应关系可在模拟中查看本装备天赋栏", and no screen showed that 天赋栏. **Now** the item card (shop, reward, hand / temp: `ItemDetail`) has a 天赋 section: "搭配以下装备时,携带者视为对应盟约的成员:", then one line per bond in bonds.json order — 【bond】 and the items whose `giveBondId` is it (one name per item family, by tier): the 14 pairings of the official talent (character_table trap_1073_acarm073, "“炎国短刀”→【炎】盟约 …"; a test checks the two against each other), its "“维式重锤”系列装备" as the 5 hammers — 18 items, built from the same `giveBondId` the server counts with (`morphPairings`; `pieceBonds` / `grantedBonds`). A bond the mode never activates is struck through with 本局禁用 (`modeOffBonds`, §21.7; 标准: 拉特兰 阿戈尔 卡西米尔 奥术 突袭). On a wearer's card (its 装备 rows; also a teammate's unit and the card a 同构 row opens) the pairing it wears is highlighted — 生效中, or 已搭配 when that bond is off — and a wearer without one reads 暂未生效. A bond item's card and row say "与变形同构体一同装备时,携带者视为【X】成员" (`MorphGrantLine`). The 机密商店 cards have no detail view and keep the official text. Phones: the list keeps ≥ 9 px (px floors in `game-panels.css`).
|
||||
- **调和's +1 explained** (issue #1: "一些不考虑整备区的盟约计算人数时也会把整备区的干员算进去" — the +1 read as a miscount; §21.11 had left "调和's +1 raises 在场 for core bonds without a member row"). The rule is unchanged (§6.3; one +1 total, research 02 §2.1 [ASSUMED]). **Now** a bond state whose count holds the +1 carries `harmony: 1` (`computeBonds`) and both views send it — m.private `bonds[]` and m.public `players[].bonds[]` entries `{ …, harmony? }` (absent otherwise; not in the battle input, `bondSnapshot`) — so the client never re-derives the rule. The popup says 在场 n(含调和 +1) and a 调和 row heads its member list, naming the 调和 operators on that board (`harmonyMembers`: 缪尔赛思, Pith's 盟约·辅助干员; a tap opens the first one's card; with that board not on screen "调和已激活:核心盟约激活人数 +1") — for your own strip and a teammate's (§20.15: the entry from m.public, the operators from the field on screen). The strip disc's and the card chip's titles add (含调和 +1).
|
||||
- **Bots skip strategies built around a switched-off bond**: in 标准 the bot took 潘格尼尼 (【定制铳械】 an elite <拉特兰>), 克莱门莎 (<阿戈尔>) or 玛恩纳 (<卡西米尔>) in 5 of 200 solo and 23 of 800 co-op picks (seeds 1–200). bands.json `bondIds` holds a band's tie, read from its own data at build time (`tools/build-data.mjs` through `shared/bandBonds.js`; `GameData.bandBondIds` and the client read the field) — the bond names in <…> of its text (the official note "在<X>部分干员缺席时体验可能不完整" among them; a bracketed item or enemy adds nothing) and the bond ids / bond pools of its blackboards (`bond`, `bond_id`; `pool` → choices.json `pools[pool].bond`, 潘格尼尼's pool_char_later) — nine bands are tied (杜遥夜 炎, 佩佩 / 娜仁图亚 萨尔贡, 哈洛德 维多利亚, 休露丝 谢拉格, 潘格尼尼 拉特兰, 克莱门莎 阿戈尔, 玛恩纳 卡西米尔, 贾维 叙拉古). `botPickBand` weighs a band tied to a bond of `gd.modeInactiveBonds` 0 — one rng draw per pick, the default band when every offered band is excluded: 0 of 200 and 0 of 800 in 标准, while the other modes keep exactly the earlier picks (绝境: 5 / 200, 23 / 800). `tools/botbench.mjs --band` still forces any band.
|
||||
- **The strategy draft marks them 本局禁用** for human players (the owner's follow-up): a band whose `bondIds` meet the mode's inactive bonds (`gameLogic.bandOffBonds`, the same field and rule as the bot — a test pins the two in every mode) carries a 本局禁用 tag on its card and, in the detail pane, "本局禁用【拉特兰】盟约,此策略效果可能无法发挥" with the bond struck through (§21.7's look); it stays selectable — the official game lets you pick it.
|
||||
- **Found on the way — the client-result layer bound** (`match/fields.js layerAllowanceOf / validateClientResult`): with the bots' new picks the full-match suite's coop/FUNNY/107 (4 humans on AI 托管) reached a 6-谢拉格 freeze board at R12 (初雪 + 银灰: garrison_28, 50 % +1 per freeze in range, no per-battle cap in the data; 凛御银灰 hands out a 60 % +2) whose honest +112 谢拉格 layers passed the flat 60 + 4·12 = 108, so the server rejected the client and re-simulated (SP_VERIFY=all agrees with the client on all 57 battles of that match). Uncapped traits are common (菲莱 / 百炼嘉维尔 per skill, 斯卡蒂 per 2 kills, 莫斯提马 per 6 bullets …) and capped ones reach 100–200 (史尔特尔, 荒芜拉普兰德), so the flat bound now adds, per bond, what the player's IN_BATTLE layer 特质 can add to it — each trait of its units and each one their ADD_BOND traits hand out (once per operator), on the bonds it names, up to the cap the sim applies (`max_add_count_per_battle`, the handed-out 华法琳 trait's `GRANTED_CAP_OVERRIDE`); a trait without a cap — or a 魔王, whose +extra counts toward none — leaves its bonds bounded only by the 999 room. A forged gain on any other bond meets the flat bound as before.
|
||||
- Normative lines rewritten with it: §6.3 (`harmony` in the views), §6.6 (the band pick), §8.2 / §8.3 (`bonds[]` entries `harmony?`), §14 / §20.12 (the layer bound), §21.11 (its 调和 note).
|
||||
- Tests: `test/ui/feedback1-gaps.test.js`, `test/match/feedback1-gaps.test.js`; opt-in `test/ui/feedback1-gaps.e2e.test.js` (`SP_E2E=1`; 1920×1080, 844×390, 756×366; `SP_E2E_OUT` moves the screenshots).
|
||||
+16
-5
@@ -212,6 +212,11 @@ The rest of this section is the legacy server-run mode
|
||||
另一组队友的战场情况" → `BAD_TARGET 'other group hidden'`); eliminated / departed players spectate any field.
|
||||
|
||||
### 1.5 AI player (bot.js)
|
||||
**Strategy** (`botPickBand`): weighted by starting LP among the offered bands (alone, 老鲤's withheld first-round funds
|
||||
× 0.02); a band whose mechanic rides on a bond the mode switches off weighs 0 — `GameData.bandBondIds` (bands.json
|
||||
`bondIds`, built by shared/bandBonds.js: the bond names in <…> of its text, the bond ids / bond pools of its blackboards)
|
||||
∩ `modeInactiveBonds`: 标准's 潘格尼尼, 克莱门莎, 玛恩纳 — and with every offered band excluded it takes the default band;
|
||||
one rng draw per pick (DESIGN §21.26). The strategy draft marks the same bands 本局禁用 for humans (still selectable).
|
||||
Buys toward a full board first (the cap is 8 from R1; leftover funds are lost), completes the merges it can afford,
|
||||
then levels on a curve (L2 ≈ R3, L3 ≈ R5, L4 ≈ R7, L5 ≈ R10, L6 ≈ R12 — the competent curve of docs/BALANCE.md; free
|
||||
levels always), then spends the rest (a band that keeps its leftover funds — 坎诺特 利滚利 — holds its interest capital
|
||||
@@ -535,22 +540,27 @@ Any `choice:` handler whose EffectRef reuses its own key must guard like this (o
|
||||
promotion, an owner moved while hand and temp are full) the summon leaves the board and its stack comes back at the
|
||||
next round start (`grantTokensFor`), so no out-of-range placement reaches the battle. The client lights and accepts
|
||||
the same tiles (`ui/gameLogic.js summonRange`), the bot plans inside them. Battle side: SIM.md §1.1 token pieces.
|
||||
* **Bonds**: bondsMeta.js (BOARD distinct, BOARD_AND_DECK, 绝技 elites, 调和 +1, 独行 downward, 助力 upper tiers,
|
||||
变形同构体 grants). Σ activated layers for the hidden core = Σ layers of active bonds at the boss round's prep end.
|
||||
* **Bonds**: bondsMeta.js (BOARD distinct, BOARD_AND_DECK, 绝技 elites, 调和 +1 — the state and the views mark it
|
||||
`harmony: 1`, DESIGN §21.26 —, 独行 downward, 助力 upper tiers, 变形同构体 grants). Σ activated layers for the hidden
|
||||
core = Σ layers of active bonds at the boss round's prep end.
|
||||
**变形同构体** (变形者集群 "与特定装备一同装备时装备者将视为特定盟约成员", 缪尔赛思's 特质 hands it out): its wearer
|
||||
with a bond item (`giveBondId`, the 14 pairings of the item's official talent) is a member of that bond — counted like
|
||||
any member (on the board only: the 14 bonds are BOARD; one member per operator, normal and elite copies alike), and
|
||||
so in the battle input / battle, the views, and on the client: the bond popup lists it among the members (tagged
|
||||
同构, `gameLogic.bondMembers` / `grantedBonds`, the same rule as `pieceBonds`) and its card shows the granted bond
|
||||
chip; a teammate's units carry their item ids for that (UnitInfo `items`, `Match.prepFieldMeta`, SIM.md §9), and a
|
||||
card without an own piece (a teammate's unit, the card a 同构 row opens) lists those items read-only under 装备.
|
||||
card without an own piece (a teammate's unit, the card a 同构 row opens) lists those items read-only under 装备. The
|
||||
item's card lists the 14 pairings of its 天赋栏 from the same `giveBondId` (`gameLogic.morphPairings`, 本局禁用 marked),
|
||||
the wearer's card highlights the pairing it wears, and a bond item's card names the bond it gives (DESIGN §21.26).
|
||||
Not covered [ASSUMED]: operator talents that test "【拉特兰】/【卡西米尔】/… 干员" in the kits read the record's bonds.
|
||||
**Layer cap** (research 11 §1; the client's `MAX_GARRISON_STACK` / `AddBondCount` = min(L + n, 999)): each bond's
|
||||
layers stop at `BOND_LAYER_CAP` = 999 (shared/constants.js; 0 / Infinity = off). Every writer clamps with
|
||||
`layerGainRoom` — `PlayerState.addLayers` (all prep-side gains: 特质, items, bands, 机变 cards, bonds; `ctx.addLayers`),
|
||||
the SETTLE of the in-battle gains and the battle's own live copy (`Battle.addLayers`, SIM.md §6); a gain at the cap
|
||||
adds 0 and dispatches no onLayers. The client-result check bounds a reported gain by the room left from the bond's
|
||||
starting layers (`fields.js validateClientResult`, 'layer bound'); `invariants.js` flags a bond above the cap. The
|
||||
starting layers and by 60 + 4·round plus what the player's IN_BATTLE layer 特质 can add to that bond (their per-battle
|
||||
caps; an uncapped trait leaves only the room — `fields.js layerAllowanceOf`, DESIGN §21.26) (`validateClientResult`,
|
||||
'layer bound'); `invariants.js` flags a bond above the cap. The
|
||||
bond strip, its popup, the effect text and the detail card show the server's capped count. The dev tools' direct
|
||||
writes (`tools/matchrun.mjs --layers N`, `tools/balance.mjs applyBoard`) stop at the cap too.
|
||||
* **Waves**: waves.js header (stage/factions/boss per match, faction replacement per round with `k` copies, scaling by
|
||||
@@ -660,7 +670,8 @@ drawn set; `disabledBonds` = drawn ∪ the mode's static list), `hiddenBossId`,
|
||||
`players[].autoplay`, `players[].uniteLeft` (UNITE, leakers only: their enemies still standing, uncapped — §4),
|
||||
`players[].bonds` = `ps.alive ? bondList(gd, ps.bondsView()) : []` — every bond with members, layers or an active tier, the same list
|
||||
and order as the player's own `m.private bonds` minus `thresholds` / `countsHand` (the client reads those from
|
||||
bonds.json): a teammate watching the player shows it in the bond strip (DESIGN §20.15); `[]` once the player is
|
||||
bonds.json; an entry whose count holds 调和's +1 carries `harmony: 1` in both lists — the bond popup's 调和 row, DESIGN
|
||||
§21.26): a teammate watching the player shows it in the bond strip (DESIGN §20.15); `[]` once the player is
|
||||
eliminated (nobody can watch them; the result screen reads `m.result`'s own bonds), and per phase: `draft { order, turn, picks, skipsLeft, turnDeadline, turnSeconds, untimed }` (BAND_DRAFT),
|
||||
`sp { family, name, desc, eventId, cards:[{ idx, kind:'bounty'|'item'|'tactic', id, name, desc, tier, descRaw?, coin?,
|
||||
payout?, rounds?, enemyKey?, count?, price?, team?, tacticKind? }], order, turn, picks:{pid: idx}, taken:{idx: pid}, untimed }`
|
||||
|
||||
+2
-1
@@ -96,11 +96,12 @@
|
||||
|
||||
- **核心盟约**(8 个势力:炎、萨尔贡、维多利亚、谢拉格、拉特兰、阿戈尔、叙拉古、卡西米尔):3 名激活,6 名获得强力的额外效果。
|
||||
- **附加盟约**(精准、迅捷、灵巧、奥术、坚守、助力、远见、奇迹、投资人、突袭、不屈、调和、协防干员、独行、绝技):多为 2 / 3 名激活;「独行」需要场上**恰好 1 名**,「绝技」按场上的精锐数量计算,没有势力盟约的干员算「协防干员」。
|
||||
- **调和**:激活时,场上已有成员的核心盟约人数各 +1(只加 1 人,不论有几名调和干员)。盟约详情会写「含调和 +1」,成员列表上方列出带来这 1 人的调和干员(如缪尔赛思)。
|
||||
- **炎佑**:场上 6 名以上「炎」干员时召唤炎佑(9 名时两只)。它追踪全场仇恨最高的敌人并悬停在其上方,技能「祛恶之焰」锁定 1 个目标**持续喷火**(最长 20 秒,每秒对目标周围的敌人造成法术伤害并附带灼燃,喷火时不普攻,目标死亡或被沉默时结束);场上暂时没有敌人时停在原地等待。它持有官方的「孤立」:医疗干员不会治疗它,友方的增益、光环和天赋也不会选中它(敌人照常可以攻击它)。
|
||||
- **层数**:盟约通过各种效果叠加层数,层数越高效果越强(例如「炎」攻击力随层数提升、「远见」每 10 层给资金)。**层数整局保留,但只有盟约激活时才生效**,多数情况下也只有激活的盟约能叠层。每个盟约最多 **999 层**(与官方相同,取自官方客户端的常量):到 999 后再获得的层数直接作废,按层数发放的奖励(如「远见」的资金)也随之停止。作战中获得的层数在盟约栏里**实时**显示,作战结束后一直保留到结算(联防期间也不会退回去)。
|
||||
- **加成怎么叠**:盟约、策略、装备、机变卡和特质给的「攻击力 / 防御力 / 生命值 +X%」与技能的「攻击力 +X%」**相加**后再乘到属性上(官方:直接乘算),例如精准 +130%、独行 +60%、一件 +40% 的装备合计 +230%,而不是 ×2.3×1.6×1.4。「伤害提升至 X%」「受到的伤害 +X%」(维多利亚、谢拉格、奥术、脆弱等)这类伤害倍率之间仍然相乘;但同名效果只算一个——同一战场两名玩家都开了「奥术」时,敌人身上只挂一层奥术易伤,取两人中较高的那一个(不再相乘);两名「灵知」的坚冰、两名「莱恩哈特」/「缄默德克萨斯」的法抗削减也一样只算最强的一个。「坚守」的反伤计入被打的那名成员(个人对领袖伤害统计里能看到)。
|
||||
- 每局会随机**禁用**若干盟约(标准模拟较少),它们的干员不会出现在商店;开局的「确认本局信息」会列出。
|
||||
- 两件同名装备会合成一件更强的装备;特定的装备组合可以为干员赋予额外的盟约效果。
|
||||
- 两件同名装备会合成一件更强的装备;特定的装备组合可以为干员赋予额外的盟约效果:「变形同构体」与另一件盟约装备(如维式重锤)一同携带时,携带者视为该装备对应盟约的成员。对应关系见变形同构体详情卡的「天赋」一栏(本局禁用的盟约会标出;装在干员身上时,正在生效的那一组会高亮)。
|
||||
|
||||
## 7. 最终攻势与隐秘核心
|
||||
|
||||
|
||||
Reference in new issue
Block a user