Playtest #6 follow-up (v2.5.1): leader HP review — 直接乘算, one debuff instance, 999 layer cap, 限伤; elite to the board; 假想敌:胄 kit
- co-op leaders died within seconds: leader HP is the official data (40 values pinned); bond / strategy / item / 机变 / 特质 "+X%" bonuses now add up (PRTS 直接乘算) instead of compounding; a same-named enemy debuff keeps one (strongest) instance per target (奥术 across a pair field); leader parts pass damage 1:1; fixed co-op pool (the user's call) - official limits read from the client: each bond stops at 999 layers (MAX_GARRISON_STACK); a leader hit of 300000+ in boss / hidden battles is cancelled (MAX_BATTLE_DAMAGE, 限伤) — shared/constants.js switches, research 11 - a merge that consumes a deployed copy puts the elite on that tile and facing (user-confirmed); gold target highlight, promotion effect - 假想敌:胄 per PRTS: one shell, shells within range, any drone death costs 2 % max HP, 剑/锤 sorties and 瘫痪 - DESIGN §20.10–§20.14; 2842 unit tests, all 28 browser suites green; Chrome = Node on boss fields
This commit is contained in:
1 parent
6464e4b46a
commit
620f88c0d8
81 files changed
+4194
-447
No files matched your search
+97
-14
@@ -101,7 +101,7 @@ public/
|
||||
screens/ title.js, lobby.js, room.js, briefing.js, bandDraft.js, game.js, result.js
|
||||
ui/ hud.js, bondStrip.js, bondPopup.js, shopBar.js, teamPanel.js, detailPanel.js,
|
||||
tooltip.js, choiceOverlay.js, rewardOverlay.js, ticker.js, emotes.js, settings.js, toasts.js
|
||||
render/ app.js, projection.js, tiles.js, units.js, spine.js, fx.js, interp.js, drag.js, pick.js (API §9)
|
||||
render/ app.js, projection.js, tiles.js, units.js, spine.js, fx.js, interp.js, drag.js, pick.js, promote.js (API §9)
|
||||
vendor/ pixi, pixi-spine, preact, hooks, htm (copied from node_modules by tools/vendor.mjs)
|
||||
assets/ downloaded art/audio (git-ignored), see docs/ASSETS.md
|
||||
fonts/ self-hosted fonts
|
||||
@@ -198,7 +198,8 @@ b.on(event, fn, { priority=0, owner } ) / b.off(...) // hook bus (§5.4)
|
||||
killed, total, // enemies attributed to this player's field/half
|
||||
leaked: [ { enemyKey, mods, lpr, sourcePlayerId } ], // for unite aggregation & LP
|
||||
perfect: bool, // no leaks
|
||||
layerGains: { [bondId]: n }, // IN_BATTLE gains (0 if flags.layerGainsEnabled false)
|
||||
layerGains: { [bondId]: n }, // IN_BATTLE gains (0 if flags.layerGainsEnabled false); what was added — capped at
|
||||
// the bond's room under BOND_LAYER_CAP 999 (§6.3, §20.12)
|
||||
coins: n, // bounty coins earned
|
||||
damageDealt: n, bossDamage: n, healingDone: n, deaths: n,
|
||||
unitsEnd: [ { uid, hpPct, sp, alive } ] } },
|
||||
@@ -212,7 +213,7 @@ b.on(event, fn, { priority=0, owner } ) / b.off(...) // hook bus (§5.4)
|
||||
Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `bat` (base attack time, s), `blockCnt`, `rangeGrid` (ops) / `rangeRadius` (enemies, 0 = melee), `moveSpeed`, `massLevel`, `weight`, `lpr` (enemies), `dmgType` ('phys'|'arts'|'heal'|'true'), `motion` ('WALK'|'FLY'), `cost`, `respawnTime`, `spRecovery`.
|
||||
|
||||
**Aggregation** (`units.js`, recomputed lazily when buffs change):
|
||||
- `ATK = (base + Σflat) × (1 + Σpct) × Πmul` — same shape for `maxHp`, `def`; `res = clamp((base + Σflat) × Πmul, 0, 100)`; `aspd = clamp(100 + Σaspd + base-100, 20, 600)` (`constants.js ASPD_MIN` 20: PRTS 数值范围 "ATTACK_SPEED 默认下限 20", §20.3).
|
||||
- `ATK = (base + Σflat) × (1 + Σpct) × Πmul` — same shape for `maxHp`, `def`; the 卫戍 systems' "+X%" ATK / DEF / max HP (盟约, 策略, 装备, 机变 cards, per-layer 特质) are 直接乘算 and go into `Σpct` with the skills' "+X%" (`sim/constants.js DIRECT_BONUS_STACKING 'add'`, content/support `directMods`; PRTS 盟约记录 / 游戏数据基础, §20.10) — "提升至X%" effects (炎佑 ×1.5) and the char_attribute_mul 特质 stay in `Πmul`; `res = clamp((base + Σflat) × Πmul, 0, 100)`; `aspd = clamp(100 + Σaspd + base-100, 20, 600)` (`constants.js ASPD_MIN` 20: PRTS 数值范围 "ATTACK_SPEED 默认下限 20", §20.3).
|
||||
- `interval = bat × (1 + ΣbatPct) × 100 / aspd` (blackboard `base_attack_time` is treated as `batPct`, e.g. −0.3 → 70%).
|
||||
- Changing `maxHp` keeps the HP **ratio**.
|
||||
- Stats are rounded only for display; the sim keeps floats.
|
||||
@@ -227,6 +228,7 @@ Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `ba
|
||||
- `flags`: `stun, freeze, sleep, silence (no skills), disarm (no attacks), stealth (untargetable by enemies/ops unless blocked or revealed), camou (an ally's 迷彩: like stealth for enemy targeting and on screen, but not 隐匿 for 隐匿 conditions — 叙拉古, 家族徽章; §20.2), invulnerable, unblockable, levitate, fear (enemy: cannot attack, unblockable, runs to random tiles of the ±45° fan away from the source; no source or itself as source = inside its own tile — `fear.js`, §20.4), taunt (+aggro), cold, noHeal (no heals: 禁疗 summons and 孤立 units; an HP-regen attribute still applies — PRTS 安洁莉娜 备注), isolated (孤立: no friendly selector picks it — no heal, buff, aura or talent of an ally, `Battle.allySelectable`; §20.3)`.
|
||||
- **Status catalogue** (`buffs.js`, keys used by content): `stun`, `freeze` (stun + res −15), `cold` (aspd −30; a second cold while cold ⇒ freeze 3 s unless immune), `sleep` (stun; ends on damage? no: in AK sleep = untargetable & unable to act; keep AK: untargetable & inactive), `slow` (moveMul), `bind` (moveMul 0), `fragile` (dmgTakenMul), `artsFragile` (artsTakenMul), `silence`, `fear` (the 恐惧 walk, `fear.js`), `burnGauge`/`necrosis`/`neural` element gauges (see damage), `stealth`, `camou` (迷彩, its own keys: 忍冬 S3 `vulpis:camou` until her next cast, 寒芒克洛丝 S1), `reveal`.
|
||||
- Immunities from enemy data (`stunImmune`, `silenceImmune`, `sleepImmune`, `frozenImmune`, `levitateImmune`) are honoured by `applyStatus`.
|
||||
- Same-named debuffs on an enemy (奥术's arts damage taken, 灵知 坚冰, the 莱恩哈特 / 缄默德克萨斯 RES cuts): one instance per target whoever applies it, the strongest wins (`Battle.applyStrongest`; PRTS 作战机制 "同名buff…只能表现出一个" — strongest rather than latest [ASSUMED], §20.10).
|
||||
|
||||
### 5.4 Hook bus (the only way content plugs in)
|
||||
|
||||
@@ -254,7 +256,7 @@ Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `ba
|
||||
|
||||
DamageInfo: `{ amount (pre-mitigation), type: 'phys'|'arts'|'true'|'element', element?: 'burn'|'necrosis'|'neural'|'apoptosis', atkScale, defIgnoreFlat, defIgnorePct, resIgnoreFlat, resIgnorePct, mul (multiplier), canDodge, isSkill, isSplash, tags[] }`.
|
||||
|
||||
Engine helpers (content must use these, never touch internals): `battle.dealDamage(source, target, dmgInfo)`, `battle.heal(source, target, amount, {overheal=false})`, `battle.applyStatus(target, key, { duration, source, value })`, `battle.addBuff(unit, buff)`, `battle.removeBuff(unit, key)`, `battle.spawnToken(ownerUnit|playerId, tokenId, row, col, opts)`, `battle.spawnEnemy(enemyKey, { routeIndex, pos, mods, tag })`, `battle.addProjectile({ from, to|target, speed, onHit, visual })`, `battle.push(e, force, { from, dir, fixed, fixedAngle, inward, effect })` / `battle.pull(e, force, { to, center, stop })` / `battle.pullToFront(e, unit, force)` / `battle.forceLevel(e, force)` / `battle.pushDistance(e, force, { effect })` (the official 力度 − 重量 tables, §20.3; `battle.displace(e, dir, distance)` is the raw mover), `battle.allySelectable(a, by)` / `battle.alliesFor(by, ownerId)` (friendly selection, skipping 孤立 units, §20.3), `battle.unitsInGrid(unit, grid)`, `battle.enemiesInRadius(x, y, r)`, `battle.alliesInRadius(x, y, r, ownerId?)`, `battle.addLayers(playerId, bondId, n, reason)` (no-op when gains disabled; records into result), `battle.addCoins(playerId, n)`, `battle.fx(kind, params)` (client VFX event), `battle.rng()` (never `Math.random`), `battle.getPlayer(playerId)` (bonds/band/effects view), `battle.redeploy(unit)`, `battle.after(seconds, fn)` (scheduled callbacks), `battle.every(seconds, fn)`.
|
||||
Engine helpers (content must use these, never touch internals): `battle.dealDamage(source, target, dmgInfo)`, `battle.heal(source, target, amount, {overheal=false})`, `battle.applyStatus(target, key, { duration, source, value })`, `battle.addBuff(unit, buff)`, `battle.removeBuff(unit, key)`, `battle.spawnToken(ownerUnit|playerId, tokenId, row, col, opts)`, `battle.spawnEnemy(enemyKey, { routeIndex, pos, mods, tag })`, `battle.addProjectile({ from, to|target, speed, onHit, visual })`, `battle.push(e, force, { from, dir, fixed, fixedAngle, inward, effect })` / `battle.pull(e, force, { to, center, stop })` / `battle.pullToFront(e, unit, force)` / `battle.forceLevel(e, force)` / `battle.pushDistance(e, force, { effect })` (the official 力度 − 重量 tables, §20.3; `battle.displace(e, dir, distance)` is the raw mover), `battle.allySelectable(a, by)` / `battle.alliesFor(by, ownerId)` (friendly selection, skipping 孤立 units, §20.3), `battle.unitsInGrid(unit, grid)`, `battle.enemiesInRadius(x, y, r)`, `battle.alliesInRadius(x, y, r, ownerId?)`, `battle.addLayers(playerId, bondId, n, reason)` (no-op when gains disabled; at most the room left under `BOND_LAYER_CAP` 999 — `shared/constants.js layerGainRoom`, §20.12; records into result), `battle.loseHp(target, amount, { source, from, sourceless })` (流失; `sourceless` = 无来源, `source` keeps the credit — leader parts pass damage on 1:1 with it, §20.10), `battle.addCoins(playerId, n)`, `battle.fx(kind, params)` (client VFX event), `battle.rng()` (never `Math.random`), `battle.getPlayer(playerId)` (bonds/band/effects view), `battle.redeploy(unit)`, `battle.after(seconds, fn)` (scheduled callbacks), `battle.every(seconds, fn)`.
|
||||
|
||||
### 5.5 Combat rules (engine)
|
||||
|
||||
@@ -264,7 +266,7 @@ Engine helpers (content must use these, never touch internals): `battle.dealDama
|
||||
- **Operator attacks**: every `interval` s if a valid target exists; targets = up to `maxTargets` (default 1) from `unitsInGrid(rangeGrid)`, priority: (1) the enemies it blocks (every blocker, in range or not, §20.3), (2) profession/trait priorities (e.g. fly-first snipers, `trait` text parsed by build-data into `targetPriority`), (3) enemy with the **least remaining path distance** to its goal, ties → earliest spawned. Melee (no ranged trait) and ground-only attacks cannot hit air units (`Unit.isFlying`: FLY, or a 近地悬浮 / 浮空 enemy — PRTS "算作空中单位", §19.1). Stealthed enemies can't be targeted unless blocked or revealed. Ranged ops fire projectiles (speed ~12 tiles/s, instant for melee/arts-beam classes as data indicates); damage applies on impact.
|
||||
- **Healers** (`dmgType heal`): target the ally in range with the lowest HP% below 100%, never a 禁疗 / 孤立 unit (tokens.json `abnormal`, `noHeal`; §20.3); do nothing otherwise (SP still regenerates).
|
||||
- **Enemy AI**: follow the route (checkpoints; WALK paths recomputed on the grid when devices change); when blocked stop & fight; ranged enemies (`rangeRadius > 0`) attack ops whose 0.25 collider touches their radius (centre distance ≤ `rangeRadius` + 0.25, §3, §20.4): the enemy's own target rule filters the candidates first (`profile.canTarget`: 只攻击地面单位 …), then priority blocker → special priority → highest taunt → **latest deployed** (`aggroSeq`) — PRTS 索敌 "阻挡→特殊优先级→仇恨值→最早出现"; a stealthed ally is attacked only by the enemy it blocks (§19.1); they pause movement for `ATTACK_PAUSE = 0.35 s` per attack. FLY enemies ignore ground obstacles (only blockFly units block them) and follow their checkpoint route. A feared enemy runs between random 恐惧可达地块 away from the source (PRTS 诱发移动; `fear.js`); 恐惧 outranks 诱导; the 恐惧 / 诱导 walks re-plan from where the enemy stands after a push or an obstacle change, and the route re-plans when they end (§20.4).
|
||||
- **Damage**: phys `max(A − max(0, D×(1−defIgnorePct) − defIgnoreFlat), 0.05·A)`; arts `max(A × (1 − R'/100), 0.05·A)` with `R' = max(0, R×(1−resIgnorePct) − resIgnoreFlat)`; true = A; 元素伤害 ('elemental') `max(A × (1 − 元素抗性/100), 0.05·A)`. Then × `dmgDealtMul` (source) × `dmgTakenMul` & type-specific taken multipliers (target) × `dmg.mul` — 元素伤害 takes 元素脆弱 (`elementalTakenMul`) alone, not `dmgTakenMul` (脆弱 = "受到的物理、法术、真实伤害提升"); shields absorb; dodge checks for phys/arts. Element damage fills a gauge (1000; enemy leaders 2000) × `elemTakenMul` × max(0.05, 1 − 损伤抵抗/100), 损伤抵抗 = data `epResistance` (PRTS 元素; the 5 % floor of DMG_e, PRTS 游戏数据基础). An element fill on a target with no HP left is refused (damage.js `hasHp`: no burst on a corpse). A full gauge bursts with the side-specific official effects, as **无来源** damage (no damage-dealt multiplier or penetration of the unit that filled the gauge, which keeps the kill and the stats; §19.5) (sim/constants.js `ELEMENT`; operators: 灼燃 1200 arts + RES −20, 神经 stun 10 s then 1000 true, 凋亡 15 s of 阻回 (`noSp`: no SP gain of any kind) + 静默 with −1 SP/s and 100 arts/s, 侵蚀 DEF −100 then 800 phys; enemies: the "·我方" terms). Every burst starts a **爆发冷却** = its duration (灼燃 / 神经 / 侵蚀 10 s — enemies' 侵蚀 8 s — 凋亡 15 s) during which NO element of the unit fills or can be recovered; when it ends every gauge of the unit resets (§18.3).
|
||||
- **Damage**: phys `max(A − max(0, D×(1−defIgnorePct) − defIgnoreFlat), 0.05·A)`; arts `max(A × (1 − R'/100), 0.05·A)` with `R' = max(0, R×(1−resIgnorePct) − resIgnoreFlat)`; true = A; 元素伤害 ('elemental') `max(A × (1 − 元素抗性/100), 0.05·A)`. Then × `dmgDealtMul` (source) × `dmgTakenMul` & type-specific taken multipliers (target) × `dmg.mul` — 元素伤害 takes 元素脆弱 (`elementalTakenMul`) alone, not `dmgTakenMul` (脆弱 = "受到的物理、法术、真实伤害提升"). Then **限伤**: on a leader (`isBoss` — the 10 bossInfoDict enemyIds and their mirrored copies; never parts, drones or escorts) in a `'boss'` / `'hidden'` battle a hit with `ceil(final) ≥ 300000` is cancelled — no HP or pool loss, no credit, no damage number, no `damaged` / kill (`shared/constants.js BOSS_HIT_LIMIT`, `damage.js leaderHitCancelled`, after every multiplier and the hit-step blocks, before HP shields; `loseHp` too — a part's 传递, a drone's death; the client's `MAX_BATTLE_DAMAGE`, §20.12). Then shields absorb; dodge checks for phys/arts. Element damage fills a gauge (1000; enemy leaders 2000) × `elemTakenMul` × max(0.05, 1 − 损伤抵抗/100), 损伤抵抗 = data `epResistance` (PRTS 元素; the 5 % floor of DMG_e, PRTS 游戏数据基础). An element fill on a target with no HP left is refused (damage.js `hasHp`: no burst on a corpse). A full gauge bursts with the side-specific official effects, as **无来源** damage (no damage-dealt multiplier or penetration of the unit that filled the gauge, which keeps the kill and the stats; §19.5) (sim/constants.js `ELEMENT`; operators: 灼燃 1200 arts + RES −20, 神经 stun 10 s then 1000 true, 凋亡 15 s of 阻回 (`noSp`: no SP gain of any kind) + 静默 with −1 SP/s and 100 arts/s, 侵蚀 DEF −100 then 800 phys; enemies: the "·我方" terms). Every burst starts a **爆发冷却** = its duration (灼燃 / 神经 / 侵蚀 10 s — enemies' 侵蚀 8 s — 凋亡 15 s) during which NO element of the unit fills or can be recovered; when it ends every gauge of the unit resets (§18.3).
|
||||
- **Leak**: an enemy reaching its goal is removed, counted as leaked for its field owner (`sourcePlayerId` for unite). LP accounting is done by the match, not the battle.
|
||||
- **End**: all spawns done and no enemies alive ⇒ `cleared`; `time ≥ timeLimit` ⇒ `timeout` (remaining enemies = leaked). Boss battles end when the shared boss pool reaches 0 (victory) or the match forces an end (team LP 0). A pool holding less than 1 HP is empty (`constants.js BOSS_POOL_MIN_HP`: the hit, or the per-player credit, that would leave less takes the rest; §20.5).
|
||||
|
||||
@@ -352,15 +354,16 @@ Summons (§20.1): an operator on the board sends its manually deployable summons
|
||||
|
||||
Equipment (research 04 §2 + addendum, 09 §1.2): 2 slots per chess; a third item on a full carrier replaces the equipped item `replaceUid` names (the replace dialog's pick, official `UseEquipUp.unloadInstId`; absent ⇒ the oldest; a uid not equipped on the target ⇒ `BAD_TARGET`, nothing changes) and the replaced item is destroyed. Equipped items are otherwise **locked**: `destroy` (销毁) accepts hand / temp items only (`BAD_TARGET 'equipped items are locked'`); equipment goes back to the hand only when its carrier merges or is sold (a sale is refused with `HAND_FULL` when there is no room for it).
|
||||
|
||||
Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (income `min(3+r,12)`; prices by tier; refresh 1; freeze toggles all; sell +1; items not sellable; upgrade prices per mode with −1/round; shared pool copies 12/14/18/16/8/5 (缪尔赛思 4) across all players; elite = 3 copies; odds = copy-weighted draw over unbanned visible chess with tier ≤ level; merge 3 (风丸 2) → elite to hand + reward offer of 3 **different** free chess of tier `min(level+1, 6)` (copy-weighted; a tier short of different chess tops up from the tier below [ASSUMED], §20.6); purchase completing a merge allowed with full hand; full hand blocks buy; overflow to temp; temp blocks ready).
|
||||
Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (income `min(3+r,12)`; prices by tier; refresh 1; freeze toggles all; sell +1; items not sellable; upgrade prices per mode with −1/round; shared pool copies 12/14/18/16/8/5 (缪尔赛思 4) across all players; elite = 3 copies; odds = copy-weighted draw over unbanned visible chess with tier ≤ level; merge 3 (风丸 2) → elite onto the consumed deployed copy's tile, else to the hand (§20.11) + reward offer of 3 **different** free chess of tier `min(level+1, 6)` (copy-weighted; a tier short of different chess tops up from the tier below [ASSUMED], §20.6); purchase completing a merge allowed with full hand; full hand blocks buy; overflow to temp; temp blocks ready).
|
||||
|
||||
**Temp overflow (临时整备区, row 8; user playtest #3 item 3).** Passive gains that find the hand full (merge elites, grants, battle-result rewards, equipment returned by a merged / sold carrier) overflow into the 5 temp slots, and Ready is refused while any is occupied (official "直到溢出情况排除才可开始进行作战"). A temp piece is resolved — a chess sold back to the pool with its summons removed, items destroyed, a summon stack removed (it comes back at the next round start, PRTS "干员所属召唤物会于下一回合返还", §20.1) — only at the deadline of the **first prep in which its player could act on it** (`PlayerState.tempDue(piece)` vs `prepsEnded`; every write into a temp slot goes through `_putTemp`, which records it): a piece that arrived during a prep before Ready is due at that prep's end; one that arrived after Ready or during the `<休整期结束时>` effects, or outside PREP (COMBAT, 联防, SETTLE — battle-result grants, a SETTLE merge's elite, returned equipment —, the next ROUND_START, 机变) is kept, visible and usable (move to a free hand slot, place, equip, use, destroy, sell) through the **next** prep. Un-readying (`setReady(false)`) makes whatever arrived while ready due at the current prep. ROUND_START no longer wipes temp (it used to destroy what SETTLE had put there before the player ever saw it). A merge's elite always goes to the hand, overflowing into temp (also outside PREP); it takes a freed board tile of a consumed copy only when the hand and temp are both full. `server/match/audit.js` (`tools/matchrun.mjs --check`) tracks the arrivals itself and flags any temp piece that outlives its due prep. The UI frames the row on the board with the rule (§17.5).
|
||||
**Temp overflow (临时整备区, row 8; user playtest #3 item 3).** Passive gains that find the hand full (merge elites, grants, battle-result rewards, equipment returned by a merged / sold carrier) overflow into the 5 temp slots, and Ready is refused while any is occupied (official "直到溢出情况排除才可开始进行作战"). A temp piece is resolved — a chess sold back to the pool with its summons removed, items destroyed, a summon stack removed (it comes back at the next round start, PRTS "干员所属召唤物会于下一回合返还", §20.1) — only at the deadline of the **first prep in which its player could act on it** (`PlayerState.tempDue(piece)` vs `prepsEnded`; every write into a temp slot goes through `_putTemp`, which records it): a piece that arrived during a prep before Ready is due at that prep's end; one that arrived after Ready or during the `<休整期结束时>` effects, or outside PREP (COMBAT, 联防, SETTLE — battle-result grants, a SETTLE merge's elite, returned equipment —, the next ROUND_START, 机变) is kept, visible and usable (move to a free hand slot, place, equip, use, destroy, sell) through the **next** prep. Un-readying (`setReady(false)`) makes whatever arrived while ready due at the current prep. ROUND_START no longer wipes temp (it used to destroy what SETTLE had put there before the player ever saw it). A merge's elite whose merge consumed a deployed copy takes that copy's tile and facing (§20.11); otherwise it goes to the hand, overflowing into temp (also outside PREP). `server/match/audit.js` (`tools/matchrun.mjs --check`) tracks the arrivals itself and flags any temp piece that outlives its due prep. The UI frames the row on the board with the rule (§17.5).
|
||||
|
||||
### 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).
|
||||
- `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).
|
||||
- A bond holds at most `BOND_LAYER_CAP` = 999 layers (the client's `MAX_GARRISON_STACK`, §20.12): every writer — `PlayerState.addLayers`, the SETTLE merge, `Battle.addLayers`, the dev tools — adds `layerGainRoom(before, n)` = min(n, 999 − before); a gain at the cap adds 0 and dispatches nothing (no onLayers, no `layerGain` hook, no 'layer' event), so the per-N milestones stop with the count.
|
||||
- Σ activated layers (hidden-core check) = Σ layers over bonds that are active at the moment of the check (end of R14 prep, per research).
|
||||
|
||||
### 6.4 Prep-phase effect dispatcher (effectsMeta.js)
|
||||
@@ -386,7 +389,7 @@ Each domain file `server/sim/content/{tokens,bonds,garrisons,items,bands,enemies
|
||||
- garrisons: 43 effect keys (IN_BATTLE via hooks; SERVER_* via meta registry).
|
||||
- items: 56 equipment (+golden) + 3 Arts; 变形同构体 bond grant; VI-tier set/combos.
|
||||
- bands: 40 strategies (meta + battle).
|
||||
- enemies: special-type behaviours (FLY, TIMES, ELEMENT, DOT, INVISIBLE, REFLECTION, SPECIAL) and notable individual abilities from `data/enemies.json`; bosses 1–10 scripted — the huge leaders (假想敌:胄 ×2, 假想敌:管 ×2, 盐风主教昆图斯, 阿利斯泰尔, “萨米的意志”) are 自缚 + 无法被阻挡 (PRTS 天赋; `content/bosses.js SELF_BOUND`: a persistent noMove + unblockable buff from spawn) with their hit area (§19.4); devices/terrain.
|
||||
- enemies: special-type behaviours (FLY, TIMES, ELEMENT, DOT, INVISIBLE, REFLECTION, SPECIAL) and notable individual abilities from `data/enemies.json`; bosses 1–10 scripted — the huge leaders (假想敌:胄 ×2, 假想敌:管 ×2, 盐风主教昆图斯, 阿利斯泰尔, “萨米的意志”) are 自缚 + 无法被阻挡 (PRTS 天赋; `content/bosses.js SELF_BOUND`: a persistent noMove + unblockable buff from spawn) with their hit area (§19.4); leader parts (斩胄之剑 / 破胄之锤 / 碎铳之簧) pass every damage they take to their leader 1:1 as 无来源 HP loss (`PART_TRANSFER` 1, PRTS "等量"), a 假想敌:胄 drone that dies costs it 2 % of the pool (`DRONE_LINK_BASE 'pool'`) (§20.10, §20.13); devices/terrain.
|
||||
- choices: 机变 families (悬赏决策 bounties, 道具补给, 机密商店, 战术决策 team/personal buffs). 悬赏决策 = cards.bounty `draft` (the PRTS 敌人轮选 table: no 战术特训, no 鸭爵 set; 105 cards), drawn uniformly; bounty cards carry `descRaw`; a multi-round card lasts two battles (`choices.js MULTI_ROUND_BOUNTY_BATTLES`, §20.6). 教鞭 = a random 战术特训 card (3 drawn, 1 taken).
|
||||
|
||||
---
|
||||
@@ -409,7 +412,7 @@ S→C:
|
||||
- `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).
|
||||
- `b.snap` — `{ fieldId, gt, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]] , dp, killed, total, down?, elem? }` 20 Hz. Optional, only when non-empty (§18.3): `down: [[id, respawnAt (game s), respawnTime (s), state]]` — knocked-out operators waiting to redeploy, `state` 0 counting | 1 timer done, waiting for DP | 2 timer done, its tile taken (sim/constants.js `DOWN_STATE`); `elem: [[id, element, fill, cooldownEnd (game s), cooldown (s)]]` — the one gauge a unit shows: the fullest (ties by the official element id 神经 < 侵蚀 < 灼燃 < 凋亡), fill with 2 decimals rounded DOWN and kept at 0.01–0.99 outside a burst (§20.8); during a 爆发冷却 the bursting element with fill 1 and the cooldown numbers (else 0, 0)
|
||||
- `b.ev` — `{ fieldId, ev: [ ... ] }` event tuples: `['spawn', UnitInfo]`, `['atk', srcId, tgtId, projKind]`, `['dmg', tgtId, amount, type]`, `['heal', tgtId, amount]`, `['skill', id, on:0|1]`, `['die', id, reason]` (reason `'killed'`, `'retreat'`, `'expired'` …; only `'killed'` plays an operator's knock-down sound), `['status', id, key, on]`, `['fx', kind, x, y, extra]`, `['layer', playerId, bondId, n]`, `['bounty', playerId, coins]`, `['deploy', id]`
|
||||
- `b.ev` — `{ fieldId, ev: [ ... ] }` event tuples: `['spawn', UnitInfo]`, `['atk', srcId, tgtId, projKind]`, `['dmg', tgtId, amount, type]`, `['heal', tgtId, amount]`, `['skill', id, on:0|1]`, `['die', id, reason]` (reason `'killed'`, `'retreat'`, `'expired'` …; only `'killed'` plays an operator's knock-down sound), `['status', id, key, on]`, `['fx', kind, x, y, extra]` (`'hitCap' {id, n}` = a cancelled 限伤 hit, drawn as nothing, §20.12), `['layer', playerId, bondId, n]`, `['bounty', playerId, coins]`, `['deploy', id]`
|
||||
- `m.toast {kind, text}` · `m.ticker {text}` · `m.emote {playerId, id}` · `m.result {...}`
|
||||
- `m.unitStats {seq, round, units: [unitStatsEntry]}` — the answer to `g.unitStats` (`seq` echoed): every own board operator / summon by uid with `{id, uid, defId, hp, alive, maxHp, atk, def, res, interval, blockCnt, moveSpeed, base: {…same}}` (shared/protocol.js `unitStatsEntry`) as its next battle starts (§18.5)
|
||||
|
||||
@@ -448,7 +451,9 @@ Piece = { uid, kind: 'chess'|'item'|'token', id, golden: bool, tier, items: [ {
|
||||
const view = await createFieldView(canvasHost /* HTMLElement */, { data, assets, audio })
|
||||
view.setStage(stage) // draw tiles (procedural), devices
|
||||
view.setCamera(kind /* 'prep'|'normal'|'unite'|'boss' */, { rect, side }) // animates camera
|
||||
view.setPrep(privateState, { editable }) // render hand/temp/board pieces as chibis (spine or fallback); editable enables drag
|
||||
view.setPrep(privateState, { editable }) // render hand/temp/board pieces as chibis (spine or fallback); editable enables drag;
|
||||
// a merge's new elite on the board plays fx.promote instead of fx.deploy (render/promote.js, §20.11),
|
||||
// also after a battle (a merge between two preps: the last prep state is kept, §20.14)
|
||||
view.enterBattle(fieldMeta) // switch to battle rendering for m.field
|
||||
view.pushSnapshot(snap) ; view.pushEvents(ev) // interpolated rendering (100 ms buffer)
|
||||
view.highlightTiles(tiles, style) // legal/illegal placement, range preview
|
||||
@@ -534,9 +539,9 @@ 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 ≤ 60 + 4·round`, 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), 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 as before) 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`: any other result — `'forced'` / `'timeout'` at t = 0, `'cleared'` while the pool still holds — 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.
|
||||
**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 as before) 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.
|
||||
|
||||
**Solo pause (official `PauseUp` / `ResumeUp`; `Match.setPause`).** C→S `g.pause { on }`: solo matches only (co-op → `WRONG_PHASE 'co-op battles never pause'`), and `on: true` only while a battle runs (COMBAT / FINAL_ASSAULT / HIDDEN_CORE with a live field, not once the Final Assault is ending — else `WRONG_PHASE 'no battle running'`); `on: false` is always accepted. While `m.public.paused` is true everything of the battle is frozen: the field clock (`b.start elapsed`, the boss plausibility budgets and the overtime drain), the authority deadline and the server-run release timers, the boss clock, the HUD `deadline` / `overtimeAt` (shifted by the paused time on resume) and the server-run pacers (FieldRunner / HeadlessPacer skip their intervals); the browser's runner stops its local clock. In solo battles the top bar shows a 暂停 / 继续作战 button (`ui/hud.js PauseButton`, `ui/matchStatus.js pauseAvailable`; `Space` toggles it), a paused overlay offers 继续作战 / 放弃模拟, and the HUD clocks freeze at the pause moment. A disconnect or leave resumes (the server takes the field over — nobody is left to resume it); the battle phase ending drops the pause.
|
||||
|
||||
@@ -808,12 +813,15 @@ Ten reports after playing v2.3 (绝境 on desktop, 标准 on an Android phone in
|
||||
|
||||
---
|
||||
|
||||
## 20. User playtest #6 (v2.5): summons, skill triggers, blocking, push force, enemies, boss pool, bounties, 机变 cards, element gauge
|
||||
## 20. User playtest #6 (v2.5): summons, skill triggers, blocking, push force, enemies, boss pool, bounties, 机变 cards, element gauge; leader HP, elite to the board, official limits
|
||||
|
||||
Nineteen reports after playing v2.4 (#1–#3 from a friend of the user). Where each is handled: #1 凯瑟琳's device could not be placed by hand → §20.1; #2 赫默's drone could not be placed by hand → §20.1; #3 忍冬's 叙拉古 stealth never ended → §20.2; #4 the bounty enemies kept coming instead of stopping after two battles → §20.6; #5 the Final Assault leader at 0 HP kept fighting until the team LP ran out, and the result still said 胜利 → §20.5; #6 the 道具补给 cards showed no description → §20.7; #7 in 联防 the leaker's count should fall live as teammates kill (it seemed capped at 10) → §20.6; #8 新约能天使 S2's shield on teammates seemed not to work → §20.2; #9 威龙 was drawn far too large → §20.4; #10 on the phone the shop's confirm strip did not buy (only the middle of the card worked) → §20.7; #11 the element gauge seemed to fill at the wrong rate → §20.8; #12 萨卡兹枯朽战车 hit too softly → §20.4; #13 “萨科塔之翼”'s 战栗 (small random flight) was missing → §20.4; #14 enemy weight vs push force (野鬃 pushed too many enemies) → §20.3; #15 skills whose range is wider than the attack range did not fire when an enemy entered it → §20.2; #16 古米's ally heal fired only after she engaged an enemy → §20.2; #17 the front operator that took over an enemy held between two operators never attacked it → §20.3; #18 炎佑 was healed by medics → §20.3; #19 the 3-choose-1 promotion reward often offered two identical operators → §20.6.
|
||||
|
||||
Eight workstreams were merged into `playtest6` (WH → WG → WD → WC → WB → WA → WF → WE); the reviews' last rounds and the cross-workstream notes were applied on top: a stocked skill summon deploys as soon as its owner is back (§20.1), the AUTO DP skills 焰尾 S1 / 凛御银灰 S1 fire on full SP (§20.2), 锏 S3's slashes give 无敌 and 晕眩 / 冻结免疫 (§20.3), the `AUTO_OP_COOLDOWN` comment and the SKILL_RANGE count (13 skills / 26 records) were corrected. The user then answered the open questions (2026-10-01); the answers are **settled decisions**, recorded in their sections and in §20.9, each with the one-line change that flips it: the placed skill summons deploy once at the battle start and 凯瑟琳's device stays a hand card (§20.1), every blocker hits what it blocks (§20.3), 萨卡兹枯朽战车 follows the re-research — ranged enemies reach an operator by its 0.25 collider (§20.4) — and multi-round bounties last two battles (§20.6). Normative lines rewritten with it: §2 (`fear.js`), §3 (the enemies' reach), §5.2 (ASPD floor 20), §5.3 (`camou`, `fear`, `noHeal`, `isolated`), §5.4 (push / pull / ally-selection helpers), §5.5 (initial deployment, blocking, operator attacks, healers, enemy AI, end), §5.6 (skill triggers, the operation cooldown), §6.1 / §6.2 (the Final Assault verdict, summon stacks, summons in the hand, the promotion reward), §7 (bounty draft, 教鞭), §8.2 / §8.3 (`uniteLeft`, `descRaw`, `counterText`, the `elem` fill, the stealth bit), §9 (gauge row, model scale), §10 (机变 card, the card tap), §14 (`left`, the pool floor, the verdict, `uniteLeft`); superseded or revised: §17.5 (the 联防 count), §18.3 (the gauge look), §18.6 #11 (skill summons), §19.2 (the melee-only blocked-first rule; 灰毫 S2), §19.5 (the gauge look).
|
||||
|
||||
|
||||
**Follow-up (`playtest6b`, 2026-10-02).** Four more workstreams were merged into `playtest6b` in this order: the elite-to-board rule (§20.11), the 假想敌:胄 kit audit (§20.13), the Final Assault leader HP report (§20.10) and the official limits read from the client binary (§20.12). Reports: co-op 终极 / 绝境 leaders died within seconds although the official leader never dies that fast with such layers (§20.10); "官方就是合成精锐时,如果消耗了场上的干员,精锐会出现在场上那个位置" (§20.11, user-confirmed). The boss-HP and the limits workstreams both capped bond layers at 999 — one implementation remains (`shared/constants.js BOND_LAYER_CAP` / `layerGainRoom`, used by every writer), and the boss-HP workstream's reading of 限伤 as an engine fixed-point overflow "not modelled" is superseded by the binary evidence (`MAX_BATTLE_DAMAGE` 300000, the hit is cancelled). Normative lines rewritten with it: §2 (`promote.js`), §5.1 (`layerGains` capped), §5.2 (直接乘算 in `Σpct`), §5.3 (same-named debuffs), §5.4 (`addLayers`, `loseHp`), §5.5 (限伤 between the multipliers and the shields), §6.2 (where a merge's elite goes), §6.3 (the layer cap), §7 (leader parts, drones), §8.2 (`hitCap`), §9 (the promotion cue), §14 (the layer bound of client results); §20.9 records the decisions. Two QA verifiers then checked the integrated build; their residual issues are handled in §20.14.
|
||||
|
||||
### 20.1 Summons placed by hand (#1, #2) — `tools/build-data.mjs`, `match/gamedata.js placeableTokens`, `PlayerState`, `match/bot.js`, `sim/Battle.js`, `content/tokens.js`, kits tier2/3/4, `ui/detailPanel.js`, `shared/constants.js SKILL_SUMMON_START_DEPLOY`
|
||||
|
||||
- **Seen** (the friend): 凯瑟琳's device and 赫默's drone could not be placed by hand. **Cause**: v2.3 (§18.6 #11) limited hand pieces to talent summons after the user's playtest #4 report on the drone, which dropped 医疗探机 and 诅咒娃娃; 爬行号·防护单元 has no shop-state entry, so 凯瑟琳's kit placed it automatically.
|
||||
@@ -930,5 +938,80 @@ The user's answers of 2026-10-01 (playtest #6 follow-up):
|
||||
| Multi-round bounty cards (§20.6) | last two battles like "接下来两场作战", blue text, in the draft | `server/match/choices.js MULTI_ROUND_BOUNTY_BATTLES = null` (official 每场) |
|
||||
| 萨卡兹枯朽战车's reach (§20.4) | data range 2.2 + the operator's 0.25 collider for every ranged enemy; scales per data (100 % ranged, 200 % blocked melee, skill 100 %) | `server/sim/constants.js ALLY_COLLIDER_RADIUS = 0` |
|
||||
| Blocking (§20.3) | every blocker hits the enemies it blocks, first, whatever its range or facing | gate `Battle.blockedTargets` / `sortEnemyTargets` blocked-first on a melee position again |
|
||||
| A merge's elite (§20.11) | takes the consumed deployed copy's tile and facing, else the hand (PRTS 帮助 "若消耗已部署至作战区的干员,则发送至作战区对应位置"; the user confirmed it, 2026-10-01) | none — official; restoring the v2.5 hand-first rule means reverting `PlayerState._mergeChess` (merge `5f3842f`) |
|
||||
|
||||
Still [ASSUMED] (each in its section): the class rows covering every MANUAL skill (§20.2); skill-summon redeploys free and never while the owner is away (§20.1); the 1-HP pool floor and the server-order verdict (§20.5); a short promotion tier topping up from below and “神秘顾客” keeping the band bounties (§20.6); the 弹道 column for unclassified pushes and the other displacement details (§20.3); 枯朽战车's skill firing on its blocker at 100 % and content enemy skills keeping a point radius (§20.4); the gauge's refill colour and placement (§20.8); the 机变 card's phone sizes (§20.7).
|
||||
Official since the follow-up — read from the client binary (research 11, §20.12) or the official texts (§20.10, §20.13):
|
||||
|
||||
| Rule | Now | One-line flip |
|
||||
|---|---|---|
|
||||
| Bond layer cap (§20.12) | 999 per bond (`MAX_GARRISON_STACK`; `AddBondCount` stores min(L + n, 999)) — no longer [ASSUMED] | `shared/constants.js BOND_LAYER_CAP = 0` |
|
||||
| 限伤 (§20.12) | a leader hit with ceil(d) ≥ 300000 in boss / hidden battles is cancelled (`MAX_BATTLE_DAMAGE`), not an overflow | `shared/constants.js BOSS_HIT_LIMIT = 0` |
|
||||
| 直接乘算 (§20.10) | bond / strategy / item / 机变 / 特质 "+X%" are summed with each other and the skills' | `server/sim/constants.js DIRECT_BONUS_STACKING = 'multiply'` |
|
||||
| Leader parts (§20.10, §20.13) | every damage a 剑 / 锤 / 碎铳之簧 takes is lost by its leader 1:1, 无来源 | `server/sim/content/bosses.js PART_TRANSFER = 0.5` (v2.5's share; v2.5 also passed a 剑 / 锤's damage only while grounded) |
|
||||
|
||||
Settled by the user ("保持固定血量"): the co-op leader pool does not shrink when players are eliminated — `config bossHpScale.aliveScaling` is **false** (pool = bloodPoint whatever the number of alive players; one community note, 巴哈姆特 12294, said otherwise) (§20.10); flip: `aliveScaling: true` (× alive / 4).
|
||||
|
||||
Still [ASSUMED] (each in its section): the class rows covering every MANUAL skill (§20.2); skill-summon redeploys free and never while the owner is away (§20.1); the 1-HP pool floor and the server-order verdict (§20.5); a short promotion tier topping up from below and “神秘顾客” keeping the band bounties (§20.6); the 弹道 column for unclassified pushes and the other displacement details (§20.3); 枯朽战车's skill firing on its blocker at 100 % and content enemy skills keeping a point radius (§20.4); the gauge's refill colour and placement (§20.8); the 机变 card's phone sizes (§20.7); the elite's tile among several deployed copies (the first in deploy order), a transformed carrier's tile and the elite's fresh summon stack (§20.11); "strongest" rather than "latest" for same-named debuffs, which "最大生命值" the 胄 drone link reads (the pool), the 剑 / 锤 dive hits passing on, the springs' split and the solo pool × 0.25 (§20.10); the scene server clamping prep gains like the client, a cancelled hit showing nothing, a passed-on loss being one hit and the order blocks → 限伤 → HP shields (§20.12); 胄's unread `max_hp` 0.5, the untargetable 初始模式 and a replacement copy inheriting only the HP (§20.13); talent debuffs keyed per source unit (two copies of one operator each apply theirs, §20.14).
|
||||
|
||||
### 20.10 Final Assault leader HP and damage (user report after playtest #6, 2026-10-01) — `server/sim/constants.js DIRECT_BONUS_STACKING`, `content/support directMods`, `sim/Battle.js applyStrongest / loseHp sourceless`, content bonds / bands / items / choices / garrisons / kits tier4, `content/bosses.js PART_TRANSFER / DRONE_LINK_BASE`, `match/gamedata.js bossPoolHp / bossPoolShare`, `match/finalAssault.js`, `data/config.json bossHpScale`
|
||||
|
||||
- **Seen**: co-op 终极 / 绝境 leaders died within seconds; with the user's layer counts the official leader never dies that fast.
|
||||
- **HP and pool — official, unchanged**: `bloodPoint[difficulty]` = activity_table act2autochess bossInfoDict `bloodPoint` / `bloodPointNormal` / `bloodPointHard` / `bloodPointAbyss` of the current data (the cache equals upstream ArknightsGameData master as of 2026-10-01; `test/match/bosshp.test.js` pins all 40 values — e.g. 胄 247 500 / 675 000 / 1 800 000 / 3 600 000, hidden 胄 750 000 / 937 500 / 3 600 000 / 7 200 000). PRTS 盟约记录's leader table is an older (11月18日) revision: 铳 险境 and 胄 / 铳 / 萨米 绝境 differ, no 终极 column, no 卢西恩 row. The data has no per-player or per-field factor; leader HP ignores `enemyScale` ("领袖单位于服务器的生命值加成不受上述加成影响"). One pool for every boss field ("最终攻势中,所有人将一起对敌方领袖造成伤害"); notice 5114's "敌方领袖的总生命值不变" means the two mirrored copies of a pair field share it.
|
||||
- **Pool size**: co-op = bloodPoint whatever the number of alive players — `bossHpScale.aliveScaling` (× alive / 4, one community note: 巴哈姆特 12294) stays **off** — the user chose the fixed pool (§20.9); solo × 0.25 [ASSUMED].
|
||||
- **Fixed — why leaders fell too fast**:
|
||||
1. **直接乘算**: "+X%" ATK / DEF / max HP from bonds, strategies, equipment, 机变 cards and per-layer 特质 are summed with each other and with the skills' "+X%" (PRTS 盟约记录 "…属性加成均为直接乘算"; 游戏数据基础 D_t = Σtᵢ, A = (A₀ + D_p)(1 + D_t)). v2.5 compounded them (×74 instead of ×12 at 600 精准 layers with four more bonuses). `DIRECT_BONUS_STACKING 'add'`, content/support `directMods`; 炎佑 ×1.5 and the char_attribute_mul 特质 stay multipliers (§5.2).
|
||||
2. **Same-named enemy debuffs**: one instance per target, the strongest (PRTS 作战机制 "同名buff…只能表现出一个"; 巴哈姆特 12316 "共享型buff會跟對面搶") — 奥术, 灵知 坚冰, the 莱恩哈特 / 缄默德克萨斯 RES cuts (`Battle.applyStrongest`, §5.3). v2.5 kept one 奥术 per player, so a pair field multiplied it (×40 arts damage taken at +400 layers; ×7.6 now). "Strongest" rather than "latest" is [ASSUMED].
|
||||
3. **Leader parts**: 剑 / 锤 / 碎铳之簧 pass every damage they take 1:1 as 无来源 HP loss, credited to the attacker (PRTS "受到等量的无来源生命流失"; `PART_TRANSFER` 1, `loseHp … sourceless`); v2.5 passed ½, a 剑 / 锤 only while grounded. The dive hits counting (PRTS talent "受到伤害时" vs the handbook "被击落时") and the springs' split to the other springs are [ASSUMED] (§20.13 for the 剑 / 锤 kit).
|
||||
4. **坚守 thorns**: dealt 无来源 by the member who was hit and credited to that member (statistics only; up to 19 % of a co-op pool was nobody's).
|
||||
5. **Bond layers**: uncapped in v2.5; now 999 per bond — official, §20.12 (this workstream first added the cap as [ASSUMED] from the community; the merge kept the limits workstream's single implementation).
|
||||
- **Kept**: a 胄 drone costs 2 % of the leader's shown max HP = the pool (`DRONE_LINK_BASE 'pool'`, = v2.5; which "最大生命值" the official client reads is [ASSUMED] — the review tried the unit's data 600 000, which made drones 19–78 % of a solo 标准 fight, and went back). Under 限伤 it is one hit: 144 000 at most today (hidden 胄 终极), cancelled only above a 14 999 950 pool (§20.12).
|
||||
- **Ruled out by measurement**: double crediting (client / server / replica), multi-hits through the 5×3 hit area, element bursts, %-max-HP damage on leaders (only the drone link).
|
||||
- **Official, not a bug**: per-layer bonuses have no cap below 999 (谢拉格 vs cold ×(1.35 + 0.01 L) = ×11.34, 奥术 at low HP ×15.7, 精准 +1208.8 % ATK at 999 layers), so very high layers still kill fast — 绝境 with +400 on every active bond: median 8.6 s, fastest 3.6 s; 终极 under 20 s needs ≈ +400 on every bond (≈ 2 800 active layers per player; median 16.8 s); with two bonds per player at 999 (≈ 2 000 layers) 绝境 kills median 10 s, 7 of 28 under 5 s; with every active bond at 999 终极 median 8.3 s, 绝境 3.7 s — and there 限伤 (§20.12) cancels 6–77 hits per 12–28 fights. The community reports the same (巴哈姆特 12522, 999 谢拉格 "boss一秒死").
|
||||
- **Measured** (real seconds — the fights run at 2×, so half the game seconds of the QA tables). Integrated build (QA 6b on `cca11e6`, after the elite-to-board merge): the R14 boards of real 4-AI co-op matches, seeds 7 / 8 / 9 / 12 × boss_1…7 = 28 Final Assaults per row, both pair fields on one pool, the same boards replayed on v2.5 → now: 终极 own layers median 206 → 258 s, +200 on every active bond 24.5 → 42.1 s (fastest 8.2 → 18.9 s), +400 11.3 → 16.8 s; 绝境 own 79 → 99 s, +200 11.6 → 14.8 s (fastest 1.9 → 7.3 s), +400 6.1 → 8.6 s; each player's top bond requested at 1500 (now held at 999): 终极 53 → 95 s, 绝境 17.5 → 33 s. On each version's own bot boards the medians moved only ×1.0–1.2 (终极 own 256 → 258 s, +200 47 → 54 s; 绝境 own 81 → 99 s, +200 13 → 15 s): the elite-to-board rule (§20.11) gives the bots slightly stronger R14 boards (终极 2.12 → 2.26 elites per player, 绝境 2.14 → 2.51; 绝境 6.0 → 6.9 active bonds), so a player whose board did not change feels the same-board numbers. No landed leader hit reached 300 000 (v2.5 landed up to 18.6 M); the largest at +400 is 144 000 (a hidden 胄 drone; 114 000 from an operator), at 999 layers 299 600. The boss-HP workstream's own measurement (42 pre-merge boards, seeds 7–9: 终极 +200 30.9 → 56.0 s, …) is kept in BALANCE §3, labelled as such; BALANCE §3 has both tables.
|
||||
- Tests: `test/match/bosshp.test.js` (real Final Assault / Hidden Core matches, the 40 bloodPoint values; the 奥术 pair test runs seed 12 since the elite-to-board merge changed the bots' boards), `test/sim/bosshp_stacking.test.js`, `test/match/layercap.test.js`, `test/sim/battle.test.js` (Battle.addLayers cap), `test/content/bonds_addon.test.js`, `kits_t4.test.js`, `kits_alt_t4.test.js`, `enemies_bosses.test.js`, `items.test.js`, `garrisons_battle.test.js`, `choices.test.js`, `test/match/unitStats.test.js`, `finalAssault.test.js`, `balance.test.js`, `playtest6-bosspool.test.js`; opt-in `SIM_E2E=1 node --test test/sim/bossfield.browser.test.js` (the real boss / hidden specs of a 999-layer bot match give the same digest, pool and fx in headless Chrome as in Node; covers §20.12 and §20.13 too).
|
||||
|
||||
### 20.11 A merge's elite takes the deployed copy's tile (user-confirmed) — `match/PlayerState.js _mergeChess / transformChess`, `match/board.js mergeTile`, `match/audit.js`, `ui/gameLogic.js mergeProgress / mergeTarget`, `ui/shopBar.js armedSlotOf / onArm`, `screens/game.js MERGE_HL`, `render/promote.js`, `render/fx.js promote`, `render/app.js setPrep`
|
||||
|
||||
- **Seen** (the user, first-hand, after playtest #6): "官方就是合成精锐时,如果消耗了场上的干员,精锐会出现在场上那个位置".
|
||||
- **Cause**: research 01 A1 row 7 read PRTS 帮助 as "1 elite goes to the hand, not to a board tile" (marked VERIFIED, repeated in `01-core-data.json` promotion.eliteGoesTo), so `_mergeChess` put every elite in the hand and used a consumed copy's tile only when the hand and temp were both full.
|
||||
- **Official** (PRTS 卫戍协议/帮助): §干员的获得与精锐化 "…发送1名【精锐】状态的该干员至手牌区(若消耗已部署至作战区的干员,则发送至作战区对应位置)"; tip "干员晋级后已配发装备会回收至整备区,需要重新配置"; §战斗部署 "如果部署的干员拥有可手动部署的附属召唤物,则该召唤物会立刻加入手牌区", deploy order "从上到下>从左到右". The text does not say which tile wins among several deployed copies.
|
||||
- **Now (server)**: when a consumed copy stood on the board the elite takes that tile and its facing, otherwise it goes to the hand, overflowing into temp (outside PREP too). Several deployed copies: the one that deploys first — row desc, then col asc, the mirrored right boss half included — `board.js mergeTile(tiles, legal)` [ASSUMED]. A deployed piece transformed into the completing copy (突变细胞, `transformChess` `fromKey` / `fromDir`) counts as deployed with its own tile [ASSUMED]. A tile the elite may not use is skipped; with the hand and temp also full the elite stays on the first deployed copy's tile rather than being lost (as `_evictIllegal`). The deploy count never grows (no BOARD_FULL path). Equipment returns to the hand, overflow temp; with both full it stays on the elite up to its `equipPerChess` (2) slots, any further item destroyed with a log warning (unchanged). The consumed copies' summons are removed; an elite on the board gets its own fresh stack (`grantTokensFor`, after the items) following the §16 loadout [ASSUMED: placed summons are not carried over]. Every path (g.buy, g.reward, `ctx.grantChess`, 画卷, transformations), every phase (a SETTLE / Final Assault merge places the elite at once, its reward offer waits for the next prep) and the boss-field prep (legality on the player's half, §19.7). Reward offer, ticker and `stats.merges` unchanged; `onMerge` carries `area` ('board' | 'hand' | 'temp'). Bots plan the elite like any owned unit.
|
||||
- **Now (client)**: `gameLogic.mergeTarget(priv, chessId)` mirrors `mergeTile`; a shop or reward card armed for a merge-completing buy lights the target tile in gold (`ShopBar onArm` → `screens/game.js` highlight group `mergeTile`); the 可晋升 tag says 精锐干员将出现在作战区原位置 or 精锐干员将进入整备区; an elite card never merges (`completesChessMerge` refuses `isGolden`, `mergeProgress` reports 0 copies: no pips, tag or tile); `render/promote.js promotionsOf` recognises a merge in setPrep and the elite plays `fx.promote` (gold pillar, shockwave, hex ring, streaks from the other copies) instead of `fx.deploy` (`view.debug.promotions`).
|
||||
- **Checks**: `audit.js` checks every merge — the tile is `mergeTile` of the deployed copies legal for the elite, with that copy's facing; the deploy count did not grow (a carrier `transformChess` took off the board first still counts); otherwise the elite is not on the board, except the no-room fallback. `tools/matchrun --check` sweeps are clean.
|
||||
- Normative lines rewritten: §2 (`promote.js`), §6.2 (the economy line and the temp-overflow paragraph), §9 (the promotion cue).
|
||||
- Tests: `test/match/playtest6-elite-to-board.test.js`, `test/match/merge.test.js`, `hardening.test.js`, `facing.test.js`, `test/ui/playtest6-elite.test.js`, `test/render/playtest6-promote.test.js`, `test/docs-consistency.test.js`; opt-in `SP_E2E=1 node --test test/ui/playtest6-elite.e2e.test.js` (real server and prep: the armed card lights the deployed copy's tile, the elite stands there after the buy, 剩余可放置角色 unchanged).
|
||||
|
||||
### 20.12 Official limits: 999 layers per bond, 限伤 300000 on leaders — `shared/constants.js BOND_LAYER_CAP / BOSS_HIT_LIMIT / layerGainRoom`, `match/PlayerState.addLayers`, `Match` SETTLE, `sim/Battle.addLayers / loseHp`, `sim/damage.js leaderHitCancelled`, `match/fields.js validateClientResult`, `match/invariants.js`, `render/fx.js`, `tools/matchrun.mjs`, `tools/balance.mjs`
|
||||
|
||||
- **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 — 限伤**: 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.
|
||||
- **Measured**: the competent and strong balance boards never reach the line (biggest leader hits 28k–72k), so BALANCE §3's model numbers stand (BALANCE §7); the co-op replays up to +400 per bond and a top bond at 999 cancel nothing either. Two bonds per player at 999 or every active bond at 999 do reach it: 6–18 cancelled hits per 28 Final Assaults and 14–77 per 12 Hidden Cores (QA 6b), and those kills take longer than with v2.5's uncapped hits (§20.10).
|
||||
- **Flip**: `BOND_LAYER_CAP = 0` and / or `BOSS_HIT_LIMIT = 0` (or Infinity).
|
||||
- Tests: `test/sim/playtest6_limits.test.js` (including the real-kit vest, 再生 and 【死亡集群】 cases), `test/match/playtest6-limits.test.js` (a 999-layer board through a real Final Assault + Hidden Core, both tools at the cap), `test/render/playtest6_hitcap.test.js`, `test/sim/playtest6_bosspool.test.js` (adapted), and the boss-HP workstream's `test/match/layercap.test.js` / `test/sim/battle.test.js` cap tests on the single API.
|
||||
|
||||
### 20.13 假想敌:胄 kit audit (PRTS) — `content/bosses.js kitHelm / kitBlade`
|
||||
|
||||
- **Audit** (a separate session, 2026-10-01), against the PRTS 技能 / 天赋 / 能力修正 texts and the skill blackboards:
|
||||
- 灭顶之灾 fires **one** 刺胄之弹 below 20 % too (PRTS 能力修正 原因 6 "描述与游戏实际表现不符合": no extra shell; no blackboard holds a second one), at the highest-ATK operator **within attack range** for both copies ("触发索敌和普通攻击相同"; boss_1 used the whole field).
|
||||
- 死亡集群: **any** drone death costs the leader 2 % max HP (= the pool, §20.10), credited to the killing operator if one killed it; a leak costs nothing (能力修正 "该无人机单位死亡时"). Drone HP: `summon.hp_ratio` (隐秘核心) scales it; skill 2's `max_hp` 0.5 is not read [ASSUMED] — no source defines it.
|
||||
- 斩胄之剑 / 破胄之锤: 初始模式 hovers invulnerable (untargetable [ASSUMED]) and attacks everything in range (锤 150 % ATK, unblockable). 掷 → 出击模式: blink to the start of the level's `<hand>_blink` route, fly at the lowest-ATK operator, no normal attack. 15 hits → 【瘫痪】: stun 10 s (its end ends 瘫痪, not the 20 s), ground unit, damage taken ×1.3. Landing or 瘫痪 ending: a new 初始模式 copy with the old HP is summoned on the `<hand>_origin` route and the old one is killed (a part: uncounted, no bounty) [ASSUMED: only the HP is inherited, so 掷 comes back after its initial 25 / 55 s instead of the 80 s cooldown].
|
||||
- Every damage a 剑 / 锤 takes passes 100 % to 胄 (PRTS "等量", also during the dive) — merged with §20.10 into the one `PART_TRANSFER` 1 (`BLADE_TRANSFER` is its alias), 无来源 and credited to the attacker.
|
||||
- **Measured** (same-seed `balance.mjs --boss-only`, n = 3–5 per cell): boss_1 unchanged (≤ 0.5 s); boss_8 独立 绝境 44.7 → 45.2 s, 独立 终极 win 2/3 → 3/3, 同盟 绝境 23.3 → 23.7 s, 同盟 终极 72.0 → 64.2 s (BALANCE §3).
|
||||
- Tests: `test/content/enemies_bosses.test.js` (shell count and range, drone deaths and leaks, the pool-based link, the sortie / 瘫痪 / replacement cycle with the 1:1 无来源 transfer and its credit).
|
||||
|
||||
### 20.14 QA residuals of the integrated build (2026-10-02) — `match/Match.js _onResult / heldResult`, `render/app.js promoBase`, `ui/shopBar.js mergeHint`, `ui/detailPanel.js`, `screens/game.js dSide`
|
||||
|
||||
Two QA verifiers ran the integrated `playtest6b` (real 4-AI and 2-human co-op matches for every leader, Node vs headless Chrome digests, the merge UI on the user's phone profile and on desktop). Every check passed; the residual issues and what was done:
|
||||
|
||||
- **Fast boss kills handed over (fixed).** Under client-side combat a pair that empties the pool faster than the plausibility budget (one field credits ≤ the pool per `BOSS_MIN_CLEAR_GS` 5 game s, §14) had its `'cleared'` `b.result` rejected as implausible, the client demoted and the field handed to the partner or the server — seen at 绝境 with every bond at 999 (kills at 2.0–4.0 game s; official 999-layer play reports "boss一秒死"). Now a `'cleared'` result whose cumulative report covers what the pool holds (1 HP slack, the pool floor) is **held** (`f.heldResult`): the boss clock credits 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 completes the field with it (`_endFinal`); a team-LP defeat first still credits nothing more. The budget itself is unchanged, so a forged report gains nothing a `b.progress` could not already get. The verdict waits at most until the budget covers the report (≤ 5 game s from the field's start). Re-run of the QA cases (绝境 seed 7 boss_1 / boss_6 with every bond pushed to 999, seed 21 with 990 early layers, 3 more at 999 / 终极): 0 rejections, 0 takeovers, every boss field kept by its client, Σ credited boss damage = the cleared pools; in headless Chrome (2 humans + 2 AI, a scratch server with a 12× slower budget so the path runs) both boss rounds held the clearing client's result and went on to the Hidden Core and the result with no takeover and no console error.
|
||||
- **No promotion cue for a merge outside the prep (fixed).** A merge completed between two preps (a band / 机变 grant at ROUND_START, a SETTLE merge) placed the elite silently: `enterBattle` emptied the prep state the cue compares with. `render/app.js` keeps the last prep pieces across a battle or scouting board (`promoBase`), so the next `setPrep` plays `fx.promote` on the elite (streaks from the consumed copies' slots). Re-run of the QA case (松桐 grants the third 古米 at R4 ROUND_START): `view.debug.promotions` records the elite on the copy's tile, copies 2.
|
||||
- **The 可晋升 destination on touch (fixed).** The tag's text was a native `title`, never shown on a phone. The detail card the first tap opens now says it under the header — 「可晋升:精锐干员将出现在作战区原位置」 / 「…将进入整备区」 (`shopBar.js mergeHint`, gold `.dhint--merge`; shop and reward cards).
|
||||
- **The detail card covered the gold tile (fixed).** At 1920×1080 the left card covered half of a target tile in the leftmost legal column; the card now docks on the side away from the armed merge tile (`screens/game.js dSide`, the underframe's `panelSide` rule), on phones too.
|
||||
- **Measurements re-taken (docs).** §20.10 / §20.12 and BALANCE §3 / §7 now give the integrated build's numbers (real seconds; the earlier boss-HP numbers are labelled as pre-merge boards) and the 999-layer profiles where 限伤 cancels hits.
|
||||
- **Not changed — per-source talent debuffs [ASSUMED].** Some talents key their debuff with the source unit (`etlchi:dot:<id>`, `ines:atkSteal:<id>`, `ines:sentry:<id>`, and similar slows / DoTs in other kits), so two copies of one operator (a pair field where both players own it) each apply theirs. PRTS 作战机制 says only that same-named buffs show one instance under the *default* overlap strategy; whether these talents use it across copies is not in the data the project has, and the effect on leader kill times is small (an arts DoT, an ATK steal, a slow). The rule stays applied to the debuffs that move leader kills (奥术, 灵知 坚冰, the RES cuts — §20.10).
|
||||
- **Not changed — temp items behind a full hand.** With the hand full, items overflowing into the 临时整备区 (row 8) stand behind the bench row's operator models and are hard to see; the row's red frame, the 「临时整备区 N 件道具待处理」 notice and the refused Ready still show them. A layout change of the temp row is left for a later pass.
|
||||
- Tests: `test/match/clientCombat.test.js` (the held result; a 'cleared' result not covering the pool is still handed over), `test/render/playtest6-promote.test.js`, opt-in `RENDER_E2E=1 node --test test/render/browser.test.js` (a merge between two preps is cued), `test/ui/playtest6-elite.test.js` (the detail hint), opt-in `SP_E2E=1 node --test test/ui/playtest6-elite.e2e.test.js` (the hint in the real detail card).
|
||||
Reference in new issue
Block a user