feedback1 integrate: DESIGN §21.21-§21.25 and CHANGELOG for batch 6 and the PR-credited fixes

DESIGN maps F1-F5 in the §21 intro, adds the hammer-per-deployment row (user 2026-10-03) to
§21.20 with the batch's official rules and [ASSUMED] items, and rewrites the normative lines:
§3 fenced tiles, §5.3 liftoff, §5.4 fatal / ignoreSelect / redeploy, §5.5 bodies, blocking and
enemy selection, §6.4 onBuy and the item snapshot, §8.2 down row/col, §18.3, §19.7. CHANGELOG
0.1.1, SIM, DATA and PLAYING follow; docs-consistency checks them against the code.
This commit is contained in:
sganggs committed 2026-10-03 13:56:19 +08:00
1 parent 36d6766d76
commit e30e038ba2
6 files changed
+135 -27

No files matched your search

+61 -16
View File
@@ -131,7 +131,7 @@ Ownership rule for parallel agents: **only edit files assigned to you**; if you
- **Partner display** cols 11–18 of rows 9–12 (the unite field uses them).
- **Boss field**: rows 1–5; left half cols 2–10, right half cols 10–18.
- Enemy preview pen rows 14–18 × cols 7–13 (row 16 empty): during prep the next round's enemies idle there (upper-gate enemies rows 17–18, lower-gate rows 14–15, ≤ 50 models, spawn-time order) — research 08 §4 / 09 §2.
- Tile semantics come from `data/stages.json` legend: height (LOW/HIGH), buildable (ALL/MELEE/RANGED/NONE — the **effective** deploy type: the 深水区 `tile_deepsea` refuses deployment, NONE with the level's `buildableType` ALL kept, PRTS 深水区 地形信息 "拒绝部署(待补充)"; an active 特制水上平台 makes its tile deployable in the prep deploy map only, §21.3), ground-passable, fly-only, special terrain (mire, smog, deepsea, infection). Melee chess may stand only on LOW tiles with buildable ALL/MELEE; ranged chess on ALL/RANGED **and** on LOW ALL/MELEE tiles ("所有行动内远程干员可部署在近战位"). Tokens follow their own `position`; a summon marked `ownerRange` (狼群, 流形: "只能部署在召唤者攻击范围内") goes only on a tile of its owner's attack range — the loadout grid rotated by the owner's facing around its tile (`PlayerState.summonRange`, §21.3).
- Tile semantics come from `data/stages.json` legend: height (LOW/HIGH), buildable (ALL/MELEE/RANGED/NONE — the **effective** deploy type: the 深水区 `tile_deepsea` refuses deployment, NONE with the level's `buildableType` ALL kept, PRTS 深水区 地形信息 "拒绝部署(待补充)"; an active 特制水上平台 makes its tile deployable in the prep deploy map only, §21.3), ground-passable, fly-only, special terrain (mire, smog, deepsea, infection). The fenced tiles (围墙 `tile_fence_bound` / 围栏 `tile_fence`, legend `b`: LOW, buildable ALL, passable to flyers only) take units, which attack normally from there but block no ground enemy (PRTS 地形机制, §21.23). Melee chess may stand only on LOW tiles with buildable ALL/MELEE; ranged chess on ALL/RANGED **and** on LOW ALL/MELEE tiles ("所有行动内远程干员可部署在近战位"). Tokens follow their own `position`; a summon marked `ownerRange` (狼群, 流形: "只能部署在召唤者攻击范围内") goes only on a tile of its owner's attack range — the loadout grid rotated by the owner's facing around its tile (`PlayerState.summonRange`, §21.3).
- **Facing (corrected, see research 09 §1.2):** like standard Arknights, every board piece has a direction `dir ∈ UP|RIGHT|DOWN|LEFT` chosen with the 4-direction deploy wheel after dropping it on a tile (also re-orientable in place); default `RIGHT`; persists across rounds. Range grid entries are `[dRow, dCol]` relative to facing RIGHT and are rotated: RIGHT `(dr,dc)`, UP `(dc,−dr)`, LEFT `(−dr,−dc)`, DOWN `(−dc,dr)` (row 0 = bottom); `rangeExtend` applies along +dCol before rotating — to a running skill's range too, unless that skill ignores 攻击距离 (`targeting.noRangeExtend`, 信仰搅拌机 S3, §21.16). The right-side Final Assault player is mirrored col `c→20−c` with RIGHT↔LEFT (UP/DOWN unchanged).
- **Range test:** an enemy is inside a grid range if the tile containing it (`round(y), round(x)`) is one of the range tiles; a huge enemy (data `hitArea`: 巨型单位, a 4.95 × 2.95 rectangle moved 1.0 up) if any tile its hit rectangle overlaps is one (PRTS 作战机制 "巨型BOSS单位的每一个占据的格子都可以让其本身通过格子判定"; `sim/body.js`, §19.4). Radius ranges (enemies, auras, AoE) use Euclidean distance in tiles — to a huge enemy's rectangle [ASSUMED] — except splash around a struck target, a 中点判定 on positions (PRTS 作战机制 "中点判定…案例:阻挡,酒神1天赋的1.3溅射半径"). A ranged enemy's normal attack takes an ally whose collider (radius 0.25, PRTS 作战机制 §碰撞体积 "我方干员碰撞体积基本均为以0.25格为半径的圆形") touches its range circle: centre distance ≤ `rangeRadius` + 0.25 (`constants.js ALLY_COLLIDER_RADIUS`, `ai.js enemyAttack`; §20.4).
- **Fields at runtime:** a `Battle` receives a **field rect** (rows/cols subset) and the full stage grid. Normal battles use rows 9–12 × cols 0–10; unite uses rows 9–12 × cols 0–20; boss uses rows 0–5 × cols 0–20 (solo boss uses the left half only). Pathfinding never leaves the rect.
@@ -229,7 +229,7 @@ Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `ba
`Buff = { key, source, duration /*s, Infinity*/, stacks=1, maxStacks=1, refresh: 'replace'|'extend'|'stack', mods, flags, onTick?, onExpire?, tags[] }`
- `mods` keys (all optional, additive within a key): `atkFlat, atkPct, atkMul, defFlat, defPct, defMul, hpFlat, hpPct, hpMul, resFlat, resMul, aspd, batPct, blockCnt, rangeExtend (tiles), moveMul, dmgDealtMul (multiplicative, e.g. 1.2), dmgTakenMul, physTakenMul, artsTakenMul, trueTakenMul, defIgnoreFlat, defIgnorePct, resIgnoreFlat, resIgnorePct, dodgePhys, dodgeArts, healingDealtMul, healingTakenMul, spRecoveryFlat, spRecoveryMul, maxTargets (+n), shield (absorbs damage; consumed)`.
- `flags`: `stun, freeze, sleep, silence (no skills), disarm (no attacks), stealth (untargetable by enemies/ops unless blocked or revealed — an operator's radius area damage skips it too, `Battle.foesInRadius`, PRTS 作战机制 §AOE伤害判定, §21.19; an enemy's b.snap stealth bit is set only while its 隐匿 is on, §21.4), 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)`.
- `flags`: `stun, freeze, sleep, silence (no skills), disarm (no attacks), stealth (untargetable by enemies/ops unless blocked or revealed — an operator's radius area damage skips it too, `Battle.foesInRadius`, PRTS 作战机制 §AOE伤害判定, §21.19; an enemy's b.snap stealth bit is set only while its 隐匿 is on, §21.4), camou (an ally's 迷彩: like stealth for enemy targeting and on screen, but not 隐匿 for 隐匿 conditions — 叙拉古, 家族徽章; §20.2), liftoff (an ally's 起飞, 蒂比's skills: it blocks flyers only and has 对地规避 — no ground enemy selects it, so no selected damage or status of one lands, while what selects nobody still does: `ignoreSelect` / 无来源 — 无视无法选择 abilities, direct picks, flying units' blasts, a debuff's ticks; it stays a ground unit on its tile; `targeting.js evadesGround`, §21.22), 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`; a unit both `invulnerable` and `untargetable` (a 重生, a 永久无敌 leader part) takes no status from the other side at all (PRTS 无敌 "无法被不同阵营选中", §21.19). Flag `selfBound` = 自缚 (守墓石像's 转换模式, the 自缚 leaders) beside its `noMove`: 余 S2 never teleports such a unit (束缚 sets `noMove` only).
- 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). PRTS 盟约记录's current 奥术 note fits this under the default overlap; an equal re-hit refreshes the instance (an earlier PRTS wording forbade re-applying while one's own lasts) [ASSUMED, slightly stronger] (§21.7).
@@ -248,6 +248,7 @@ Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `ba
| `hit` | `{ source, target, dmg }` | before mitigation; mutate `dmg` (DamageInfo) |
| `damaged` | `{ source, target, amount, type, dmg }` | after application |
| `heal` | `{ source, target, amount }` | before application (mutable amount) |
| `fatal` | `{ unit, source, credit, dmg, amount, prevented }` | HP would reach 0 — fired by every HP loss of a unit without a boss pool (hits of any type, element bursts, 无来源 damage, `loseHp`); set `prevented`. Order: kits' own savers (10 … −60) → items' 不死 (坚固维式重锤, once per deployment) `PRIO_REVIVE` −100 → items' 复活 (M3茧甲) `PRIO_RESPAWN` −101 → 埃芒加德 −110 (§21.21) |
| `kill` | `{ killer, victim }` | victim hp reached 0; a `killed` ability that keeps the enemy alive (a knock-out into a 重生 / form change, §21.4) hides it from every later handler, the kill count, credit and the bounty |
| `death` | `{ unit }` | unit removed (ops may redeploy later); the `die` event and the death hooks fire only on removal |
| `skillStart` / `skillEnd` | `{ unit, skill }` | |
@@ -258,18 +259,18 @@ Base stats (from data): `maxHp, atk, def, res` (0–100), `aspd` (100 base), `ba
| `enemySpawn` / `enemyLeak` | `{ enemy }` | |
| `battleEnd` | `{ result }` | may add layer gains / coins |
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[] }`.
DamageInfo: `{ amount (pre-mitigation), type: 'phys'|'arts'|'true'|'element', element?: 'burn'|'necrosis'|'neural'|'apoptosis', atkScale, defIgnoreFlat, defIgnorePct, resIgnoreFlat, resIgnorePct, mul (multiplier), canDodge, isSkill, isSplash, ignoreSelect (no selection — a 无视无法选择 ability, a direct pick, a debuff's tick: it reaches an airborne 起飞 ally, §21.22), 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; a 静态刚体 — data `staticBody` — is never moved, §21.9; `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)` (every enemy: enemy-side auras / heals, collisions), `battle.foesInRadius(x, y, r)` (the enemies an operator's area effect can select — no untargetable, no unblocked unrevealed 隐匿 enemy; PRTS 作战机制 §AOE伤害判定, §21.19), `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 })` (流失 — it skips the damage events: no 受击回复, TAKE_DAMAGE, shield or counter, `damage.js isHpLoss`; only for what the official calls 流失 — "N 真实伤害 per second" effects such as 源石溶剂 are `periodicDamage` damage, §21.12; a 物理流失 is first reduced by the target's DEF, 阿戈尔's devour, §21.19; `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)`.
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; a 静态刚体 — data `staticBody` — is never moved, §21.9; `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)` (every enemy: enemy-side auras / heals, collisions), `battle.foesInRadius(x, y, r)` (the enemies an operator's area effect can select — no untargetable, no unblocked unrevealed 隐匿 enemy; PRTS 作战机制 §AOE伤害判定, §21.19), `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 })` (流失 — it skips the damage events: no 受击回复, TAKE_DAMAGE, shield or counter, `damage.js isHpLoss`; only for what the official calls 流失 — "N 真实伤害 per second" effects such as 源石溶剂 are `periodicDamage` damage, §21.12; a 物理流失 is first reduced by the target's DEF, 阿戈尔's devour, §21.19; `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)` (on its rest tile: where a knocked-out operator lies, else its home; `battle.isReservedTile(r, c)` = a tile no automatic placement takes, `battle.downOn(r, c)` = the knocked-out operator lying there, whose tile no ally deploys or moves onto — §21.24), `battle.after(seconds, fn)` (scheduled callbacks), `battle.every(seconds, fn)`.
### 5.5 Combat rules (engine)
- **Initial deployment** at t=0, free: operators row-major top→bottom then left→right (right-side boss player: right→left), then the summons (PRTS 卫戍协议/帮助 "按从上到下>从左到右的顺序部署。优先部署干员,随后为召唤物"; `aggroSeq` ranks every start-of-battle summon after all operators) — a skill's placed summon (赫默 医疗探机, 巫恋 诅咒娃娃) included, once and free ("所有手动部署的召唤物,无视所属干员的持有状态…作战开始时立即部署一次"; afterwards it re-appears on its tile each time the owner's skill gives one, §20.1); on a shared field (联防, boss) the players' i-th operators deploy together [ASSUMED]. Deploy effects fire (`deploy` with `initial: true`). Then operators carried into 联防 knocked out (`carryState.down`) are forced out (`FORCED_EXIT`, HP 0, §19.3). Then `battleStart`, after which the forced-out operators' timers are re-read (redeploy-time effects that start with the battle, e.g. 机变 征召, cover them too).
- **DP**: per player, start 10, +1/s, cap 99; used only for auto-redeploy (dead op: after `respawnTime`, if its tile is free and DP ≥ cost ⇒ redeploy with full HP, SP = initSp). A knocked-out operator stays **down** on its tile from the knock-out until it redeploys (b.snap `down`, §18.3: counting / waiting for DP / its tile taken) — also one that entered 联防 knocked out (`FORCED_EXIT`, its full redeploy timer — confirmed by the user — with the battle-start redeploy effects, §19.3); summons, devices and enemies leave at once.
- **Blocking** (contact radius, PRTS 游戏数据基础 §阻挡半径 / 作战机制 "中点判定…案例:阻挡"; `constants.js BLOCK_RADIUS`, `Battle._checkBlock`, §19.2): an operator with free capacity blocks an unblocked, blockable enemy whose position lies within its block radius of its centre — ground 0.7071 (squared 0.49999037), 0.8944 for blockFly units vs flyers (起飞), devices 0.4472 (阻隔工事 / 障碍物); air units (FLY, 近地悬浮, 浮空) only by blockFly units. The nearest such operator wins (ties: scan order [ASSUMED]). It is re-checked every tick, moving or not, so another operator in contact with room takes the enemy over when its blocker dies, is withdrawn or is stunned, and an operator whose capacity frees up grabs an overlapping enemy; one that finds no room walks on. A head-on enemy stops at contact (≈ 0.71 tile), outside the blocker's tile. Enemy uses `blockCnt` capacity (default 1). Blocked enemies stop moving and attack their blocker if melee (or anyone in range if ranged, blocker first). **Every blocker** — melee units (重装要塞 / 领主 / 哨戒铁卫 included), summons and a ranged operator on a melee tile alike — whose attack hits enemies may always target the enemies it blocks, in range or not, whatever its facing, and targets them first ("可以选择且优先选择阻挡单位", PRTS 选择器; the user's rule after playtest #6, "阻挡了就一定要能打到": officially the collision pushes a blocked enemy to its blocker's front; `Battle.blockedTargets`, `targeting.js sortEnemyTargets`; §20.3 — it replaces the playtest #5 QA's melee-only restriction of §19.2). A healer (dmgType `heal`, or a skill attack turned into a heal) selects injured allies only and keeps healing while it blocks (PRTS 卫戍协议/帮助 "对于医疗干员(咒愈师分支除外),攻击目标为需要治疗的单位"; §21.17); 咒愈师 follow the blocker rule. The huge leaders are 自缚 + unblockable (§7).
- **DP**: per player, start 10, +1/s, cap 99; used only for auto-redeploy (dead op: after `respawnTime`, if the tile it lies on is free and DP ≥ cost ⇒ redeploy there with full HP, SP = initSp). A knocked-out operator stays **down** on the tile it fell on — its own home when it fell on another board piece's home and its home is free — until it redeploys there, and no ally deploys or moves onto that tile meanwhile (PRTS 卫戍协议/帮助 §作战阶段 单位部署; `Battle.downOn` / `restTile` / `_layBody`, §21.24; b.snap `down`, §18.3: counting / waiting for DP / its tile taken, a safeguard) — also one that entered 联防 knocked out (`FORCED_EXIT`, its full redeploy timer — confirmed by the user — with the battle-start redeploy effects, §19.3); summons, devices and enemies leave at once.
- **Blocking** (contact radius, PRTS 游戏数据基础 §阻挡半径 / 作战机制 "中点判定…案例:阻挡"; `constants.js BLOCK_RADIUS`, `Battle._checkBlock`, §19.2): an operator with free capacity blocks an unblocked, blockable enemy whose position lies within its block radius of its centre — ground 0.7071 (squared 0.49999037), 0.8944 for blockFly units vs flyers (起飞), devices 0.4472 (阻隔工事 / 障碍物); air units (FLY, 近地悬浮, 浮空) only by blockFly units. A ground enemy is never blocked by a unit standing on a tile ground units cannot pass (围墙 / 围栏: PRTS 地形机制 "部署在其中的单位,若当前阻挡类型为'地面阻挡'则无法阻挡敌人" — an enemy pushed or pulled against the fence walks on; air blocking there stays [ASSUMED], §21.23) nor by an airborne (起飞) unit, which blocks flyers only (§21.22). The nearest such operator wins (ties: scan order [ASSUMED]). It is re-checked every tick, moving or not, so another operator in contact with room takes the enemy over when its blocker dies, is withdrawn or is stunned, and an operator whose capacity frees up grabs an overlapping enemy; one that finds no room walks on. A head-on enemy stops at contact (≈ 0.71 tile), outside the blocker's tile. Enemy uses `blockCnt` capacity (default 1). Blocked enemies stop moving and attack their blocker if melee (or anyone in range if ranged, blocker first). **Every blocker** — melee units (重装要塞 / 领主 / 哨戒铁卫 included), summons and a ranged operator on a melee tile alike — whose attack hits enemies may always target the enemies it blocks, in range or not, whatever its facing, and targets them first ("可以选择且优先选择阻挡单位", PRTS 选择器; the user's rule after playtest #6, "阻挡了就一定要能打到": officially the collision pushes a blocked enemy to its blocker's front; `Battle.blockedTargets`, `targeting.js sortEnemyTargets`; §20.3 — it replaces the playtest #5 QA's melee-only restriction of §19.2). A healer (dmgType `heal`, or a skill attack turned into a heal) selects injured allies only and keeps healing while it blocks (PRTS 卫戍协议/帮助 "对于医疗干员(咒愈师分支除外),攻击目标为需要治疗的单位"; §21.17); 咒愈师 follow the blocker rule. The huge leaders are 自缚 + unblockable (§7).
- **Operator attacks**: every `interval` s if a valid target exists; targets = up to `maxTargets` (default 1) from `unitsInGrid(rangeGrid)` (a `rangeAoe` profile — 阵法术师, 轰击术师 — takes every selectable enemy in range, §21.18), 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; a 锁定攻击范围 AoE (`rangeAoe`) strikes every selectable enemy on its range at once with instant hits (§21.18).
- **Healers** (`dmgType heal`): target the ally in range with the lowest HP% below 100%, never a 禁疗 / 孤立 unit (tokens.json `abnormal`, `noHeal`; §20.3), and never the enemies they block — also while blocking (§21.17); 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 (`dmgType 'none'` enemies never attack unless content arms them through `e.profile` — 转译基底's armed forms, §21.4); 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).
- **Enemy AI**: follow the route (checkpoints; WALK paths recomputed on the grid when devices change); when blocked stop & fight (`dmgType 'none'` enemies never attack unless content arms them through `e.profile` — 转译基底's armed forms, §21.4); 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); an airborne (起飞) ally is never a ground enemy's selection — picks, area damage, zones and statuses skip it, while 无视无法选择 abilities, direct picks, debuff ticks and flying units still reach it (§21.22); 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` (脆弱 = "受到的物理、法术、真实伤害提升"). 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.
- **Results**: `damageDealt` / a unit's `dmg` count only HP removed from the other side — a unit's own drain or 流失 and friendly damage are the target's `taken` only (§21.12).
@@ -375,7 +376,7 @@ Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (inco
### 6.4 Prep-phase effect dispatcher (effectsMeta.js)
A registry mapping **effect keys** → handler objects `{ onRoundStart, onPrepEnd, onGain(piece), onSold(piece), onRefresh, onPrice(slot), onBuy, onMerge, onLevelUp, onBattleResult }`. Sources: garrison `SERVER_*` keys (per chess on board or hand as the key requires), band effects, item meta effects, bond meta effects (助力, 远见, 奇迹, 投资人…), choice/team cards. Content owners register handlers; the dispatcher owns ordering and the 投资人 "获得时 ×2/×3" multiplier.
A registry mapping **effect keys** → handler objects `{ onRoundStart, onPrepEnd, onGain(piece), onSold(piece), onRefresh, onPrice(slot), onBuy, onMerge, onLevelUp, onBattleResult }`. Sources: garrison `SERVER_*` keys (per chess on board or hand as the key requires), band effects, item meta effects, bond meta effects (助力, 远见, 奇迹, 投资人…), choice/team cards. Content owners register handlers; the dispatcher owns ordering and the 投资人 "获得时 ×2/×3" multiplier. `onBuy` = a shop purchase (`g.buy`) only: a free pick or a grant never counts as 购买 (休露丝, 玛恩纳, 升华, 整备; §21.21). Equipped items run from a snapshot — the owned chess and each holder's items as that step begins — each only while it is still equipped on its still-owned holder (handlers move and destroy pieces: 突变细胞, 博士投影; §21.25).
### 6.5 Waves (waves.js)
@@ -418,7 +419,7 @@ S→C:
- `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).
- `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.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, row, col]]` — knocked-out operators waiting to redeploy, `state` 0 counting | 1 timer done, waiting for DP | 2 timer done, its tile taken — a safeguard, no ally deploys there (sim/constants.js `DOWN_STATE`); `row, col` = the tile it lies on and comes back on (§21.24); `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]` (`'hitCap' {id, n}` = a cancelled 限伤 hit, drawn as nothing, §20.12; `extra.form` = the enemy's model form from then on — `shared/protocol.js fxForm`, state like a spawn: never dropped by the client, §21.4), `['layer', playerId, bondId, n]`, `['bounty', playerId, coins]`, `['deploy', id]`
- `m.toast {kind, text}` · `m.ticker {text, id, type, priority, playerId}` (the strip plays the highest priority first, §21.10) · `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, range?, base: {…same}}` (shared/protocol.js `unitStatsEntry`; `range` = an ally's live range grid, facing RIGHT, never a target-selection grid, §21.16) as its next battle starts (§18.5)
@@ -656,7 +657,7 @@ Thirteen reports after playing v2.2. Where each is handled: #1 picking should si
### 18.3 Knocked-out operators and element gauges (#9, #8) — `sim/Battle.js`, `sim/damage.js`, `render/interp.js`, `render/units.js`
- **Knocked out, not gone**: `Battle.snapshot()` lists `down: [[id, respawnAt, respawnTime, state]]` (§8.2) for operators knocked out (reason `'killed'`, not withdrawn — or `FORCED_EXIT`, entering 联防 knocked out, §19.3), deployed at least once and with a finite respawn timer; `state` = sim/constants.js `DOWN_STATE` (0 counting, 1 timer done but DP < cost, 2 timer done but the home tile is taken — the official auto-redeploy rule, research 01 §4.3). `Battle.isDown(u)`; `fieldMeta()` lists them too, so a client joining mid-battle (reconnect, observer, replica) shows them. The renderer (`interp.js downAt`, `app.js syncBattle`, `units.js setDown`) keeps such an operator's view on its tile: its Spine Die clip plays once and holds its last frame (a collapsed / kneeling pose), lightly greyed (tint 0xb4b4b4, alpha 0.92), under a **redeploy ring** above the head — a dark disc with a mint arc filling as the timer runs and the seconds left (game s); timer done: a full pulsing amber ring "DP" (waiting for DP) or red "!" (its tile taken). The `deploy` event plays the Start clip and restores the normal look; a view created for an operator already down starts on the held pose; leaving the list without a redeploy fades it out. Summons, devices and enemies vanish as before. [ASSUMED] look: the official knocked-down / ring art is in no extracted autochess bundle.
- **Knocked out, not gone**: `Battle.snapshot()` lists `down: [[id, respawnAt, respawnTime, state, row, col]]` (§8.2; the tile since §21.24) for operators knocked out (reason `'killed'`, not withdrawn — or `FORCED_EXIT`, entering 联防 knocked out, §19.3), deployed at least once and with a finite respawn timer; `state` = sim/constants.js `DOWN_STATE` (0 counting, 1 timer done but DP < cost, 2 timer done but its tile is taken — the official auto-redeploy rule, research 01 §4.3; a safeguard since §21.24, no ally may take the tile a knocked-out operator lies on). `Battle.isDown(u)`; `fieldMeta()` lists them too, so a client joining mid-battle (reconnect, observer, replica) shows them. The renderer (`interp.js downAt`, `app.js syncBattle`, `units.js setDown`) keeps such an operator's view on the tile it lies on (the entry's row, col): its Spine Die clip plays once and holds its last frame (a collapsed / kneeling pose), lightly greyed (tint 0xb4b4b4, alpha 0.92), under a **redeploy ring** above the head — a dark disc with a mint arc filling as the timer runs and the seconds left (game s); timer done: a full pulsing amber ring "DP" (waiting for DP) or red "!" (its tile taken). The `deploy` event plays the Start clip and restores the normal look; a view created for an operator already down starts on the held pose; leaving the list without a redeploy fades it out. Summons, devices and enemies vanish as before. [ASSUMED] look: the official knocked-down / ring art is in no extracted autochess bundle.
- **Element gauges, official rules** (PRTS 元素 / 术语释义; `damage.js`): gain × `elemTakenMul` × (1 − 损伤抵抗 / 100), 损伤抵抗 = data `epResistance` (EP_RESISTANCE; it used to read `epDamageResistance` as a fraction). A burst's **爆发冷却** (= its duration: 灼燃 / 神经 / 侵蚀 10 s — enemies' 侵蚀 8 s — 凋亡 15 s; the operators' 侵蚀 had none) locks EVERY element of the unit — nothing fills or recovers (`reduceElement`) — and its end resets EVERY gauge (`burstLocked(unit)` takes no element). 神经 applies the stun (operators) / 3 麻痹 (enemies) before its damage, 侵蚀 its DEF cut before its damage; the operators' 凋亡 burst holds 阻回 (`noSp`: `skills.js gainSp` refuses SP of every kind — time, attack, hurt, granted) + 静默, −1 SP/s and 100 arts/s.
- **The gauge on screen** (official display: one icon, the "当前损伤元素"): `damage.js elementView(u, now)` → b.snap `elem` (§8.2): the fullest gauge, ties by the official element id (`constants.js ELEMENT_ORDER`: 神经 < 侵蚀 < 灼燃 < 凋亡); during a 爆发冷却 the bursting element, refilling over the cooldown. `interp.js` appends it to the unit's tuple; `units.js` draws it — **superseded by §20.8**: a row under the unit's own HP / SP bars, the element disc (0.15 tile, 8–15 px; operators with the glyph, enemies ×0.8 plain colour) and a white bar of the remaining 元素值 (1 − fill), refilling in the element's colour with a pulsing disc during the 爆发冷却 (v2.3 drew a white ring right of the bars; the procedural atlas `textures.js hudRings` keeps the discs and the redeploy ring), sprites made on first use and hidden when idle. The burst's lock status (`burnBurst`, `neuralBurst` …) is not repeated in the status row while the element icon shows it (only a feed without `elem`, e.g. an older recording, shows it there). [ASSUMED] placement (the official draws it under the model, the remake's bars are above the head) and look (§20.8).
- Recordings (`tools/record-battle.mjs`) carry both keys, so the render demo shows them. Tests: `test/sim/downelem.test.js`, `test/sim/combat.test.js`, `test/render/downring.test.js`, `test/render/interp.test.js`, `test/render/downelem.browser.test.js`.
@@ -792,7 +793,7 @@ Ten reports after playing v2.3 (绝境 on desktop, 标准 on an Android phone in
- **#6 cause**: the 3D board builds only the current view's area, and for the normal / 联防 views it stopped at row 12. act2 m01's own blowers (#001 / #002, the partner's #101 / #102) stand on the separator row 13 facing DOWN into rows 12–10 (level_act2autochess_m01 predefines: (13, 5/9/13/17) and (6, 5/9/11/15), all DOWN, none hidden), so they were never built; the Pixi airflow streaks and the sim were right. **Now** the normal / 联防 field rects are rows 6–13 and one device rule serves both boards: every active device standing on a built tile is drawn (3D `stageDevices`, 2D `_stageDevices` minus its faded margin). Normal / prep: (13,5) and (13,9) above the lanes, and the boss field's (6,5) / (6,9) on the wall under the bench; 联防 adds (13,13) and (13,17); 最终攻势 / 隐秘核心: (6,5), (6,9), (6,11), (6,15).
- **#6, the boss field's machines under the bench**: the integration QA hid the row-6 blowers (6,5) / (6,9) in the normal / 联防 views (reading the official shop camera as putting that wall under the shop bar); the user's follow-up, first-hand — "吹风机原版道中也该有" — puts them back: the official normal rounds show them, so every view draws every device on its built tiles again (the rule above, as the workstream had it). Their airflow covers drawn tiles only, so it shows in the Final Assault view. Unchanged (cosmetic, pre-existing): the official normal view also shows the partner's half beside the own field (its row-13 machine (13,13) at the top right); the remake builds the own half only.
- **#7 cause**: every legality check of a boss round's prep read the normal field (board (10–12, 8) = tile_forbidden) — `board.js buildDeployMap`, the client's `canPlace` / highlights, the bot and the invariants — while the pieces stand on the player's half of the boss field, where (3–5, 8) and (3–5, 12) are tile_fence_bound (low ground, buildable ALL, fly-only passable). **Now** `buildDeployMap(stage, { field })` takes `'normal' | 'bossL' | 'bossR'` (board row = boss-field row + `BOARD_ROWS_ABOVE_BOSS` 7; the right-hand player mirrored col c → 20 − c, research 09 §1.2 ConvertChessPositionInfoToBossMap); `Match.bossGroupOf` is the one pairing lookup (`deployFieldOf`, `nextEnemiesFor`, the bot); `Match.startRound` plans the round's enemies before `PlayerState.startRound`, so R14 → R15 re-checks boss-half pieces on the boss field (they used to be withdrawn with 地形变化; rngWaves is used only there, no random stream changes); a quit before the boss fight re-pairs and re-checks at once; the client's `gameLogic.deployFieldOf` feeds `placementContext({ field })`. Operators on the fence tiles hit the huge boss (§19.4); ground enemies cannot walk onto those tiles.
- **#7 cause**: every legality check of a boss round's prep read the normal field (board (10–12, 8) = tile_forbidden) — `board.js buildDeployMap`, the client's `canPlace` / highlights, the bot and the invariants — while the pieces stand on the player's half of the boss field, where (3–5, 8) and (3–5, 12) are tile_fence_bound (low ground, buildable ALL, fly-only passable). **Now** `buildDeployMap(stage, { field })` takes `'normal' | 'bossL' | 'bossR'` (board row = boss-field row + `BOARD_ROWS_ABOVE_BOSS` 7; the right-hand player mirrored col c → 20 − c, research 09 §1.2 ConvertChessPositionInfoToBossMap); `Match.bossGroupOf` is the one pairing lookup (`deployFieldOf`, `nextEnemiesFor`, the bot); `Match.startRound` plans the round's enemies before `PlayerState.startRound`, so R14 → R15 re-checks boss-half pieces on the boss field (they used to be withdrawn with 地形变化; rngWaves is used only there, no random stream changes); a quit before the boss fight re-pairs and re-checks at once; the client's `gameLogic.deployFieldOf` feeds `placementContext({ field })`. Operators on the fence tiles hit the huge boss (§19.4) but block no ground enemy there (§21.23); ground enemies cannot walk onto those tiles.
- **Audit** (all 11 stages × normal / 联防 / boss L / boss R, server = client = builder deployTiles = the raw level files, except the 深水区 refusal and the 特制水上平台 exception of §21.3): the only wrong tiles were act2 m01's six boss tiles; on every stage normal-legal ⊆ boss-legal and boss L = boss R, also under every map card, so R13 → R14 and R14 → R15 move nothing. act2 m01 now deploys 13 melee + 3 ranged-only tiles on the normal board and 16 + 3 on each boss half. Devices: 3D = 2D for every stage × view; act1 m05's 特制水上平台 are ×64 (16 per normal half, 32 on the boss field). The DOM fallback board (`ui/fallbackField.js`, no WebGL / `?render=fallback`) draws the player's boss half in the boss prep too — `screens/game.js` now hands it the `bossPrep` camera, which it maps through `gameLogic.fieldTile` on the own board's layout (integration QA: it drew the normal field's walls under legal highlights). Limitations: `Match.unitStats` previews on the normal field (stats only); 射击台 stays ranged-only [ASSUMED].
- Tests: `test/match/playtest5-deploy.test.js` (incl. boss → boss R14 → R15 and the fallback board), `test/render/playtest5-maps.test.js` (every device on a built tile in every view, the row-6 machines under the bench included), `test/ui/playtest2.test.js`; browser (opt-in) `test/render/board3d.browser.test.js` (area key '6,13,0,10').
@@ -1114,9 +1115,9 @@ In SETTLE it is the field the last battle left on screen. In prep without watchi
## 21. Player feedback after 0.1.0 (v0.1.1)
Twenty-seven reports after the public 0.1.0 release (2026-10-02), relayed by the user in five batches; most were not tested by the user. Verdicts: **real** (fixed), **partly** (the rule was right, what a player sees was wrong or a related part was wrong — fixed), **not real** (not reproduced; tests lock the rule). Where each is handled: batch 1 — #1 拉普兰德's "获得该干员后该回合的首次刷新" should stack 叙拉古 → §21.1 (real); #2 late bounty enemies offered in early bounty drafts → §21.2 (real); #3 the water pool map: operators deployable in the water, 突袭 operators land in it → §21.3 (real); #4 昆图斯's 突变细胞 gone after one use → §21.1 (real: not consumed); #5 转译基底 does not transform → §21.4 (real); #6 凯瑟琳's three upgrade items shown wrong → §21.2 (real); #7 鸭爵's 圆仔 blockable, audit the other 鸭爵 enemies → §21.5 (real); #8 深池逐火战士's 怨恨的余烬 cannot be killed → §21.4 (real); #9 tacticians (伺夜) can place the tactical point outside their attack range → §21.3 (real); #10 bots too dumb, smarter without more CPU → §21.6 (real). Batch 2 — B1 奥术 not working → §21.7 (partly: the bond works, 标准 switches it off); B2 源石溶剂 white + white → gold, then an extra white → §21.7 (not real); B3 维娜·维多利亚 S3 summons one 黄金盟誓 → §21.8 (real); B4 玛恩纳's skill deals no damage → §21.8 (real, on flying waves); B5 薄绿 can pull flying enemies → §21.9 (real). Batch 3 — C1 the hidden leader's "造成50%伤害" broadcast before the fight → §21.10 (real); C2 变形同构体 + class item: the bond member count excludes the wearer → §21.11 (partly: display only). Batch 4 — D1 信仰搅拌机: 源石溶剂's HP drain should trigger S3 → §21.12 (real); D2 野鬃's skill sometimes pushes the wrong way → §21.13 (real); D3 special 源石虫 all drawn as the plain one → §21.14 (partly); D4 暴鸰's bombs do not drop properly → §21.14 (partly); D5 the originium-floor map's lower route should cut diagonally from row 4 to row 3 → §21.15 (real). Batch 5 — E1 烛煌 S3's attack range does not change → §21.16 (partly: the card); E2 medics that block a roaming leader stop healing and attack it → §21.17 (not real); E3 卡涅利安's attack is not a true AoE → §21.18 (real).
Thirty-two reports after the public 0.1.0 release (2026-10-02), relayed by the user in six batches; most were not tested by the user. Verdicts: **real** (fixed), **partly** (the rule was right, what a player sees was wrong or a related part was wrong — fixed), **not real** (not reproduced; tests lock the rule). Where each is handled: batch 1 — #1 拉普兰德's "获得该干员后该回合的首次刷新" should stack 叙拉古 → §21.1 (real); #2 late bounty enemies offered in early bounty drafts → §21.2 (real); #3 the water pool map: operators deployable in the water, 突袭 operators land in it → §21.3 (real); #4 昆图斯's 突变细胞 gone after one use → §21.1 (real: not consumed); #5 转译基底 does not transform → §21.4 (real); #6 凯瑟琳's three upgrade items shown wrong → §21.2 (real); #7 鸭爵's 圆仔 blockable, audit the other 鸭爵 enemies → §21.5 (real); #8 深池逐火战士's 怨恨的余烬 cannot be killed → §21.4 (real); #9 tacticians (伺夜) can place the tactical point outside their attack range → §21.3 (real); #10 bots too dumb, smarter without more CPU → §21.6 (real). Batch 2 — B1 奥术 not working → §21.7 (partly: the bond works, 标准 switches it off); B2 源石溶剂 white + white → gold, then an extra white → §21.7 (not real); B3 维娜·维多利亚 S3 summons one 黄金盟誓 → §21.8 (real); B4 玛恩纳's skill deals no damage → §21.8 (real, on flying waves); B5 薄绿 can pull flying enemies → §21.9 (real). Batch 3 — C1 the hidden leader's "造成50%伤害" broadcast before the fight → §21.10 (real); C2 变形同构体 + class item: the bond member count excludes the wearer → §21.11 (partly: display only). Batch 4 — D1 信仰搅拌机: 源石溶剂's HP drain should trigger S3 → §21.12 (real); D2 野鬃's skill sometimes pushes the wrong way → §21.13 (real); D3 special 源石虫 all drawn as the plain one → §21.14 (partly); D4 暴鸰's bombs do not drop properly → §21.14 (partly); D5 the originium-floor map's lower route should cut diagonally from row 4 to row 3 → §21.15 (real). Batch 5 — E1 烛煌 S3's attack range does not change → §21.16 (partly: the card); E2 medics that block a roaming leader stop healing and attack it → §21.17 (not real); E3 卡涅利安's attack is not a true AoE → §21.18 (real). Batch 6 — F1 坚固维式重锤's lock does not work → §21.21 (partly: the lock works; it was once per battle, now once per deployment as the user settled, and an M3茧甲 equipped first took the first lethal hit); F2 a free 谢拉格 uses up 休露丝's discount → §21.21 (not real); F3 蒂比 still hit after taking off → §21.22 (real); F4 深巡 on a fenced tile does not attack → §21.23 (not real; a fence blocking fault found on the way is fixed); F5 突袭 operators land on a knocked-out operator's body → §21.24 (real). Two hardening fixes are credited to pull requests (PR #2, PR #7) → §21.25.
Eighteen workstreams (WA–WR, each on its own branch from master 7f13473) were merged into `feedback1` in the order WB, WA, WE, WD, WC, WI, WM, WN, WO, WL, WG, WH, WJ, WK, WP, WR, WQ, WF, then WB's follow-up from 66 screenshots of 22 matches (bounty lists, the 机密商店 and 战术决策 with repeats) after QA; data/*.json was rebuilt by `tools/build-data.mjs --offline` after every builder merge, never hand-merged. The integration fixed a bug already in 0.1.0 (a 联防 battle timed out during 隐德来希 S3 reported its 心烛 as leaks, so the server refused an honest client result — candles carry `mem.noLeak`), the last review of the transform workstream (form fx were still dropped by the render engine), every cross-workstream note worth doing with a primary source, and reconciled the bot with the merged rules — §21.19. §21.20 lists the settled decisions and what stays [ASSUMED]. Normative lines rewritten with it: §2 (`grid.js`), §3 (the effective `buildable`, the 深水区, range-bound summons, 攻击距离 vs skill ranges), §5.2 (`liveRangeGrid`, `form`), §5.3 (stealth vs area damage, the 奥术 refresh), §5.4 (`kill` / `death`, `foesInRadius`, `loseHp` vs damage, 静态刚体), §5.5 (healers that block, `rangeAoe`, armed enemies, results), §5.6 (TAKE_DAMAGE, `noRangeExtend` / `showOwnRange`, the profession splash radii), §6.2 (rewardOffer, piece `meta.round`, summons in range, returned equipment), §6.3 (变形同构体), §6.6 (the bot), §7 (bounty drafts, 鸭爵, knock-out forms), §8.2 / §8.3 (fx `form`, `m.ticker`, `range`, UnitInfo `items` / `form`, the stealth bit, rewardOffer), §9 (forms, alias tint), §10 (offers, ticker, 本局禁用), §13 (`spine/enemy`), §14 (ground pathing, boss tickers, the runner's form fx), §16, §17.3, §18.5, §18.6 #10, §19.1, §19.7, §20.1, §20.3, §20.6, §20.9.
Eighteen workstreams (WA–WR, each on its own branch from master 7f13473) were merged into `feedback1` in the order WB, WA, WE, WD, WC, WI, WM, WN, WO, WL, WG, WH, WJ, WK, WP, WR, WQ, WF, then WB's follow-up from 66 screenshots of 22 matches (bounty lists, the 机密商店 and 战术决策 with repeats) after QA, then batch 6's four in the order WS, WU, WT (on WI's branch), WV (on WC's); data/*.json was rebuilt by `tools/build-data.mjs --offline` after every builder merge, never hand-merged. The integration fixed a bug already in 0.1.0 (a 联防 battle timed out during 隐德来希 S3 reported its 心烛 as leaks, so the server refused an honest client result — candles carry `mem.noLeak`), the last review of the transform workstream (form fx were still dropped by the render engine), every cross-workstream note worth doing with a primary source, and reconciled the bot with the merged rules — §21.19. §21.20 lists the settled decisions and what stays [ASSUMED]; batch 6 and the pull-request fixes follow it (§21.21–§21.25). Normative lines rewritten with it: §2 (`grid.js`), §3 (the effective `buildable`, the 深水区, range-bound summons, 攻击距离 vs skill ranges), §5.2 (`liveRangeGrid`, `form`), §5.3 (stealth vs area damage, the 奥术 refresh), §5.4 (`kill` / `death`, `foesInRadius`, `loseHp` vs damage, 静态刚体), §5.5 (healers that block, `rangeAoe`, armed enemies, results), §5.6 (TAKE_DAMAGE, `noRangeExtend` / `showOwnRange`, the profession splash radii), §6.2 (rewardOffer, piece `meta.round`, summons in range, returned equipment), §6.3 (变形同构体), §6.6 (the bot), §7 (bounty drafts, 鸭爵, knock-out forms), §8.2 / §8.3 (fx `form`, `m.ticker`, `range`, UnitInfo `items` / `form`, the stealth bit, rewardOffer), §9 (forms, alias tint), §10 (offers, ticker, 本局禁用), §13 (`spine/enemy`), §14 (ground pathing, boss tickers, the runner's form fx), §16, §17.3, §18.5, §18.6 #10, §19.1, §19.7, §20.1, §20.3, §20.6, §20.9; with batch 6: §3 (fenced tiles), §5.3 (`liftoff`), §5.4 (`fatal`, `ignoreSelect`, `redeploy`), §5.5 (bodies and redeploys, blocking, enemy AI), §6.4 (`onBuy`, the item snapshot), §8.2 (`down` row, col), §18.3, §19.7.
### 21.1 拉普兰德's first refresh; 突变细胞 is not consumed (#1, #4) — `sim/content/garrisons/meta.js SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT`, `match/PlayerState.js pieceRoundCount / bumpPieceRoundCount / _mergeChess / transformChess`, `match/effectsMeta.js ctx.pieceCounter / incPieceCounter / transform`, `match/builtinMeta.js char_chess_transformation_equip`, `match/bot.js cellTarget / makeHandRoom`, `tools/build-data.mjs ITEM_RULES`, `ui/detailPanel.js ItemDetail`
@@ -1333,11 +1334,12 @@ Settled by the user:
|---|---|---|
| A tactician re-oriented in place (§21.3) | a range-bound summon still inside the new range stays; one outside goes back to its stack (user-confirmed 2026-10-03; PRTS only says a MOVED owner resets every summon) | `PlayerState._liftOutOfRange` lifting every range-bound summon of a re-oriented owner |
| Bounty drafts (§21.2) | the official structure of each round from the user's 66 screenshots of 22 matches (R3 fixed sets, R9 boss groups, R11 seen 7-card lists; no multi-round card in a draft) | add a newly seen set / group / list to `tools/build-data.mjs BOUNTY_INITIAL_SETS / BOUNTY_BOSS_GROUPS / BOUNTY_HUNTER_GROUPS` and rebuild |
| 机密商店 / 战术决策 repeats (§21.2) | the same card can be offered twice, as officially (the user: "机密商店按官方改成可以重复吧", "战术决策也按官方改成可以重复吧"); R11 机密商店 = two VI, ≥ 1 V, ≥ 1 盟约之币 | `choices.json shopDraft` / `tacticDraft` removed (choices.js falls back to the previous draws, still with replacement) |
| 机密商店 / 战术决策 repeats (§21.2) | the same card can be offered twice, as officially (the user, 2026-10-03: "机密商店按官方改成可以重复吧", "战术决策也按官方改成可以重复吧"); R11 机密商店 = two VI, ≥ 1 V, ≥ 1 盟约之币 | `choices.json shopDraft` / `tacticDraft` removed (choices.js falls back to the previous draws, still with replacement) |
| 坚固维式重锤's scope (§21.21) | one 不死 lock per DEPLOYMENT: the carrier's own redeploy (after a knock-out, a 突袭 retreat + redeploy) re-arms it and ends a window still running (the user, 2026-10-03: "每次部署一次"; the text says only 首次) | `content/items/battle.js hammerAcquire`: drop its `deploy` handler (one lock per battle) |
| Multi-round bounties (§20.6, unchanged) | last two battles — now only 教鞭's card | `server/match/choices.js MULTI_ROUND_BOUNTY_BATTLES = null` |
| §20.9's decisions | unchanged (start-deploy summons, 凯瑟琳's hand card, every blocker hits, the collider reach, the elite to the board, the fixed leader pool, the watched bond strip) | as §20.9 |
Official since this release (each with its source in its section): 突变细胞 not consumed (§21.1); the 深水区 refuses deployment and a tactical point stays in range (§21.3); the knock-out forms and kill accounting (§21.4); 圆仔 / 鸭爵 unblockable, +300 % speed, swaps in leader rounds, lpr 1 (§21.5); 标准's 本局禁用 bonds (§21.7); 维娜's lions on every free tile, 玛恩纳 S3 可对空 and his per-kill rule (§21.8); 静态刚体 (§21.9); the per-round BOSS_HIT share and the priority strip (§21.10, §21.19); the 变形同构体 member (§21.11); 源石溶剂 as damage, 信仰搅拌机's counters, the BUFF-damage enemies (§21.12); 野鬃's directional push and 琳琅's radial coins (§21.13); 暴鸰's drop (§21.14); diagonal routes (§21.15); 烛煌 S3's splash, 攻击距离 vs 信仰搅拌机 S3 (§21.16); medics heal while blocking (§21.17); 阵法术师 / 轰击术师 strike all in range (§21.18); area damage vs 隐匿, the splash radii, 阿罗玛's shared mark, 奥术法阵 on 炎佑, 重生 cooldowns, the 自行炮, 琳琅's coin range, 阿戈尔's 物理流失, no status on a 无敌 + 无法选中 unit, 余 S2 vs 自缚, 信仰搅拌机 S2 with too few bullets (§21.19); the R11 机密商店 composition and repeats in the 机密商店 and the 战术决策 (§21.2).
Official since this release (each with its source in its section): 突变细胞 not consumed (§21.1); the 深水区 refuses deployment and a tactical point stays in range (§21.3); the knock-out forms and kill accounting (§21.4); 圆仔 / 鸭爵 unblockable, +300 % speed, swaps in leader rounds, lpr 1 (§21.5); 标准's 本局禁用 bonds (§21.7); 维娜's lions on every free tile, 玛恩纳 S3 可对空 and his per-kill rule (§21.8); 静态刚体 (§21.9); the per-round BOSS_HIT share and the priority strip (§21.10, §21.19); the 变形同构体 member (§21.11); 源石溶剂 as damage, 信仰搅拌机's counters, the BUFF-damage enemies (§21.12); 野鬃's directional push and 琳琅's radial coins (§21.13); 暴鸰's drop (§21.14); diagonal routes (§21.15); 烛煌 S3's splash, 攻击距离 vs 信仰搅拌机 S3 (§21.16); medics heal while blocking (§21.17); 阵法术师 / 轰击术师 strike all in range (§21.18); area damage vs 隐匿, the splash radii, 阿罗玛's shared mark, 奥术法阵 on 炎佑, 重生 cooldowns, the 自行炮, 琳琅's coin range, 阿戈尔's 物理流失, no status on a 无敌 + 无法选中 unit, 余 S2 vs 自缚, 信仰搅拌机 S2 with too few bullets (§21.19); the R11 机密商店 composition and repeats in the 机密商店 and the 战术决策 (§21.2); M3茧甲's revive after every 不死 (§21.21); 起飞 = 对地规避 (§21.22); no ground blocking from a fenced tile (§21.23); a knocked-out operator keeps its tile and redeploys there, rule 3 included (§21.24).
Still [ASSUMED] (each in its section):
- §21.1: an elite merged this round keeps its copies' refresh count; a 拉普兰德 re-bought after a sale fires again; a 6阶 突变细胞 carrier becomes another 6阶.
@@ -1357,5 +1359,48 @@ Still [ASSUMED] (each in its section):
- §21.16: the 技能范围 areas on the card, extra content tiles not drawn, the 全场 / 6 × 7 thresholds, the bonus on a dodged hit.
- §21.18: no target cap; damage at the attack instant; the beam look.
- §21.19: the 奥术法阵 rider ending with its carriers; the 自行炮's immunity window = its bombardment.
- §21.21: the 阿戈尔 devour and the carrier's own 源石溶剂 spend the lock; kits' own savers before the item's 不死; M3茧甲 before 埃芒加德; a running window ends with its deployment; a free pick or grant is no 购买; a 谢拉格 already priced ≤ 1 still counts as the round's first.
- §21.22: a ground enemy's shot in flight when 蒂比 takes off lands on nothing; ground-enemy auras still apply; 流失 DoTs keep ticking.
- §21.23: air blocking (blockFly) on a fenced tile stays; the gate reads ground passability.
- §21.24: the body goes home once, at the knock-out, only when its home is free; 初始位置 = the board pieces of every player on the field; 歌利亚头盔 ignores bodies; a withdrawn operator leaves no body.
Not done (noted, no primary source or out of scope): whether 华法琳 S2's 流失 can kill an ally (§21.17); 伊利昂的木驮兽's `Full` skin (§21.14); the render-demo recording `public/dev/recordings/normal-a1m04.json` still shows 战场#04's old L-shaped lower route (display only).
Not done (noted, no primary source or out of scope): whether 华法琳 S2's 流失 can kill an ally (§21.17); 伊利昂的木驮兽's `Full` skin (§21.14); the render-demo recording `public/dev/recordings/normal-a1m04.json` still shows 战场#04's old L-shaped lower route (display only); M3茧甲's "同名装备的效果不可叠加" (two copies give two revives, a + b three) and two own 坚固维式重锤 giving a 16 s lock; the M3茧甲 / 埃芒加德 revive in PRTS's form (退场, then a 0-time / 0-cost redeploy) and a status icon for the 不死 window (§21.21); 深巡 S2's nearest-first order (§21.23); 【盲信之誓】 hurting once per chain (PRTS "多条锁链的伤害不叠加") and the “帝国的甲胄” barrage's timing (§21.22's audit).
### 21.21 坚固维式重锤's lock, once per deployment; 休露丝's discount (F1, F2) — `content/items/battle.js hammerAcquire / PRIO_REVIVE / PRIO_RESPAWN`, `content/bands/battle.js PRIO_BAND_REVIVE`, `content/bands/meta.js` (休露丝), `match/PlayerState.js buy / pickReward`
- **F1 seen**: "维多利亚坚固锤子的锁血没生效". **Official** (act2autochess eff_acarm043 / eff_acgarm043): "首次受到致命伤害时生命值不低于1,持续8秒", `undeadable_duration` 8 (the latest upstream data agrees; PRTS still prints the act1 15 s); 异常效果 不死 "重设常规生命值时不会使其低于1".
- **Measured on 0.1.0, the lock works**: every HP loss of a unit without a boss pool fires `fatal` (hits of every type, element bursts, 无来源 damage, `loseHp` 流失) — on every visible chess against scaled real enemies, in forced-hammer bot matches and in the browser's local battle.
- **Why it looked missing**: it was once per battle — the hammer state lives on the unit, which a redeploy reuses, so a carrier knocked out after its lock came back with none (forced-hammer co-op matches, 3 绝境 + 4 困难: 27 of 382 carrier knock-outs); the 阿戈尔 battle-start devour (a lethal hit on the operator in front: 10 of 179 locks on 绝境) and the carrier's own 源石溶剂 spend it; with an M3茧甲 equipped first, its revive (same priority, registered first) took the first lethal hit.
- **Now**: once per **deployment** — the user's first-hand memory of the official mode (2026-10-03, "每次部署一次"; the text says only 首次 where M3茧甲 says 一场战斗 and 拉特兰桥夹 每次部署): the carrier's own non-initial `deploy` (the redeploy after a knock-out, a 突袭 retreat + redeploy) re-arms the lock and ends a window still running. The `fatal` order is kits' own savers (10 … −60) → items' 不死 `PRIO_REVIVE` −100 → items' 复活 (M3茧甲) `PRIO_RESPAWN` −101 → 埃芒加德 −110: PRTS (M3茧甲 / 埃芒加德 / 阿戈尔 备注) "“复活”的实现方式为:受益者因移动之外的原因退场时下次部署的再部署时间和费用归零" — a revive acts on a knock-out, which a 不死 prevents. No status icon shows the 8-s window (the client has none for 不死; a one-shot fx marks the lock).
- **F2 seen**: a 谢拉格 taken from the promotion reward used up 休露丝's "每回合购买的首名<谢拉格>干员消耗资金为1". **Not real**: only `g.buy` dispatches `onBuy`, where every 购买 counter lives (休露丝, 玛恩纳, 升华, 整备); the promotion reward, 寻呼模块's pick and the 拟态物质 / 紧急调度券 / 简易通讯机 grants never touch it (pinned through real intents; META `onBuy`). Buying the copy that completes a merge uses it, as it should.
- [ASSUMED]: §21.20.
- Tests: `test/content/feedback1f-hammer.test.js` (per deployment, a retreat + redeploy, the devour, the M3茧甲 order), `test/match/feedback1f-sciurus.test.js`.
### 21.22 蒂比's 起飞 is 对地规避 (F3) — `sim/targeting.js evadesGround / canTargetAlly`, `sim/damage.js liftoffEvades` (DamageInfo `ignoreSelect`), `Battle.applyStatus / _blockerFor`, `buffs.js` (`liftoff`), `kits/tier2.js LIFTOFF_FLAGS`, `kits/tier5.js` (隐德来希), `content/enemies.js`, `content/bosses.js`
- **Seen**: after 蒂比 took off, enemies still hit her. **Real**: 起飞 was `unit.ground = false`, which freed her from melee enemies only; ranged ground enemies kept shooting her and their areas and statuses kept landing (46 of 241 enemy types hit her while airborne; the client replica the same).
- **Official**: ba.liftoff "不阻挡地面敌人且不会被地面敌人攻击,可以阻挡飞行敌人"; PRTS 术语释义: 起飞 includes 对地规避, a 无法选择 effect only selectors whose 行动方式 is ground respect, and she keeps a ground 行动方式; PRTS 异常效果: direct picks and abilities that "无视无法选择" are not stopped (these effects act "仅在选择时"); PRTS 作战机制: an AoE judges only selectable units.
- **Now**: 起飞 is the skill flag `liftoff` (S1 and S2, normal and elite). No ground enemy (not `Unit.isFlying`) selects her — normal attacks, ability picks, 周围四格 additions, chains, bounces, shells, the “帝国的甲胄” barrage, 鼠王's 沙狱, 烹泉's blast and steam, ground zones (集团军重型火炮's 燃烧区域 is "不可对空", 迷彩 alone exempted) and their statuses; the pipeline refuses a ground enemy's damage on her before the `hit` hook, so a refused hit neither sets off her S2 nor restarts 片场工作指南. She blocks flyers only; flyers, 近地悬浮 and 浮空 enemies still attack her with no 对空 check; she stays a ground unit (污染秽蚀 at the low-ground 50/s; 隐德来希 S2's 血镰 on her cuts air).
- **Still reaches her** (no selection — DamageInfo / `applyStatus` `ignoreSelect`, or 无来源): the "无视无法选择" abilities (【污染秽蚀】, 【盲信之誓】, 萨卡兹悖谬暴虐兵长's 暴击 splash — her 片场工作指南 may dodge it); 碎铳之簧's counter on its attacker; the 刺胄之弹 / 剑 / 锤 blasts (flying units); the ticks of a debuff a ground enemy put on her before take-off (出血, 沙狱, burning, 淤困, 【自然涌动】 — their mods stay); sourceless damage (毒雾, the chimera aura, 源石溶剂).
- [ASSUMED]: §21.20. Audit: 蒂比 is the mode's only 起飞 / 对地规避 operator; outside F3, ranged enemies still pick invulnerable allies (锏 S3, 泥岩 S3) and enemy splash still hits 隐匿 allies (§19.1).
- Tests: `test/sim/feedback1f-liftoff.test.js` (13), opt-in `test/render/feedback1f-liftoff.browser.test.js` (`RENDER_E2E=1`), `kits_t1t2` / `kits_alt_t2`.
### 21.23 深巡 on a fenced tile; fenced units block no ground enemy (F4) — `sim/Battle.js _blockerFor`
- **Seen**: "干员深巡放在围栏围着的地块里不会主动攻击". **Official**: the fenced tiles are 围墙 (`tile_fence_bound`, "可放置近战单位,敌方无法进入") and 围栏 (`tile_fence`, "不可以通行") — LOW, buildable ALL, passable to flyers only; PRTS 围墙 / 围栏 地形机制 "部署在其中的单位,若当前阻挡类型为'地面阻挡'则无法阻挡敌人"; 深巡 is a 哨戒铁卫 ("能够阻挡三个敌人,可以进行远程攻击", range 2-2); defender skills fire "受到伤害时" (PRTS 卫戍协议/帮助).
- **Not reproduced**: from a fenced tile she attacks every targetable enemy on her range tiles — every deployable fenced tile of the 7 stages that have them, every direction facing walkable ground, normal and elite; the Final Assault halves and 联防; a real Match; the browser's battle (7 attacks, 1977 damage). Targeting reads range tiles only (no path or line-of-sight condition).
- **Likely what players saw** (official): in some directions her 3-tile line faces only fence or forbidden tiles; 阻隔工事 crates hold some fenced tiles from the start (act1 m01: 3 of each 6-tile block; act1 m04, act2 m03); nothing hits her there, so her skill rarely fires; an own-tile melee unit never attacks from a fenced tile.
- **Fixed** (found while checking F4): a push or pull stops an enemy at the fence edge, 0.5 from the fenced unit — inside the 0.7071 ground radius — and 0.1.0 let that unit block it (薄绿 S2 on a fenced tile held the enemies she dragged). `_blockerFor` now refuses ground blocking from a tile ground units cannot pass (on the stages exactly the `b` tiles).
- [ASSUMED]: §21.20. Tests: `test/sim/feedback1f-fence.test.js`; PLAYING §10 tip.
### 21.24 Knocked-out operators keep their tile (F5) — `sim/Battle.js downOn / restTile / _layBody / isReservedTile / redeploy / _checkRedeploys / _downState / snapshot`, `sim/units.js body`, `content/bonds/addon/battle.js` (突袭, 不屈), `content/bonds/core.js` (阿戈尔), kits tier1 / 3 / 4 free-tile helpers, `render/interp.js`, `render/units.js setDown`
- **Seen**: "突袭盟约的干员会移动到濒死干员的尸体上". **Real**: a knocked-out operator left the occupancy map at once and only its home tile stayed reserved — a 突袭 member that fell after its jump lay on its landing tile, which the next jump picked; a timed redeploy came back on its home, where a teammate that had jumped there lay (96 real battles: 6 placements on a body; the shared boss field mirrored: 38).
- **Official** (PRTS 卫戍协议/帮助 §作战阶段 单位部署): "干员退场后…原地留下一个“倒地干员”…满足再部署条件时,移除场上的该倒地干员并自动部署至该位置"; "倒地干员所在地块视为可部署,但所有我方单位在此处的部署行为将被阻止"; "若干员被击倒的位置为其他干员或召唤物的初始位置,则在被击倒后,尝试返回其自身的初始位置". The 突袭 text "再部署至一名地面敌人周围" adds no tile rule.
- **Now**: a knocked-out operator lies on `body` — where it fell, or its own home when it fell on another board piece's home and its home is free (x / y / tile keep where it fell for the 被击倒时 handlers). Every redeploy comes back there (timed, 不屈, 阿戈尔); a withdrawn operator still comes back home. `downOn`: `_deploy` (redeploys, the 突袭 landing, summons), `spawnDevice` and `relocate` refuse the body tile for every ally; `isReservedTile` reports it, so the 突袭 landing, tactical points, summon / device pickers and 维娜's lions skip it. b.snap `down` carries the tile (§8.2) and the client draws the body there; WAIT_TILE matches the `_checkRedeploys` hold-back and is a safeguard. After the fix: 0 placements on a body in 526 jumps. **Superseded**: "later redeploys use the board tile" — an operator moved off its board tile (a 突袭 jump, 乌尔比安's anchor) now redeploys where it fell; research 01 §4.3 carries the PRTS quotes.
- [ASSUMED]: §21.20. Tests: `test/sim/feedback1f-downed-tile.test.js`, `test/render/feedback1f-down-tile.test.js`, opt-in `test/render/feedback1f-down-tile.browser.test.js`; `downelem`, `bonds_addon`, `engine-requests`, `robustness` follow the rule.
### 21.25 Hardening credited to pull requests (PR #2, PR #7) — `match/effectsMeta.js dispatch / stillEquipped`, `tools/fetch-assets.mjs shrinkGuard`, `tools/assets/manifest.mjs droppedEntries`
- **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`).
- **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`).