feedback5: merge W5V — the owner's decisions of 2026-10-06: 望's placed 棋子 take and return her stock (her skills cycle all battle), skill triggers through summon ranges (麦哲伦 S1, 令 S3, 电弧 S2 / S3; summoner.js summonTriggerArea), a melee 突袭 member prefers a tile where it can block (GitHub #148); from the sources: 泥岩 S3 闭锁 releases and stops blocking (PRTS 异常效果 不可阻挡), 忍冬 / 深靛 / 阿 pinned as they are, the 自选 kits' 庇护 copies use holdProtect; DESIGN §24.2's #148 note points to §25; golden: roster-040, boss-boss_2 / 3 / 4 / 6 / 7-pair, hidden-boss_10-pair moved (W5V's own; the sleep-port moves kept)

This commit is contained in:
sganggs committed 2026-10-06 18:33:11 +08:00
commit 73b810ec45
27 files changed
+746 -334

No files matched your search

+1 -1
View File
@@ -2221,7 +2221,7 @@ Reports after the 0.1.3 release. Each was checked against the official data and
- [ASSUMED] (kept from §23.35): the sim's automatic tile searches (突袭 landing, summon / device tiles, `grid.canStand` - [ASSUMED] (kept from §23.35): the sim's automatic tile searches (突袭 landing, summon / device tiles, `grid.canStand`
without `ranged`) still treat every MELEE unit as ground-only; a prep placement on a 高台 is the tile the battle without `ranged`) still treat every MELEE unit as ground-only; a prep placement on a 高台 is the tile the battle
deploys and redeploys on. deploys and redeploys on.
- Note on GitHub #148 (「突袭近战干员部署在高台」, a battle screenshot on the 深水区 map): not changed here. The rule - Note on GitHub #148 (「突袭近战干员部署在高台」, a battle screenshot on the 深水区 map): not changed here (0.2.0: a melee 突袭 member prefers a landing tile where it can block — the owner's decision of 2026-10-06, §25). The rule
above admits no 突袭 operator on a 高台, and the 突袭 landing search is meant to be ground-only (the [ASSUMED] above); above admits no 突袭 operator on a 高台, and the 突袭 landing search is meant to be ground-only (the [ASSUMED] above);
a melee 突袭 member standing on a 高台 after its jump needs its own reproduction. a melee 突袭 member standing on a 高台 after its jump needs its own reproduction.
- **Integrator**: §23.35 (title and **Now**) describes the 2026-10-04 rule; it needs a pointer here ("withdrawn in - **Integrator**: §23.35 (title and **Now**) describes the 2026-10-04 rule; it needs a pointer here ("withdrawn in
+8 -3
View File
@@ -121,7 +121,8 @@ its loadout's `skillIndex` / `moduleId` and no equipment (the copies' items went
Token pieces: `{ kind:'token', tokenId, ownerUid, row, col, dir? }` — the manually deployable summons the player placed Token pieces: `{ kind:'token', tokenId, ownerUid, row, col, dir? }` — the manually deployable summons the player placed
(data `placeable`, PRTS 卫戍协议/帮助 §战斗部署; user playtest #6). A piece marks the tile its summon deploys on: a talent (data `placeable`, PRTS 卫戍协议/帮助 §战斗部署; user playtest #6). A piece marks the tile its summon deploys on: a talent
summon the owner holds from the start (狼群, 海嗣, 流形, 凯瑟琳's 爬行号·防护单元 — facing the operator it shields) deploys summon the owner holds from the start (狼群, 海嗣, 流形, 凯瑟琳's 爬行号·防护单元 — facing the operator it shields) deploys
with the board; a skill's summon (赫默 S2 医疗探机, 巫恋 S2 诅咒娃娃, "获得一个…") deploys once at the battle start, free with the board (望's 棋子 too, each one occupying her stock while it stands and going back into it as it leaves — the
owner's decision of 2026-10-06, kits/ops/op-wang.js); a skill's summon (赫默 S2 医疗探机, 巫恋 S2 诅咒娃娃, "获得一个…") deploys once at the battle start, free
and regardless of the holding (PRTS §作战阶段 "所有手动部署的召唤物,无视所属干员的持有状态…作战开始时立即部署一次"), then and regardless of the holding (PRTS §作战阶段 "所有手动部署的召唤物,无视所属干员的持有状态…作战开始时立即部署一次"), then
waits on its tile and takes the field there each time the skill gives one (stock ≤ 1, after the token's redeploy time waits on its tile and takes the field there each time the skill gives one (stock ≤ 1, after the token's redeploy time
once it left, free; never while its owner is off the field [ASSUMED] — a stocked one deploys as soon as the owner is once it left, free; never while its owner is off the field [ASSUMED] — a stocked one deploys as soon as the owner is
@@ -853,8 +854,12 @@ or guard with a per-unit flag while dealing it. When the guard trips, the logged
(`unit.baseRangeKeys`: its own grid + its permanent rangeExtend — "攻击范围扩大" modules/talents as persist never-expiring (`unit.baseRangeKeys`: its own grid + its permanent rangeExtend — "攻击范围扩大" modules/talents as persist never-expiring
`rangeExtend` buffs; no skill range, no temporary extend, no extra keys) or blocked by the melee unit — or, checked `rangeExtend` buffs; no skill range, no temporary extend, no extra keys) or blocked by the melee unit — or, checked
**every tick**, an enemy (flyers included) inside a content trigger range (`unit.skill.addTriggerRange(fn)`, **every tick**, an enemy (flyers included) inside a content trigger range (`unit.skill.addTriggerRange(fn)`,
`fn(battle, unit)` → list of ally units (their current range) or tile-key arrays; returns an unregister fn; not for `fn(battle, unit)` → list of ally units (their current range), tile-key arrays or `{ keys, profile }` (tile keys with
heal skills: 海嗣 "攻击范围视为自身攻击范围的延伸", 流形); `SKILL_RANGE` — "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其不可选中)时释放技能": any the enemy profile the effect selects by — `canHitFly` false: ground enemies only); returns an unregister fn; not for
heal skills: 海嗣 "攻击范围视为自身攻击范围的延伸", 流形, 谬因 S2's beam, and the areas a skill acts through around its
owner's standing summons — 麦哲伦 S1 (her drones' ranges), 令 S3 (each summon's x-5, ground), 电弧 S2 (赛柯's range,
ground) / S3 (桑特拉's range): the owner's larger-range rule of 2026-10-06, kits/shared/summoner.js
`summonTriggerArea`); `SKILL_RANGE` — "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其不可选中)时释放技能": any
living enemy (stealthed, untargetable, flying included) on `trigger.customRangeGrid` (= the skill's rangeGrid), every living enemy (stealthed, untargetable, flying included) on `trigger.customRangeGrid` (= the skill's rangeGrid), every
tick, no attack needed; `trigger.allies` (+ `hpAtMost`, default 1) — set by a kit, or by the data for an ally row (黍 S3's tick, no attack needed; `trigger.allies` (+ `hpAtMost`, default 1) — set by a kit, or by the data for an ally row (黍 S3's
official `TRY_SEARCH_ALLY_SKILL`, "技能范围内存在可治疗的我方单位时释放技能": tools/build-data.mjs `TRIGGER_ALLY_RULES`, official `TRY_SEARCH_ALLY_SKILL`, "技能范围内存在可治疗的我方单位时释放技能": tools/build-data.mjs `TRIGGER_ALLY_RULES`,
+21 -9
View File
@@ -36,7 +36,9 @@
// next to the most advanced ground enemy it can reach: on a free tile its position may be deployed // next to the most advanced ground enemy it can reach: on a free tile its position may be deployed
// on from which its range covers that enemy (GitHub issue #51 [ASSUMED]: the first of the 8 most // on from which its range covers that enemy (GitHub issue #51 [ASSUMED]: the first of the 8 most
// advanced that has such a tile; none → it stays and the next poll looks again, never a jump that // advanced that has such a tile; none → it stays and the next poll looks again, never a jump that
// hits nothing); grid.canStand: never the 深水区 (player report #3 after 0.1.0, members dropped // hits nothing) — for a melee member that blocks, a tile where its block applies first (ground
// units pass it, an enemy path runs through it: not a 围墙 — the owner's decision of 2026-10-06,
// GitHub #148); grid.canStand: never the 深水区 (player report #3 after 0.1.0, members dropped
// into 战场#08's pool after an enemy wading in it); Battle.isReservedTile: never a tile a knocked-out // into 战场#08's pool after an enemy wading in it); Battle.isReservedTile: never a tile a knocked-out
// operator lies on (player report F5, members landed on a fallen teammate). A real redeployment // operator lies on (player report F5, members landed on a fallen teammate). A real redeployment
// (retreat + free redeploy on the landing tile, full HP, `deploy` fires — 部署时 traits such as // (retreat + free redeploy on the landing tile, full HP, `deploy` fires — 部署时 traits such as
@@ -268,25 +270,35 @@ function raidReach(u) {
* Landing tile [row, col] of a jump to enemy `e`, or null: a tile from which the member's range (`reach`, raidReach) * Landing tile [row, col] of a jump to enemy `e`, or null: a tile from which the member's range (`reach`, raidReach)
* covers the enemy's body (a huge enemy: any tile it occupies — body.js), within RAID_SEARCH tiles (Chebyshev) of the * covers the enemy's body (a huge enemy: any tile it occupies — body.js), within RAID_SEARCH tiles (Chebyshev) of the
* enemy, inside the field rect, that the member's position may be deployed on (grid.canStand: never the 深水区 — * enemy, inside the field rect, that the member's position may be deployed on (grid.canStand: never the 深水区 —
* player report #3 after 0.1.0) and that is free (Battle.isReservedTile: no living unit, no knocked-out operator's * player report #3 after 0.1.0 —, and for a melee member low ground only, never a 高台: GitHub #148) and that is free
* body — player report F5 —, no waiting piece's tile). The nearest first (Chebyshev, then 0.01·Manhattan); last * (Battle.isReservedTile: no living unit, no knocked-out operator's body — player report F5 —, no waiting piece's tile).
* tie-break the offset in the member's facing-RIGHT frame (sim/dir.js localOrder; for a RIGHT-facing unit the plain * A melee member that blocks takes a tile where its block applies first — ground units pass it (not a 围墙 / 围栏 tile,
* tile-key order), so the landing tile turns with its direction. Only the tiles a range offset leads back to from a * where a unit blocks no ground enemy: Battle._blockerFor) and an enemy ground path runs through it
* body tile are looked at, so a search that finds nothing stays cheap. * (Battle.groundPathTiles) or its enemy stands on it — over one where it would block nothing (the owner's decision of
* 2026-10-06 after GitHub
* #148: a member landed on a 围墙 tile, drawn raised, while a road tile covered its enemy too [ASSUMED: the official
* landing picks among the legal tiles at random]); then the nearest (Chebyshev, then 0.01·Manhattan); last tie-break
* the offset in the member's facing-RIGHT frame (sim/dir.js localOrder; for a RIGHT-facing unit the plain tile-key
* order), so the landing tile turns with its direction. Only the tiles a range offset leads back to from a body tile
* are looked at, so a search that finds nothing stays cheap.
*/ */
function raidTile(battle, u, e, reach) { function raidTile(battle, u, e, reach) {
const er = Math.round(e.y), ec = Math.round(e.x); const er = Math.round(e.y), ec = Math.round(e.x);
const ranged = u.def?.position === 'RANGED'; const ranged = u.def?.position === 'RANGED';
let best = null, bd = Infinity, bo = null; const body = bodyKeys(e);
for (const k of bodyKeys(e)) { const path = !ranged && u.s.blockCnt > 0 ? battle.groundPathTiles() : null;
let best = null, bp = 2, bd = Infinity, bo = null;
for (const k of body) {
const br = Math.floor(k / COLS), bc = k - br * COLS; const br = Math.floor(k / COLS), bc = k - br * COLS;
for (let i = 0; i < reach.length; i += 2) { for (let i = 0; i < reach.length; i += 2) {
const r = br - reach[i], c = bc - reach[i + 1], dr = r - er, dc = c - ec; const r = br - reach[i], c = bc - reach[i + 1], dr = r - er, dc = c - ec;
if (Math.abs(dr) > RAID_SEARCH || Math.abs(dc) > RAID_SEARCH) continue; if (Math.abs(dr) > RAID_SEARCH || Math.abs(dc) > RAID_SEARCH) continue;
if (!battle.grid.inRect(r, c) || !battle.grid.canStand(r, c, { ranged }) || battle.isReservedTile(r, c)) continue; if (!battle.grid.inRect(r, c) || !battle.grid.canStand(r, c, { ranged }) || battle.isReservedTile(r, c)) continue;
const t = r * COLS + c;
const p = path && battle.grid.tile(r, c).pass === 'ALL' && (path.has(t) || body.includes(t)) ? 0 : 1; // its block applies
const d = Math.max(Math.abs(dr), Math.abs(dc)) + 0.01 * (Math.abs(dr) + Math.abs(dc)); const d = Math.max(Math.abs(dr), Math.abs(dc)) + 0.01 * (Math.abs(dr) + Math.abs(dc));
const o = localOrder(dr, dc, u.dir); const o = localOrder(dr, dc, u.dir);
if (d < bd - 1e-9 || (Math.abs(d - bd) <= 1e-9 && localBefore(o, bo))) { best = [r, c]; bd = d; bo = o; } if (p < bp || (p === bp && (d < bd - 1e-9 || (Math.abs(d - bd) <= 1e-9 && localBefore(o, bo))))) { best = [r, c]; bp = p; bd = d; bo = o; }
} }
} }
return best; return best;
@@ -11,7 +11,8 @@ export default {
// 柔光缚目: attacks bind the target `duration` s with `prob`; bound enemies are never chosen as targets. // 柔光缚目: attacks bind the target `duration` s with `prob`; bound enemies are never chosen as targets.
// Alternate S1 灯塔守卫者: for `duration` s the skill range (4-1), attack interval ×(1 + base_attack_time) (PRTS: // Alternate S1 灯塔守卫者: for `duration` s the skill range (4-1), attack interval ×(1 + base_attack_time) (PRTS:
// "大幅度缩短(-80%)" ⇒ ×0.2 — a flat −0.8 s on 3 s would be less than S2's "略微缩短 ×0.7"), every attack hits for // "大幅度缩短(-80%)" ⇒ ×0.2 — a flat −0.8 s on 3 s would be less than S2's "略微缩短 ×0.7"), every attack hits for
// attack@atk_scale × ATK arts. Elite module MSC-X (store 4) is the mystic profile of the module trait (TUNE.mystic); // attack@atk_scale × ATK arts — its stored energies too (PRTS S1 备注 "该技能的"每次攻击的攻击倍率"会实时应用在特性积攒的"攻击
// 能量"抛射物上"; the client's ChargeAttackS1 reads the same atk_scale), not the branch's general 100 %. Elite module MSC-X (store 4) is the mystic profile of the module trait (TUNE.mystic);
// the store itself is the shared 秘术师 profile (professions.js installMystic: at the attack check, PRTS 分支特性信息). // the store itself is the shared 秘术师 profile (professions.js installMystic: at the attack check, PRTS 分支特性信息).
chess_char_1_17_a: (bb, chess, def) => { chess_char_1_17_a: (bb, chess, def) => {
const t = talentBb(chess, 0); const t = talentBb(chess, 0);
@@ -20,7 +20,9 @@ export default {
// regen; 精锐 module SOL-X: ATK/DEF + while blocking // regen; 精锐 module SOL-X: ATK/DEF + while blocking
// S1 小施惩戒 (charges): next attack + extra_damage_ratio × ATK arts and +cost DP; S2 坠刃拷问 (charges, the data rule // S1 小施惩戒 (charges): next attack + extra_damage_ratio × ATK arts and +cost DP; S2 坠刃拷问 (charges, the data rule
// SKILL_RANGE: fires once an enemy is inside its own range 3-12, charges 3 s apart): +cost DP, ≤ max_target enemies of that range take atk_scale arts and // SKILL_RANGE: fires once an enemy is inside its own range 3-12, charges 3 s apart): +cost DP, ≤ max_target enemies of that range take atk_scale arts and
// 停顿 — the ones already 停顿 are also stunned. (SOL-Y "首次部署时部署费用-4": the initial deployment is free.) // 停顿 — the ones already 停顿 are also stunned; its targets come from the 3-12 only (PRTS S2 备注 "可对空;无法选中被自身
// 阻挡而没有位于技能范围内的敌人" — an S2 note, not S3's: S3 隐狐之艺 has none, its client selector is the standard
// 「同时攻击阻挡的所有敌人」 one). (SOL-Y "首次部署时部署费用-4": the initial deployment is free.)
chess_char_3_18_a: (bb, chess, def) => { chess_char_3_18_a: (bb, chess, def) => {
const d = defOf(chess, def); const d = defOf(chess, def);
const t0 = talentBb(d, 0), t1 = talentBb(d, 1); const t0 = talentBb(d, 0), t1 = talentBb(d, 1);
@@ -8,10 +8,12 @@ import {
export default withDefaults({ export default withDefaults({
// ===== 泥岩 (unyield) S2 岩崩锤 — next attack: heal 4 %, 190 % phys to all ground enemies around, 30 % stun; talents // ===== 泥岩 (unyield) S2 岩崩锤 — next attack: heal 4 %, 190 % phys to all ground enemies around, 30 % stun; talents
// S1 防御力强化·γ型 (TAKE_DAMAGE); S3 秽壤的血脉 (10 s dormant — cannot act, takes no damage, keeps blocking — // S1 防御力强化·γ型 (TAKE_DAMAGE); S3 秽壤的血脉 (10 s dormant — PRTS 备注 "实际将会进入闭锁状态": 闭锁 = 强制缴械 +
// with enemies around −60 % speed; then ground enemies around stunned 3 s/3.5 s and for the rest of the skill // 无敌 + 不可阻挡 (PRTS 异常效果: SHELTERING; 不可阻挡 BLOCK_FREE "无法阻挡/被阻挡,自动解除阻挡") — she makes no attack,
// BAT −0.3 s, ATK/DEF up, attacks every blocked enemy); module UNY-Y (沃土的愿景): ATK/DEF +8 % with no ally // takes no damage and blocks nobody: the enemies she held walk on (flag noBlock — "keeps blocking" until 0.2.0
// on the 8 tiles around // WV); the 备注's 眩晕 / 冻结 / 沉默 反制 are not modelled — with enemies around −60 % speed; then ground
// enemies around stunned 3 s/3.5 s and for the rest of the skill BAT −0.3 s, ATK/DEF up, attacks every blocked
// enemy); module UNY-Y (沃土的愿景): ATK/DEF +8 % with no ally on the 8 tiles around
chess_char_4_18_a: (bb, chess, def) => { chess_char_4_18_a: (bb, chess, def) => {
const t0 = tbb(def, 0), t1 = tbb(def, 1); const t0 = tbb(def, 0), t1 = tbb(def, 1);
const tb = def.traitBb || {}; const tb = def.traitBb || {};
@@ -25,7 +27,8 @@ export default withDefaults({
attack: { hitAllBlocked: true }, attack: { hitAllBlocked: true },
onStart({ battle, unit }) { onStart({ battle, unit }) {
unit.mem.mudS3 = { awake: false, t: 0 }; unit.mem.mudS3 = { awake: false, t: 0 };
battle.addBuff(unit, { key: 'mudrok:dormant', duration: sleep + 1, flags: { invulnerable: true, disarm: true }, visible: true }); // 闭锁: 强制缴械 + 无敌 + 不可阻挡 (her blocked enemies are released: ai.js lets go of a noBlock blocker)
battle.addBuff(unit, { key: 'mudrok:dormant', duration: sleep + 1, flags: { invulnerable: true, disarm: true, noBlock: true }, visible: true });
battle.fx('stone', { x: unit.x, y: unit.y, id: unit.id }); battle.fx('stone', { x: unit.x, y: unit.y, id: unit.id });
}, },
onTick({ battle, unit, dt }) { onTick({ battle, unit, dt }) {
+3 -1
View File
@@ -43,7 +43,9 @@
// +attack_speed (S3) for the skill's duration (per 阿 on the target: independentCharacterSource). [ASSUMED] the volley // +attack_speed (S3) for the skill's duration (per 阿 on the target: independentCharacterSource). [ASSUMED] the volley
// lands at once (its 0.42 s of shots and flight are not modelled); with no ally in his range the cast does nothing (the // lands at once (its 0.42 s of shots and flight are not modelled); with no ally in his range the cast does nothing (the
// client's sequence stops when its attack finds no target) — his skill still runs and spends its SP: the data's DEFAULT // client's sequence stops when its attack finds no target) — his skill still runs and spends its SP: the data's DEFAULT
// trigger needs an enemy, not an ally (reported as an open question). // trigger needs an enemy, not an ally — settled by the sources in 0.2.0 WV: no skillTriggerDataList row names 阿 or 怪杰
// (the basic strategy), the skill prefabs cast with no target and keep the SP spent (skchr_haak_2 / _3 `_allowNoTarget`
// 1, `_checkHasTargetBeforeDoCast` 0, `_recoverSpIfNoTarget` 0) and PRTS 阿 has no note on it.
import { num, talentBb, moduleBb, moduleOn, skillRec, statBuff, toggleBuff, up } from '../shared/tier1.js'; import { num, talentBb, moduleBb, moduleOn, skillRec, statBuff, toggleBuff, up } from '../shared/tier1.js';
import { toLocal } from '../../../dir.js'; import { toLocal } from '../../../dir.js';
+1 -2
View File
@@ -47,7 +47,7 @@
// invulnerable / untargetable one) and while it stays levitated takes atk_scale × her ATK arts damage per second (the // invulnerable / untargetable one) and while it stays levitated takes atk_scale × her ATK arts damage per second (the
// first after 0.5 s, then every `interval` s; 持续伤害, not dodgeable; 无来源 at her last ATK once she left). // first after 0.5 s, then every `interval` s; 持续伤害, not dodgeable; 无来源 at her last ATK once she left).
import { num, up, traitBb, talentBb, skillRec, batMod, giveSp } from '../shared/tier1.js'; import { num, up, traitBb, talentBb, skillRec, batMod, giveSp, protectMods } from '../shared/tier1.js';
import { absoluteRangeKeys } from '../../../targeting.js'; import { absoluteRangeKeys } from '../../../targeting.js';
import { COLS } from '../../../constants.js'; import { COLS } from '../../../constants.js';
@@ -58,7 +58,6 @@ const S3 = 'skchr_haruka_3';
export const BUBBLE = 'haruka:bubble'; export const BUBBLE = 'haruka:bubble';
/** The S3 levitation damage (haruka_s_3_debuff). */ /** The S3 levitation damage (haruka_s_3_debuff). */
export const FLOAT_DOT = 'haruka:float'; export const FLOAT_DOT = 'haruka:float';
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
/** 浮光泡影: the break delay when the talent carries no `interval`. */ /** 浮光泡影: the break delay when the talent carries no `interval`. */
const BREAK_DELAY = 0.5; const BREAK_DELAY = 0.5;
/** S3's levitation damage: the first tick (haruka_s_3_debuff firstTriggerInterval). */ /** S3's levitation damage: the first tick (haruka_s_3_debuff firstTriggerInterval). */
+2 -5
View File
@@ -47,7 +47,7 @@
// - S3 披荆斩棘 (MANUAL, hurt SP; data DEFAULT, rawRule TAKE_DAMAGE): at once barrier +hp_ratio × max HP (topping T1's, // - S3 披荆斩棘 (MANUAL, hurt SP; data DEFAULT, rawRule TAKE_DAMAGE): at once barrier +hp_ratio × max HP (topping T1's,
// capped), then `duration` s ATK +atk, attack interval +base_attack_time s (a flat +0.9 on 1.6 s), taunt +taunt_level. // capped), then `duration` s ATK +atk, attack interval +base_attack_time s (a flat +0.9 on 1.6 s), taunt +taunt_level.
import { num, traitBb, talentBb, skillRec, toggleBuff, batMod, up, enemiesInGrid } from '../shared/tier1.js'; import { num, traitBb, talentBb, skillRec, toggleBuff, batMod, up, enemiesInGrid, holdProtect, PROTECT } from '../shared/tier1.js';
import { isHpLoss } from '../../../damage.js'; import { isHpLoss } from '../../../damage.js';
const S1 = 'skchr_judge_1'; const S1 = 'skchr_judge_1';
@@ -60,9 +60,6 @@ const X4 = Object.freeze([[1, -1], [1, 0], [1, 1], [0, -1], [0, 0], [0, 1], [-1,
/** judge_s_2[aoe]: firstTriggerInterval 0.9 s, then triggerInterval 1 s. */ /** judge_s_2[aoe]: firstTriggerInterval 0.9 s, then triggerInterval 1 s. */
const S2_FIRST = 0.9; const S2_FIRST = 0.9;
const S2_EVERY = 1; const S2_EVERY = 1;
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest). */
const PROTECT = 'protect';
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {}; const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const tagged = (d, tag) => !!(d && Array.isArray(d.tags) && d.tags.includes(tag)); const tagged = (d, tag) => !!(d && Array.isArray(d.tags) && d.tags.includes(tag));
@@ -79,7 +76,7 @@ export default {
/** S2's 庇护 (damage_resistance[inf] for the skill's time). */ /** S2's 庇护 (damage_resistance[inf] for the skill's time). */
const protect = (battle, unit, skill) => { const protect = (battle, unit, skill) => {
const dr = num(b2.damage_resistance); const dr = num(b2.damage_resistance);
if (dr > 0) battle.applyStrongest(unit, PROTECT, { duration: Math.max(0.05, skill.timeLeft), value: dr, mods: protectMods, source: unit }); holdProtect(battle, unit, dr, Math.max(0.05, skill.timeLeft), unit); // 庇护: the shared effect (同名效果取最高)
}; };
/** judge_t_1's shield_scale: 1, + S2's shield_scale while S2 runs. */ /** judge_t_1's shield_scale: 1, + S2's shield_scale while S2 runs. */
const shieldScale = (unit) => 1 + (unit.skill?.active && unit.skill.id === S2 ? num(b2.shield_scale) : 0); const shieldScale = (unit) => 1 + (unit.skill?.active && unit.skill.id === S2 ? num(b2.shield_scale) : 0);
+11 -3
View File
@@ -42,7 +42,9 @@
// The 备注's cast with no target cannot happen here: the data's DEFAULT casts it on an attack. // The 备注's cast with no target cannot happen here: the data's DEFAULT casts it on an attack.
// - S3 宁作吾 (MANUAL, data DEFAULT, 30 s): she and her summons ATK +atk, DEF +def; every `interval` (0.5) s from the moment // - S3 宁作吾 (MANUAL, data DEFAULT, 30 s): she and her summons ATK +atk, DEF +def; every `interval` (0.5) s from the moment
// it covers a summon, every ground enemy on that summon's x-5 takes atk_scale × 令's ATK arts (溅射: not dodgeable, her // it covers a summon, every ground enemy on that summon's x-5 takes atk_scale × 令's ATK arts (溅射: not dodgeable, her
// damage — PRTS); +cnt held when it ends (not when she leaves). Passive: the 弦惊 merge — at a 弦惊's deployment, another // damage — PRTS); +cnt held when it ends (not when she leaves). Its cast: the data's DEFAULT, and — the owner's
// larger-range rule of 2026-10-06 (its pulses act on each summon's x-5) — also a ground enemy on a standing summon's x-5
// (shared/summoner.js summonTriggerArea, checked every tick). Passive: the 弦惊 merge — at a 弦惊's deployment, another
// basic 弦惊 of hers on its range ⇒ that one turns 高级形态 and the new one is absorbed (withdrawn); else, the new one // basic 弦惊 of hers on its range ⇒ that one turns 高级形态 and the new one is absorbed (withdrawn); else, the new one
// standing on the range of one or more basic ones ⇒ it turns 高级形态 and the latest deployed of them is absorbed. 高级形态 // standing on the range of one or more basic ones ⇒ it turns 高级形态 and the latest deployed of them is absorbed. 高级形态
// (until it leaves the field): its skill's 2.max_hp / 2.atk / 2.def (直接乘算), RES ×(1 + 2.magic_resistance), attack // (until it leaves the field): its skill's 2.max_hp / 2.atk / 2.def (直接乘算), RES ×(1 + 2.magic_resistance), attack
@@ -53,7 +55,7 @@
// stats). Its attributes are in the stats. // stats). Its attributes are in the stats.
import { num, talentBb, traitBb, moduleOn, skillRec, batMod, giveSp, up } from '../shared/tier1.js'; import { num, talentBb, traitBb, moduleOn, skillRec, batMod, giveSp, up } from '../shared/tier1.js';
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js'; import { summonDeck, holdBuff, tokenStat, summonTriggerArea } from '../shared/summoner.js';
import { absoluteRangeKeys } from '../../../targeting.js'; import { absoluteRangeKeys } from '../../../targeting.js';
import { acquireTargets } from '../../../ai.js'; import { acquireTargets } from '../../../ai.js';
import { COLS, PROJECTILE_SPEEDS } from '../../../constants.js'; import { COLS, PROJECTILE_SPEEDS } from '../../../constants.js';
@@ -72,6 +74,8 @@ export const RECYCLE_DELAY = 1.2;
const X5 = Object.freeze([[1, 0], [0, -1], [0, 0], [0, 1], [-1, 0]]); const X5 = Object.freeze([[1, 0], [0, -1], [0, 0], [0, 1], [-1, 0]]);
const TAG_S2 = 'ling:s2'; const TAG_S2 = 'ling:s2';
const TAG_S3 = 'ling:s3'; const TAG_S3 = 'ling:s3';
/** S3's pulses: ground enemies only ("不可对空"). */
const GROUND = Object.freeze({ canHitFly: false });
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {}; const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const skillOn = (u, id) => !!(u && u.skill && u.skill.active && u.skill.id === id); const skillOn = (u, id) => !!(u && u.skill && u.skill.active && u.skill.id === id);
@@ -127,7 +131,7 @@ function soulKit(t, owner, opts) {
if (battle.time + 1e-9 < s.mem.lingAoeAt) return; if (battle.time + 1e-9 < s.mem.lingAoeAt) return;
s.mem.lingAoeAt += iv; s.mem.lingAoeAt += iv;
const keys = absoluteRangeKeys(X5, s.tileR, s.tileC, s.dir, 0); const keys = absoluteRangeKeys(X5, s.tileR, s.tileC, s.dir, 0);
for (const e of battle.enemiesInKeys(keys, s, { canHitFly: false })) { for (const e of battle.enemiesInKeys(keys, s, GROUND)) {
if (!e.alive) continue; if (!e.alive) continue;
battle.dealDamage(owner, e, { amount: owner.s.atk * num(opts.b3.atk_scale), type: 'arts', isSkill: true, isSplash: true, canDodge: false, tags: ['skill', TAG_S3] }); battle.dealDamage(owner, e, { amount: owner.s.atk * num(opts.b3.atk_scale), type: 'arts', isSkill: true, isSplash: true, canDodge: false, tags: ['skill', TAG_S3] });
} }
@@ -241,6 +245,10 @@ export default {
} }, } },
// 随付笺咏醉屠苏 lives in the deck's onLeave above // 随付笺咏醉屠苏 lives in the deck's onLeave above
], ],
install(battle, unit) {
// S3's cast: a ground enemy on a standing summon's x-5 too (the owner's larger-range rule, 2026-10-06)
summonTriggerArea(battle, unit, S3, (t) => ({ keys: absoluteRangeKeys(X5, t.tileR, t.tileC, t.dir, 0), profile: GROUND }));
},
}; };
}, },
}; };
+9 -3
View File
@@ -38,7 +38,9 @@
// - S1 高效制冷模块 (MANUAL, data DEFAULT, 15 s): passive (while S1 is picked and not running) every attack@interval (3) s from // - S1 高效制冷模块 (MANUAL, data DEFAULT, 15 s): passive (while S1 is picked and not running) every attack@interval (3) s from
// her deployment / the skill's end, every enemy (air units too) on her attack range and on each of her standing drones' // her deployment / the skill's end, every enemy (air units too) on her attack range and on each of her standing drones'
// ranges (龙腾.F: x-4) takes 停顿 attack@sluggish s; while it runs, every 3 s from the cast, 束缚 attack@frozen_duration s // ranges (龙腾.F: x-4) takes 停顿 attack@sluggish s; while it runs, every 3 s from the cast, 束缚 attack@frozen_duration s
// instead. It acts while she is stunned or silenced (the buff is neither — isStunnable / isSilenceable 0). // instead. It acts while she is stunned or silenced (the buff is neither — isStunnable / isSilenceable 0). Its cast:
// the data's DEFAULT, and — the owner's larger-range rule of 2026-10-06 (its 束缚 acts through the drones' ranges) —
// also an enemy (air units too) on a standing drone's range (shared/summoner.js summonTriggerArea, checked every tick).
// - S2 激光开采模块 (MANUAL, data DEFAULT, 15 s): she and her drones ASPD +attack_speed; a 龙腾.L strikes every enemy on its tile // - S2 激光开采模块 (MANUAL, data DEFAULT, 15 s): she and her drones ASPD +attack_speed; a 龙腾.L strikes every enemy on its tile
// (群体) while it runs. S3 武装打击模块 (MANUAL, data DEFAULT, 15 s): she and her drones ATK +atk; 龙腾.A's blast radius // (群体) while it runs. S3 武装打击模块 (MANUAL, data DEFAULT, 15 s): she and her drones ATK +atk; 龙腾.A's blast radius
// BLAST_S3 (1.25). Passive parts ("无人机可以部署在近战位 / 远程位"): placement — the piece's position, nothing in battle. // BLAST_S3 (1.25). Passive parts ("无人机可以部署在近战位 / 远程位"): placement — the piece's position, nothing in battle.
@@ -51,10 +53,12 @@
// drones' lower costs (the module variant); stage 2+ T1 "最多同时部署4个", stage 3 "属性更强的" (the module variant's stats). // drones' lower costs (the module variant); stage 2+ T1 "最多同时部署4个", stage 3 "属性更强的" (the module variant's stats).
import { num, talentBb, traitBb, moduleOn, skillRec, up } from '../shared/tier1.js'; import { num, talentBb, traitBb, moduleOn, skillRec, up } from '../shared/tier1.js';
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js'; import { summonDeck, holdBuff, tokenStat, summonTriggerArea } from '../shared/summoner.js';
import { absoluteRangeKeys } from '../../../targeting.js'; import { absoluteRangeKeys } from '../../../targeting.js';
import { COLS } from '../../../constants.js'; import { COLS } from '../../../constants.js';
/** S1's selection on her range and her drones' (air units too). */
const ANY = Object.freeze({ canHitFly: true });
const S1 = 'skchr_mgllan_1'; const S1 = 'skchr_mgllan_1';
const S2 = 'skchr_mgllan_2'; const S2 = 'skchr_mgllan_2';
const S3 = 'skchr_mgllan_3'; const S3 = 'skchr_mgllan_3';
@@ -171,6 +175,8 @@ export default {
install(battle, unit) { install(battle, unit) {
// S1 高效制冷模块: every attack@interval s, 停顿 (passive) / 束缚 (running) on her range and her drones' ranges // S1 高效制冷模块: every attack@interval s, 停顿 (passive) / 束缚 (running) on her range and her drones' ranges
if (picked !== S1) return; if (picked !== S1) return;
// its cast: an enemy on a standing drone's range too (the owner's larger-range rule, 2026-10-06)
summonTriggerArea(battle, unit, S1, (d) => ({ keys: d.rangeKeys, profile: ANY }));
const iv = num(b1['attack@interval'], 3); const iv = num(b1['attack@interval'], 3);
if (!(iv > 0)) return; if (!(iv > 0)) return;
const restart = (c) => { if (c.unit === unit) unit.mem.mgllanPulseAt = battle.time + iv; }; const restart = (c) => { if (c.unit === unit) unit.mem.mgllanPulseAt = battle.time + iv; };
@@ -186,7 +192,7 @@ export default {
if (!(duration > 0)) return; if (!(duration > 0)) return;
const done = new Set(); const done = new Set();
for (const src of [unit, ...(unit.mem.summonDeck?.standing() ?? [])]) { for (const src of [unit, ...(unit.mem.summonDeck?.standing() ?? [])]) {
for (const e of battle.enemiesInKeys(src.rangeKeys, src, { canHitFly: true })) { for (const e of battle.enemiesInKeys(src.rangeKeys, src, ANY)) {
if (done.has(e)) continue; if (done.has(e)) continue;
done.add(e); done.add(e);
battle.applyStatus(e, key, { duration, source: src }); battle.applyStatus(e, key, { duration, source: src });
+3 -6
View File
@@ -71,7 +71,7 @@
// [ASSUMED] (beyond op-ironmn.js's): the 监工专员 loses its 1 HP per pulse with or without an operator in range (dec_blood is a // [ASSUMED] (beyond op-ironmn.js's): the 监工专员 loses its 1 HP per pulse with or without an operator in range (dec_blood is a
// passive buff of the ability); the combined range one step deep; the 小工程师's 1 s heal / SP timer runs from its deployment. // passive buff of the ability); the combined range one step deep; the 小工程师's 1 s heal / SP timer runs from its deployment.
import { num, talentBb, traitBb, skillRec, up, giveSp } from '../shared/tier1.js'; import { num, talentBb, traitBb, skillRec, up, giveSp, holdProtect } from '../shared/tier1.js';
import { absoluteRangeKeys } from '../../../targeting.js'; import { absoluteRangeKeys } from '../../../targeting.js';
import { COLS } from '../../../constants.js'; import { COLS } from '../../../constants.js';
import { frontOf } from '../../../dir.js'; import { frontOf } from '../../../dir.js';
@@ -86,9 +86,6 @@ export const NSTBLD = 'token_10061_nasti_nstbld';
const DEVICES = new Set([NSTDEF, NSTCHR, NSTBLD]); const DEVICES = new Set([NSTDEF, NSTCHR, NSTBLD]);
/** A device's 1-1 when its def carries none. */ /** A device's 1-1 when its def carries none. */
const FRONT1 = Object.freeze([[0, 0], [0, 1]]); const FRONT1 = Object.freeze([[0, 0], [0, 1]]);
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest; op-judge.js, op-zumama.js). */
const PROTECT = 'protect';
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
/** 注意安全's 庇护 refresh: a little longer than the check period, so it never lapses while it holds. */ /** 注意安全's 庇护 refresh: a little longer than the check period, so it never lapses while it holds. */
const T2_PERIOD = 0.25; const T2_PERIOD = 0.25;
const PROTECT_HOLD = T2_PERIOD + 0.05; const PROTECT_HOLD = T2_PERIOD + 0.05;
@@ -402,13 +399,13 @@ export default {
if (!up(unit)) { triggered = false; timers.clear(); return; } if (!up(unit)) { triggered = false; timers.clear(); return; }
if (!triggered) triggered = battle.enemies.some((e) => e.alive && !e.hidden && RANGED_WAYS.has(String(e.def?.applyWay ?? '').toUpperCase())); if (!triggered) triggered = battle.enemies.some((e) => e.alive && !e.hidden && RANGED_WAYS.has(String(e.def?.applyWay ?? '').toUpperCase()));
if (!triggered) { if (!triggered) {
if (resBase > 0) battle.applyStrongest(unit, PROTECT, { duration: PROTECT_HOLD, value: resBase, mods: protectMods, source: unit }); holdProtect(battle, unit, resBase, PROTECT_HOLD, unit); // 庇护: the shared effect (同名效果取最高)
return; return;
} }
const who = [unit, ...battle.allyUnits.filter((a) => a !== unit && up(a) && !a.hidden && a.kind === 'op' && RANGED_POS.has(String(a.def?.position ?? '').toUpperCase()))]; const who = [unit, ...battle.allyUnits.filter((a) => a !== unit && up(a) && !a.hidden && a.kind === 'op' && RANGED_POS.has(String(a.def?.position ?? '').toUpperCase()))];
for (const a of [...timers.keys()]) if (!who.includes(a)) timers.delete(a); for (const a of [...timers.keys()]) if (!who.includes(a)) timers.delete(a);
for (const a of who) { for (const a of who) {
if (resPlus > 0) battle.applyStrongest(a, PROTECT, { duration: PROTECT_HOLD, value: resPlus, mods: protectMods, source: unit }); holdProtect(battle, a, resPlus, PROTECT_HOLD, unit);
if (!(spEvery > 0)) continue; if (!(spEvery > 0)) continue;
if (!timers.has(a)) { timers.set(a, battle.time + spEvery); continue; } if (!timers.has(a)) { timers.set(a, battle.time + spEvery); continue; }
if (battle.time + 1e-9 < timers.get(a)) continue; if (battle.time + 1e-9 < timers.get(a)) continue;
+12 -2
View File
@@ -43,10 +43,14 @@
// - S2 环形鳞地 (MANUAL, data DEFAULT, 25 s): +cnt held; she and her summons ATK +atk (base_attack_time 0: no change); 赛柯's // - S2 环形鳞地 (MANUAL, data DEFAULT, 25 s): +cnt held; she and her summons ATK +atk (base_attack_time 0: no change); 赛柯's
// attacks fire BULLET_OFFSETS (three bullets: its centre, 0.2 tile left-up and right-down — fixed, PRTS) that fly its token // attacks fire BULLET_OFFSETS (three bullets: its centre, 0.2 tile left-up and right-down — fixed, PRTS) that fly its token
// skill's attack@projectile_life_time s (0.6 at rank 4, 0.8 at rank 7: "子弹飞行距离+1"); its 3-2 range is unchanged at ranks // skill's attack@projectile_life_time s (0.6 at rank 4, 0.8 at rank 7: "子弹飞行距离+1"); its 3-2 range is unchanged at ranks
// 4 / 7 (PRTS). Passive "会持续向前方发射子弹": 赛柯's data. // 4 / 7 (PRTS). Passive "会持续向前方发射子弹": 赛柯's data. Its cast: the data's DEFAULT, and — the owner's larger-range
// rule of 2026-10-06 (it acts through 赛柯's shots) — also a ground enemy on a standing summon's range (its 3-2: the
// bullets reach no air unit; shared/summoner.js summonTriggerArea, checked every tick).
// - S3 手牵手 (MANUAL, data DEFAULT, 30 s): +cnt held; she and her summons ATK +atk; before each damage of her attacks and of // - S3 手牵手 (MANUAL, data DEFAULT, 30 s): +cnt held; she and her summons ATK +atk; before each damage of her attacks and of
// any damage of her summons on an enemy: 停顿 `sluggish` s and 法术脆弱 (artsFragile damage_scale − 1, "同名效果取最高") for // any damage of her summons on an enemy: 停顿 `sluggish` s and 法术脆弱 (artsFragile damage_scale − 1, "同名效果取最高") for
// weak[magic][limit] s (a dodged hit still carries them [ASSUMED]). Passive "召唤物可部署在远程位…协同攻击": 桑特拉 (above). // weak[magic][limit] s (a dodged hit still carries them [ASSUMED]). Passive "召唤物可部署在远程位…协同攻击": 桑特拉 (above).
// Its cast: the data's DEFAULT, and — the owner's larger-range rule of 2026-10-06 (it acts through her summons' damage)
// — also an enemy (air units too: S3 备注 "召唤物可对空") on a standing summon's range (summonTriggerArea).
// - Modules SO-A 电弧特勤证章 / SO-B 新起点: their trait and talent parts act "在【岁的界园志异】中" only (the client gates them to // - Modules SO-A 电弧特勤证章 / SO-B 新起点: their trait and talent parts act "在【岁的界园志异】中" only (the client gates them to
// the 集成战略 theme rogue_yan / validInGameTag roguelike): N/A here — the module talents the composed record carries // the 集成战略 theme rogue_yan / validInGameTag roguelike): N/A here — the module talents the composed record carries
// (SO-A's hidden respawn_time / prob / sp, SO-B's attack@max_target and x-5 recall) are ignored; their attributes are in the // (SO-A's hidden respawn_time / prob / sp, SO-B's attack@max_target and x-5 recall) are ignored; their attributes are in the
@@ -54,7 +58,7 @@
// its tests. // its tests.
import { num, talentBb, skillRec, up } from '../shared/tier1.js'; import { num, talentBb, skillRec, up } from '../shared/tier1.js';
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js'; import { summonDeck, holdBuff, tokenStat, summonTriggerArea } from '../shared/summoner.js';
import { acquireTargets } from '../../../ai.js'; import { acquireTargets } from '../../../ai.js';
import { aggregateMods } from '../../../buffs.js'; import { aggregateMods } from '../../../buffs.js';
import { dirVec } from '../../../dir.js'; import { dirVec } from '../../../dir.js';
@@ -79,6 +83,9 @@ const BULLET_OFFSETS = Object.freeze([[0, 0], [-0.2, 0.2], [0.2, -0.2]]);
const LINK_SPEED = 50; const LINK_SPEED = 50;
const SHIELD_TYPES = Object.freeze(['phys', 'arts', 'true']); const SHIELD_TYPES = Object.freeze(['phys', 'arts', 'true']);
const KEY_BARRIER = 'radian:s1:barrier'; const KEY_BARRIER = 'radian:s1:barrier';
/** The summons' areas a skill casts on (summonTriggerArea): 赛柯's bullets hit ground enemies only, S3's summons air too. */
const GROUND = Object.freeze({ canHitFly: false });
const ANY = Object.freeze({ canHitFly: true });
const TAG_BULLET = 'radian:bullet'; const TAG_BULLET = 'radian:bullet';
const TAG_LINK = 'radian:link'; const TAG_LINK = 'radian:link';
@@ -268,6 +275,9 @@ export default {
// 加油~ lives in the deck's install above (its 鼓舞 at each deployment and every second) // 加油~ lives in the deck's install above (its 鼓舞 at each deployment and every second)
], ],
install(battle, unit) { install(battle, unit) {
// S2 / S3's cast: an enemy on a standing summon's range too (the owner's larger-range rule, 2026-10-06)
summonTriggerArea(battle, unit, S2, (t) => ({ keys: t.rangeKeys, profile: GROUND }));
summonTriggerArea(battle, unit, S3, (t) => ({ keys: t.rangeKeys, profile: ANY }));
// S3 手牵手: before each damage of her attacks / of her summons' damage on an enemy, 停顿 + 法术脆弱 // S3 手牵手: before each damage of her attacks / of her summons' damage on an enemy, 停顿 + 法术脆弱
if (picked !== S3) return; if (picked !== S3) return;
const slug = num(b3.sluggish), dur = num(b3['weak[magic][limit]']), frag = num(b3.damage_scale, 1) - 1; const slug = num(b3.sluggish), dur = num(b3['weak[magic][limit]']), frag = num(b3.damage_scale, 1) - 1;
+2 -5
View File
@@ -54,7 +54,7 @@
// 单位时释放技能"), which tools/build-data.mjs (TRIGGER_ALLY_RULES) writes as the engine's SKILL_RANGE + `allies` on the // 单位时释放技能"), which tools/build-data.mjs (TRIGGER_ALLY_RULES) writes as the engine's SKILL_RANGE + `allies` on the
// skill range x-2 (an injured, healable allied unit there, herself included; 古米 S1's option). // skill range x-2 (an injured, healable allied unit there, herself included; 古米 S1's option).
import { num, traitBb, skillRec, batMod, up, giveSp } from '../shared/tier1.js'; import { num, traitBb, skillRec, batMod, up, giveSp, holdProtect } from '../shared/tier1.js';
import { COLS } from '../../../constants.js'; import { COLS } from '../../../constants.js';
import { effectiveProfile, acquireTargets, performAttack } from '../../../ai.js'; import { effectiveProfile, acquireTargets, performAttack } from '../../../ai.js';
@@ -80,9 +80,6 @@ const PROF_NEEDED = 3;
const SUI_NEEDED = 4; const SUI_NEEDED = 4;
/** 【岁】: character_table groupId 'sui' (data/backups.json diy.operators powers carry it for the 自选 picks). */ /** 【岁】: character_table groupId 'sui' (data/backups.json diy.operators powers carry it for the 自选 picks). */
const SUI = new Set(['char_2014_nian', 'char_2015_dusk', 'char_2023_ling', 'char_2024_chyue', 'char_2025_shu', 'char_2026_yu', 'char_2027_wang']); const SUI = new Set(['char_2014_nian', 'char_2015_dusk', 'char_2023_ling', 'char_2024_chyue', 'char_2025_shu', 'char_2026_yu', 'char_2027_wang']);
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest). */
const PROTECT = 'protect';
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
const FARM = 'talent:shu:farm'; const FARM = 'talent:shu:farm';
const HARVEST = 'skill:shu:harvest'; const HARVEST = 'skill:shu:harvest';
@@ -122,7 +119,7 @@ export default {
if (hp > 0 && !(cur && cur.source !== unit && (cur.data?.v ?? 0) > hp && cur.timeLeft > 0.05)) { if (hp > 0 && !(cur && cur.source !== unit && (cur.data?.v ?? 0) > hp && cur.timeLeft > 0.05)) {
battle.addBuff(a, { key: FARM, duration: FARM_HOLD, mods: { hpRegen: hp }, source: unit, data: { v: hp }, tags: ['talent'] }); battle.addBuff(a, { key: FARM, duration: FARM_HOLD, mods: { hpRegen: hp }, source: unit, data: { v: hp }, tags: ['talent'] });
} }
if (dr > 0) battle.applyStrongest(a, PROTECT, { duration: FARM_HOLD, value: dr, mods: protectMods, source: unit }); holdProtect(battle, a, dr, FARM_HOLD, unit); // 庇护: the shared effect (同名效果取最高)
} }
}; };
/** Sow the tiles of `grid` around (r, c) — only tiles that can be deployed on or walked. Returns the new ones. */ /** Sow the tiles of `grid` around (r, c) — only tiles that can be deployed on or walked. Returns the new ones. */
+3 -6
View File
@@ -59,7 +59,7 @@
// held like the hammer's (PRIO_UNDYING_HELD −99). // held like the hammer's (PRIO_UNDYING_HELD −99).
// Statuses: none applied to enemies. Damage: arts normal attacks only. // Statuses: none applied to enemies. Damage: arts normal attacks only.
import { num, up, traitBb, moduleBb, talentBb, skillRec, once } from '../shared/tier1.js'; import { num, up, traitBb, moduleBb, talentBb, skillRec, once, holdProtect } from '../shared/tier1.js';
import { releaseSkillSummon } from '../../tokens.js'; import { releaseSkillSummon } from '../../tokens.js';
import { holdsUndying, PRIO_UNDYING_HELD, PRIO_REVIVE, PRIO_RESPAWN } from '../../items/battle.js'; import { holdsUndying, PRIO_UNDYING_HELD, PRIO_REVIVE, PRIO_RESPAWN } from '../../items/battle.js';
import { absoluteRangeKeys } from '../../../targeting.js'; import { absoluteRangeKeys } from '../../../targeting.js';
@@ -69,9 +69,6 @@ const S1 = 'skchr_slent2_1';
const S2 = 'skchr_slent2_2'; const S2 = 'skchr_slent2_2';
const S3 = 'skchr_slent2_3'; const S3 = 'skchr_slent2_3';
export const LAMP = 'token_10029_slent2_protrb'; export const LAMP = 'token_10029_slent2_protrb';
/** 庇护 (ba.protect, the client's damage_resistance[inf]): one "同名效果取最高" effect per unit (battle.applyStrongest). */
export const PROTECT = 'protect';
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
/** The client's trigger period of slent2_t_1 / slent2_t_2 (talent abilities, triggerInterval 0.1). */ /** The client's trigger period of slent2_t_1 / slent2_t_2 (talent abilities, triggerInterval 0.1). */
export const TALENT_IV = 0.1; export const TALENT_IV = 0.1;
/** Lifetime of a refreshed effect: a little longer than the refresh period, so it never lapses while it holds. */ /** Lifetime of a refreshed effect: a little longer than the refresh period, so it never lapses while it holds. */
@@ -136,7 +133,7 @@ function lampKit(t, owner, t1) {
const keys = new Set(absoluteRangeKeys(grid, lamp.tileR, lamp.tileC, lamp.dir, 0)); const keys = new Set(absoluteRangeKeys(grid, lamp.tileR, lamp.tileC, lamp.dir, 0));
for (const a of alliesOn(battle, lamp, keys)) { for (const a of alliesOn(battle, lamp, keys)) {
const v = shelterOf(tb, a, scale); const v = shelterOf(tb, a, scale);
if (v > 0) battle.applyStrongest(a, PROTECT, { duration: HOLD, value: v, mods: protectMods, source: lamp }); holdProtect(battle, a, v, HOLD, lamp); // 庇护 (the client's damage_resistance[inf]): the shared effect
} }
}, { owner: lamp, immediate: true }); }, { owner: lamp, immediate: true });
}, },
@@ -194,7 +191,7 @@ export default {
const now = new Set(allies); const now = new Set(allies);
for (const a of allies) { for (const a of allies) {
const v = shelterOf(t1, a, scale); const v = shelterOf(t1, a, scale);
if (v > 0) battle.applyStrongest(a, PROTECT, { duration: HOLD, value: v, mods: protectMods, source: unit }); holdProtect(battle, a, v, HOLD, unit);
const mult = isRhine(a) ? 2 : 1; const mult = isRhine(a) ? 2 : 1;
if (maxHp > 0) battle.applyStrongest(a, MAXHP_KEY, { duration: HOLD, value: maxHp * mult, mods: (x) => ({ hpPct: x }), source: unit }); if (maxHp > 0) battle.applyStrongest(a, MAXHP_KEY, { duration: HOLD, value: maxHp * mult, mods: (x) => ({ hpPct: x }), source: unit });
if (regenRatio > 0 && a.hpRatio < regenBelow - 1e-9) { if (regenRatio > 0 && a.hpRatio < regenBelow - 1e-9) {
+16 -5
View File
@@ -22,12 +22,19 @@
// may). Module TRP-X "部署费用更低(-1)…可同时部署的陷阱数量提升(+1)": the 棋子 variant's cost 2 (the DP of its returns) and // may). Module TRP-X "部署费用更低(-1)…可同时部署的陷阱数量提升(+1)": the 棋子 variant's cost 2 (the DP of its returns) and
// deploy limit 7 (the hand count — the match's); its attributes in the stats. // deploy limit 7 (the hand count — the match's); its attributes in the stats.
// - T1 铸子 (cnt / attack@max_spawn_cnt; "最多拥有7枚"): the 棋子 are hand pieces the player places; they deploy with the board // - T1 铸子 (cnt / attack@max_spawn_cnt; "最多拥有7枚"): the 棋子 are hand pieces the player places; they deploy with the board
// for free and without using her stock (stock = cnt at the start, at most STOCK_CAP; the skills add to it). 棋子 / 跟子 // for free. Her stock (持有库存: the 棋子 in her hand — cnt at the start, at most STOCK_CAP; the skills add to it) counts
// them — the owner's decision of 2026-10-06, a deliberate deviation from PRTS 卫戍协议/帮助 (placed summons "无视所属干员的
// 持有状态,不消耗持有数量", which left her stock at cnt: one S1 / S2 cast reached the cap and 阻回 held her SP for the rest
// of the battle): every deployment of a 棋子 (the battle start's and each return) takes one from it, like one she deployed
// herself (never below 0 [ASSUMED: a 7th placed piece of TRP-X's deploy limit takes none]), and a 棋子 that leaves the
// field (set off, or gone with her) goes back into it at once by the official summon rule — its card back in her hand
// while its redeploy time runs (as shared/summoner.js's recall) —, at most the cap [ASSUMED: one over the cap is lost].
// So "立即获得两枚棋子" and the 阻回 at the cap keep cycling through the battle. 棋子 / 跟子
// that touch on a side (上下 / 左右) activate each other and stay active once activated [ASSUMED from the S2 备注]; an // that touch on a side (上下 / 左右) activate each other and stay active once activated [ASSUMED from the S2 备注]; an
// enemy (air units too) on an active one's tile sets it off: its effect, then it is used up (a 棋子 leaves the field; a // enemy (air units too) on an active one's tile sets it off: its effect, then it is used up (a 棋子 leaves the field; a
// 跟子 is gone) [ASSUMED: a trap is spent by its trigger]. A spent 棋子 comes back on its tile ("原地再部署") after its // 跟子 is gone) [ASSUMED: a trap is spent by its trigger]. A spent 棋子 comes back on its tile ("原地再部署") after its
// redeploy time (2 s), paying its cost, while 望 is on the field — its own card returns to the stock as it leaves, so the // redeploy time (2 s), paying its cost and one of her stock, while 望 is on the field. That return is the mode's
// stock does not change [ASSUMED]. That return is the mode's deployment of her card: it counts as a 手动部署 for 铸子 — // deployment of her card: it counts as a 手动部署 for 铸子 —
// one 跟子 on a tile next to it (the PRTS order above), at most attack@max_spawn_cnt on the field [ASSUMED: the battle-start // one 跟子 on a tile next to it (the PRTS order above), at most attack@max_spawn_cnt on the field [ASSUMED: the battle-start
// deployment does not count]. When 望 leaves the field her 棋子 and 跟子 vanish (PRTS 分支特性信息); the 棋子 come back // deployment does not count]. When 望 leaves the field her 棋子 and 跟子 vanish (PRTS 分支特性信息); the 棋子 come back
// with her. // with her.
@@ -195,7 +202,7 @@ export default {
const at = battle.time + Math.max(0, num(stone.base.respawnTime)); const at = battle.time + Math.max(0, num(stone.base.respawnTime));
battle.every(RETRY, (b, sched) => { battle.every(RETRY, (b, sched) => {
if (stone.alive || stone.removed || b.finished) { sched.cancel(); return; } if (stone.alive || stone.removed || b.finished) { sched.cancel(); return; }
if (b.time + 1e-9 < at || !up(unit)) return; if (b.time + 1e-9 < at || !up(unit) || unit.mem.wang.stock < 1) return; // (one of her stock: the deployment takes it)
const r = stone.homeR, c = stone.homeC; const r = stone.homeR, c = stone.homeC;
const s3on = unit.skill?.id === S3 && unit.skill.active && inHerRange(unit, r, c); const s3on = unit.skill?.id === S3 && unit.skill.active && inHerRange(unit, r, c);
if (groundFoeOn(b, r, c) && !s3on) return; // "陷阱无法放置于敌人已在的格子中" if (groundFoeOn(b, r, c) && !s3on) return; // "陷阱无法放置于敌人已在的格子中"
@@ -219,12 +226,16 @@ export default {
battle.addBuff(unit, { key: 'trait:wangStone', flags: { untargetable: true }, persist: true, allowDead: true }); battle.addBuff(unit, { key: 'trait:wangStone', flags: { untargetable: true }, persist: true, allowDead: true });
battle.on('deploy', (ctx) => { battle.on('deploy', (ctx) => {
if (ctx.unit !== unit) return; if (ctx.unit !== unit) return;
unit.mem.wangNode = { seq: ++owner.mem.wang.seq, act: false, h: false, v: false, stacks: 0 }; const w = owner.mem.wang;
unit.mem.wangNode = { seq: ++w.seq, act: false, h: false, v: false, stacks: 0 };
w.stock = Math.max(0, w.stock - 1); // it occupies her stock (the owner's decision of 2026-10-06)
}, { owner: unit }); }, { owner: unit });
battle.on('death', (ctx) => { battle.on('death', (ctx) => {
if (ctx.unit !== unit || battle.finished) return; if (ctx.unit !== unit || battle.finished) return;
unit.removed = false; // the piece is kept (Battle.redeploy accepts it; its hooks stay) unit.removed = false; // the piece is kept (Battle.redeploy accepts it; its hooks stay)
unit.mem.wangNode = null; unit.mem.wangNode = null;
const w = owner.mem.wang;
w.stock = Math.min(stockCap, w.stock + 1); // its card goes back into her stock (the official summon rule)
scheduleReturn(battle, owner, unit); scheduleReturn(battle, owner, unit);
}, { owner: unit, priority: -10 }); }, { owner: unit, priority: -10 });
}, },
+2 -5
View File
@@ -42,7 +42,7 @@
// she is stunned `stun` s (zumama_s_3 ON_BUFF_FINISH 'stun'; a stunned operator blocks nobody). // she is stunned `stun` s (zumama_s_3 ON_BUFF_FINISH 'stun'; a stunned operator blocks nobody).
// - Melee physical, ground-only (data canHitFly false), block 1, range 1-1; ground enemies target her (no 起飞 / 迷彩). // - Melee physical, ground-only (data canHitFly false), block 1, range 1-1; ground enemies target her (no 起飞 / 迷彩).
import { num, traitBb, skillRec, toggleBuff, batMod, up } from '../shared/tier1.js'; import { num, traitBb, skillRec, toggleBuff, batMod, up, holdProtect } from '../shared/tier1.js';
const S1 = 'skchr_zumama_1'; const S1 = 'skchr_zumama_1';
const S2 = 'skchr_zumama_2'; const S2 = 'skchr_zumama_2';
@@ -51,9 +51,6 @@ const S3 = 'skchr_zumama_3';
const T1_PERIOD = 0.1; const T1_PERIOD = 0.1;
/** 庇护 refresh: a little longer than the check period, so it never lapses while it holds. */ /** 庇护 refresh: a little longer than the check period, so it never lapses while it holds. */
const PROTECT_HOLD = T1_PERIOD + 0.05; const PROTECT_HOLD = T1_PERIOD + 0.05;
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest). */
const PROTECT = 'protect';
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
/** S2's blockee stun, renewed every tick while it holds (three ticks long). */ /** S2's blockee stun, renewed every tick while it holds (three ticks long). */
const STUN_PULSE = 0.1; const STUN_PULSE = 0.1;
const VALOR = 'talent:zumama:valor'; const VALOR = 'talent:zumama:valor';
@@ -101,7 +98,7 @@ export default {
const has = unit.findBuff(VALOR); const has = unit.findBuff(VALOR);
if (high && !has && scale !== 1) battle.addBuff(unit, { key: VALOR, mods: { atkScaleMul: scale }, tags: ['talent'] }); if (high && !has && scale !== 1) battle.addBuff(unit, { key: VALOR, mods: { atkScaleMul: scale }, tags: ['talent'] });
else if (!high && has) battle.removeBuff(unit, VALOR); else if (!high && has) battle.removeBuff(unit, VALOR);
if (up(unit) && !high && dr > 0) battle.applyStrongest(unit, PROTECT, { duration: PROTECT_HOLD, value: dr, mods: protectMods, source: unit }); if (up(unit) && !high) holdProtect(battle, unit, dr, PROTECT_HOLD, unit); // 庇护: the shared effect
}; };
battle.every(T1_PERIOD, check, { owner: unit, immediate: true }); battle.every(T1_PERIOD, check, { owner: unit, immediate: true });
battle.on('deploy', (ctx) => { if (ctx.unit === unit) check(); }, { owner: unit }); battle.on('deploy', (ctx) => { if (ctx.unit === unit) check(); }, { owner: unit });
+31 -1
View File
@@ -1,6 +1,7 @@
// server/sim/content/kits/shared/summoner.js — the 召唤师 (summoner) branch in 卫戍: the summon deck the 自选 kits of 麦哲伦 // server/sim/content/kits/shared/summoner.js — the 召唤师 (summoner) branch in 卫戍: the summon deck the 自选 kits of 麦哲伦
// (ops/op-mgllan.js), 令 (ops/op-ling.js) and 电弧 (ops/op-radian.js) share — the holding (持有), the return of a placed piece // (ops/op-mgllan.js), 令 (ops/op-ling.js) and 电弧 (ops/op-radian.js) share — the holding (持有), the return of a placed piece
// that left the field, recalls (回收) and the summons vanishing with their owner. Kit contract and rules: ../README.md. // that left the field, recalls (回收), the summons vanishing with their owner, and the skills that cast on an enemy in a
// summon's area (summonTriggerArea: the owner's larger-range rule, 2026-10-06). Kit contract and rules: ../README.md.
// //
// Sources: the client's battle data (charpack char_248_mgllan / char_2023_ling / char_4195_radian: Talents/1 // Sources: the client's battle data (charpack char_248_mgllan / char_2023_ling / char_4195_radian: Talents/1
// charge_token[born] — RechargeToken `cnt` at every deployment of the owner; CommonAbilities die_to_kill_token — KillTokens // charge_token[born] — RechargeToken `cnt` at every deployment of the owner; CommonAbilities die_to_kill_token — KillTokens
@@ -42,6 +43,35 @@ export function holdBuff(battle, u, key, want, mods, extra = null) {
battle.addBuff(u, { key, mods, tags: ['skill'], ...extra }); battle.addBuff(u, { key, mods, tags: ['skill'], ...extra });
} }
/**
* The owner's larger-range rule through summons (the owner's decision of 2026-10-06; ACTIVE_RANGE, server/sim/skills.js:
* "a skill whose effect reaches farther than the operator's own range casts when an enemy is inside the larger area"):
* when `owner`'s picked skill is `skillId`, it also casts while an enemy is inside the area it acts through around one of
* the owner's summons standing on the field — on top of its data rule (DEFAULT: an enemy in the owner's own range, about
* to attack). A content trigger range (SkillRuntime.addTriggerRange), checked every tick: `area(piece)` gives that
* summon's area as `{ keys, profile }` (absolute tile keys; the enemy profile the effect selects by — `canHitFly` false
* when it cannot reach air units) or null. Used by 麦哲伦 S1 (her drones' ranges), 令 S3 (each summon's x-5) and 电弧 S2 /
* S3 (赛柯's / 桑特拉's ranges). Returns the unregister fn, or null when the skill is another one.
* @param {object} battle
* @param {object} owner the summoner unit (its deck: owner.mem.summonDeck)
* @param {string} skillId
* @param {(piece: object) => ({ keys: number[], profile?: object } | null)} area
*/
export function summonTriggerArea(battle, owner, skillId, area) {
const sk = owner?.skill;
if (!sk || sk.id !== skillId) return null;
return sk.addTriggerRange(() => {
const deck = owner.mem.summonDeck;
if (!deck || !up(owner) || battle.finished) return [];
const out = [];
for (const t of deck.standing()) {
const a = area(t);
if (a && Array.isArray(a.keys) && a.keys.length) out.push(a);
}
return out;
});
}
/** /**
* A field of a summon's token record at its owner's form and module (data/backups.json `tokens[id].variants[<charId>@ * A field of a summon's token record at its owner's form and module (data/backups.json `tokens[id].variants[<charId>@
* <status>]`, `byModule[uniEquipId]`): the stats the normalised def drops (`deployLimit`, `deckStack`). * <status>]`, `byModule[uniEquipId]`): the stats the normalised def drops (`deployLimit`, `deckStack`).
+11 -6
View File
@@ -8,7 +8,8 @@
// Trigger rules (the official 技能策略, PRTS 卫戍协议/帮助 §作战阶段 技能操作; data: tools/build-data.mjs resolveTrigger): // Trigger rules (the official 技能策略, PRTS 卫戍协议/帮助 §作战阶段 技能操作; data: tools/build-data.mjs resolveTrigger):
// DEFAULT — the basic strategy: ready + about to attack/heal + enemy / injured ally in the INITIAL range (or blocked by // DEFAULT — the basic strategy: ready + about to attack/heal + enemy / injured ally in the INITIAL range (or blocked by
// a melee unit) — or, checked every tick, an enemy inside one of the content trigger ranges added with // a melee unit) — or, checked every tick, an enemy inside one of the content trigger ranges added with
// addTriggerRange: 海嗣, 流形; // addTriggerRange: 海嗣, 流形, 谬因 S2's beam, the summons' areas a skill acts through (麦哲伦 S1, 令 S3, 电弧 S2 / S3 —
// the owner's larger-range rule, 2026-10-06: kits/shared/summoner.js summonTriggerArea);
// SKILL_RANGE — a MANUAL skill with a 技能范围 of its own: "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其 // SKILL_RANGE — a MANUAL skill with a 技能范围 of its own: "不通过普通攻击/治疗触发技能,仅在技能范围内存在敌人(无视其
// 不可选中)时释放技能": any living enemy on the trigger grid (the skill range; stealthed / untargetable / flying ones // 不可选中)时释放技能": any living enemy on the trigger grid (the skill range; stealthed / untargetable / flying ones
// too), checked every tick, no attack needed. Kit option `trigger.allies` (+ `hpAtMost`, default 1): a healable, // too), checked every tick, no attack needed. Kit option `trigger.allies` (+ `hpAtMost`, default 1): a healable,
@@ -135,9 +136,12 @@ export class SkillRuntime {
/** /**
* Extra DEFAULT-trigger range (海嗣 "攻击范围视为自身攻击范围的延伸", 流形): `fn(battle, unit)` returns a list whose * Extra DEFAULT-trigger range (海嗣 "攻击范围视为自身攻击范围的延伸", 流形): `fn(battle, unit)` returns a list whose
* entries are ally units (their current `rangeKeys` count while they are on the field) or arrays of absolute tile * entries are ally units (their current `rangeKeys` count while they are on the field), arrays of absolute tile
* keys. A targetable enemy (flyers included) on those tiles satisfies the DEFAULT rule (and unknown DEFAULT-like * keys, or `{ keys, profile }` — tile keys with the enemy profile the effect selects by (`canHitFly` false: ground
* rules); it is checked every tick, since the unit itself may have nothing to attack. Returns an unregister fn. * enemies only — the owner's larger-range rule through a summon's area, kits/shared/summoner.js summonTriggerArea;
* 0.2.0 WV, additive). A targetable enemy (flyers included unless the entry's profile says otherwise) on those tiles
* satisfies the DEFAULT rule (and unknown DEFAULT-like rules); it is checked every tick, since the unit itself may
* have nothing to attack. Returns an unregister fn.
*/ */
addTriggerRange(fn) { addTriggerRange(fn) {
if (typeof fn !== 'function') return () => {}; if (typeof fn !== 'function') return () => {};
@@ -153,10 +157,11 @@ export class SkillRuntime {
const list = b._safe(() => fn(b, u), 'skill.triggerRange', u); const list = b._safe(() => fn(b, u), 'skill.triggerRange', u);
if (!list || typeof list[Symbol.iterator] !== 'function') continue; if (!list || typeof list[Symbol.iterator] !== 'function') continue;
for (const x of list) { for (const x of list) {
let keys = null; let keys = null, prof = TRIGGER_PROFILE;
if (Array.isArray(x)) keys = x; if (Array.isArray(x)) keys = x;
else if (x && typeof x === 'object' && x.side === 'ally' && x.alive && x.deployed && !x.hidden) keys = x.rangeKeys; else if (x && typeof x === 'object' && x.side === 'ally' && x.alive && x.deployed && !x.hidden) keys = x.rangeKeys;
if (keys && keys.length && b.enemiesInKeys(keys, u, TRIGGER_PROFILE).length) return true; else if (x && typeof x === 'object' && Array.isArray(x.keys)) { keys = x.keys; if (x.profile) prof = x.profile; }
if (keys && keys.length && b.enemiesInKeys(keys, u, prof).length) return true;
} }
} }
return false; return false;
+12 -1
View File
@@ -7,7 +7,7 @@
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs'; import { readFileSync, readdirSync } from 'node:fs';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { PROTECT, holdProtect } from '../../server/sim/content/kits/shared/tier1.js'; import { PROTECT, holdProtect } from '../../server/sim/content/kits/shared/tier1.js';
import { getDefaultSource } from '../../server/sim/simdata.js'; import { getDefaultSource } from '../../server/sim/simdata.js';
@@ -79,3 +79,14 @@ test('余 (blocking), 缪尔赛思\'s melee copies, 赫拉格 / 左乐 (SBL-X, b
assert.equal(u.findBuff(PROTECT), null, 'gone above 50 %'); assert.equal(u.findBuff(PROTECT), null, 'gone above 50 %');
done(h); done(h);
}); });
test('0.2.0 WV: no kit keeps its own copy of the shared 庇护 key or mods — 淬羽赫默, 黍, 斥罪, 森蚺, 娜斯提 hold it through holdProtect, 遥\'s bubbles share only the mods', () => {
const dir = new URL('../../server/sim/content/kits/ops/', import.meta.url);
for (const f of readdirSync(dir).filter((x) => x.endsWith('.js'))) {
const s = readFileSync(new URL(f, dir), 'utf8');
assert.doesNotMatch(s, /const (PROTECT|protectMods) =|applyStrongest\([^)]*['"]protect['"]|applyStrongest\(\w+, PROTECT\b/, `${f}: the shared tier1.js PROTECT / protectMods / holdProtect`);
}
for (const f of ['op-slent2.js', 'op-shu.js', 'op-judge.js', 'op-zumama.js', 'op-nasti.js']) {
assert.match(readFileSync(new URL(f, dir), 'utf8'), /holdProtect\(/, `${f} uses holdProtect`);
}
});
+31 -4
View File
@@ -3,12 +3,17 @@
// grid.canStand for its position) lands on low ground only, never on a 高台; on 战场#08 (act2 m04) it may land on a 围墙 tile // grid.canStand for its position) lands on low ground only, never on a 高台; on 战场#08 (act2 m04) it may land on a 围墙 tile
// (tile_fence_bound: LOW, buildable ALL — deployable, it attacks from there and blocks no ground enemy, DESIGN §21.23), which // (tile_fence_bound: LOW, buildable ALL — deployable, it attacks from there and blocks no ground enemy, DESIGN §21.23), which
// the board draws as a raised concrete area inside an orange railing: what the screenshot most likely shows. // the board draws as a raised concrete area inside an orange railing: what the screenshot most likely shows.
// Updated on purpose by 0.2.0 WV #3 (the owner's decision of 2026-10-06): a melee member that blocks lands where its block
// applies — a tile ground units pass (not a 围墙) with an enemy ground path through it (Battle.groundPathTiles) or its enemy
// on it — whenever such a tile covers its enemy, before a nearer one where it would block nothing; a 围墙 tile only when
// nothing else does.
// Run: node --test test/content/feedback5-we2-raid-148.test.js // Run: node --test test/content/feedback5-we2-raid-148.test.js
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { makeBattle, chessRec, enemyRec } from '../helpers/battleHarness.js'; import { makeBattle, chessRec, enemyRec } from '../helpers/battleHarness.js';
import { getData } from '../../server/data.js'; import { getData } from '../../server/data.js';
import { COLS } from '../../server/sim/constants.js';
const D = getData({ log: { warn() {}, error() {}, info() {} } }); const D = getData({ log: { warn() {}, error() {}, info() {} } });
const STAGES = Object.keys(D.stages).filter((id) => D.stages[id].kind !== 'unite'); const STAGES = Object.keys(D.stages).filter((id) => D.stages[id].kind !== 'unite');
@@ -17,9 +22,21 @@ const defs = {
enemies: { e: enemyRec({ key: 'e', hp: 1e9, speed: 0, mass: 0 }) }, enemies: { e: enemyRec({ key: 'e', hp: 1e9, speed: 0, mass: 0 }) },
}; };
const RAID = { raidShip: { count: 3, active: true, tier: 1, layers: 1 } }; const RAID = { raidShip: { count: 3, active: true, tier: 1, layers: 1 } };
/** The tiles raidTile looks at for an enemy on (er, ec): its range [[0, 0], [0, 1]] facing RIGHT led back from the enemy. */
function landingTiles(b, er, ec) {
const out = [];
for (const [dr, dc] of [[0, 0], [0, 1]]) {
const r = er - dr, c = ec - dc;
if (b.grid.inRect(r, c) && b.grid.canStand(r, c) && !b.isReservedTile(r, c)) out.push([r, c]);
}
return out;
}
/** Its block applies there: ground units pass the tile, and an enemy ground path runs through it or its enemy stands on it. */
const blocksOn = (b, [r, c], er, ec) => b.grid.tile(r, c).pass === 'ALL' && (b.groundPathTiles().has(r * COLS + c) || (r === er && c === ec));
const same = (t, r, c) => t[0] === r && t[1] === c;
test('#3 / GitHub #148: a melee 突袭 member never lands on a 高台 (any stage, any enemy tile of the field); 战场#08 lands it on 围墙 tiles', () => { test('#3 / GitHub #148: a melee 突袭 member never lands on a 高台 (any stage, any enemy tile of the field); it lands where it can block whenever such a tile covers its enemy (the owner\'s decision of 2026-10-06), a 围墙 tile only when nothing else does', () => {
let jumps = 0, fence = 0; let jumps = 0, fence = 0, preferred = 0;
for (const stageId of STAGES) { for (const stageId of STAGES) {
const st = D.stages[stageId]; const st = D.stages[stageId];
const tileAt = (r, c) => st.tiles[st.rows[r]?.[c]]; const tileAt = (r, c) => st.tiles[st.rows[r]?.[c]];
@@ -35,14 +52,24 @@ test('#3 / GitHub #148: a melee 突袭 member never lands on a 高台 (any stage
if (!t || t.height !== 'LOW' || (er === home[0] && ec === home[1])) continue; if (!t || t.height !== 'LOW' || (er === home[0] && ec === home[1])) continue;
const h = makeBattle({ stageId, defs, bonds: RAID, enemies: [{ key: 'e', pos: [er, ec] }], units: [{ chessId: 'm', row: home[0], col: home[1] }], autoFinish: false, timeLimit: 60 }); const h = makeBattle({ stageId, defs, bonds: RAID, enemies: [{ key: 'e', pos: [er, ec] }], units: [{ chessId: 'm', row: home[0], col: home[1] }], autoFinish: false, timeLimit: 60 });
const u = h.unit('m'); const u = h.unit('m');
h.step();
const tiles = landingTiles(h.b, er, ec);
const blocking = tiles.filter((x) => blocksOn(h.b, x, er, ec));
h.run(12); h.run(12);
if (u.tileR === home[0] && u.tileC === home[1]) continue; if (u.tileR === home[0] && u.tileC === home[1]) continue;
jumps++; jumps++;
const at = `${stageId}: enemy ${er},${ec} → ${u.tileR},${u.tileC}`;
const lt = tileAt(u.tileR, u.tileC); const lt = tileAt(u.tileR, u.tileC);
assert.ok(lt && lt.height === 'LOW' && (lt.buildable === 'ALL' || lt.buildable === 'MELEE'), `${stageId}: enemy ${er},${ec} → ${u.tileR},${u.tileC} ${lt?.tileKey}/${lt?.height}`); assert.ok(lt && lt.height === 'LOW' && (lt.buildable === 'ALL' || lt.buildable === 'MELEE'), `${at} ${lt?.tileKey}/${lt?.height}`);
assert.ok(tiles.some((x) => same(x, u.tileR, u.tileC)), `${at}: a tile that covers its enemy`);
if (blocking.length) {
assert.ok(blocking.some((x) => same(x, u.tileR, u.tileC)), `${at}: a tile where it blocks was there (${JSON.stringify(blocking)})`);
if (!blocksOn(h.b, tiles[0], er, ec)) preferred++; // the nearest one (its enemy's own) would block nothing
}
if (lt.tileKey === 'tile_fence_bound') fence++; if (lt.tileKey === 'tile_fence_bound') fence++;
} }
} }
assert.ok(jumps > 50, `${jumps} jumps checked`); assert.ok(jumps > 50, `${jumps} jumps checked`);
assert.ok(fence > 0, 'some land on a 围墙 tile (the #148 look)'); assert.ok(preferred > 0, `the preference decided ${preferred} landings`);
assert.ok(fence > 0, 'some still land on a 围墙 tile (the #148 look) — where no tile it blocks on covers the enemy');
}); });
+70
View File
@@ -0,0 +1,70 @@
// test/content/feedback5-wv-sources.test.js — 0.2.0 WV #4: the questions settled by the sources with no rule change, pinned.
// * 忍冬 — PRTS's "无法选中被自身阻挡而没有位于技能范围内的敌人" is the 备注 of S2 坠刃拷问 (技能范围 3-12; WE1 read it as S3's): its
// kit selects on the skill range only, so an enemy she blocks off that range is never one of its targets. S3 has no such
// note (its client selector is the standard 「同时攻击阻挡的所有敌人」 one).
// * 深靛 S1 灯塔守卫者 — PRTS 深靛 S1 备注 "该技能的"每次攻击的攻击倍率"会实时应用在特性积攒的"攻击能量"抛射物上" (the client's
// ChargeAttackS1 reads the same `atk_scale`): an energy she stored lands with S1's attack@atk_scale × ATK, not the
// branch's general 100 % (PRTS 分支特性信息 秘术师 "由储存能量形成的弹道造成攻击力100%的法术普通伤害").
// (阿 S2 / S3 — no ally condition in the data: no skillTriggerDataList row for him or 怪杰, the skill prefab casts with no
// target and keeps the SP spent — is pinned by test/content/op_haak.test.js "爆发剂 edge cases".)
// Run: node --test test/content/feedback5-wv-sources.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { getDefaultSource } from '../../server/sim/simdata.js';
const ds = getDefaultSource();
const dummy = (key, o = {}) => enemyRec({ key, hp: 1e9, speed: 0, mass: 0, ...o });
const approx = (a, b, msg, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg}: ${a} vs ${b}`);
const done = (h) => { checkInvariants(h.b); assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); };
const skillIndexOf = (id, skillId) => ds.rawChess(id).skills.find((s) => s.skillId === skillId).index;
test('#4 忍冬 S2 坠刃拷问 (PRTS 备注 "无法选中被自身阻挡而没有位于技能范围内的敌人"): an enemy she blocks off her 3-12 is never hit; the one inside is', () => {
for (const id of ['chess_char_3_18_a', 'chess_char_3_18_b']) {
const h = makeBattle({
seed: 3, autoFinish: false, timeLimit: 60, hooks: ['damaged'], captureNoisy: true, defs: { enemies: { e: dummy('e') } },
units: [{ chessId: id, row: 10, col: 5, skillIndex: skillIndexOf(id, 'skchr_vulpis_2') }],
});
h.step();
const u = h.unit(id);
const behind = h.spawn('e', { pos: [10, 4] });
behind.x = 4.4; // in contact (0.6 < 0.71) but on the tile behind her: off the 3-12
h.step();
assert.equal(behind.blockedBy, u, `${id}: she blocks it`);
const inside = h.spawn('e', { pos: [10, 7] });
h.step();
assert.ok(u.skill.activate('test', { free: true }), `${id}: cast`);
h.step();
const hit = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.tags?.includes('vulpisTorture'));
assert.ok(hit.some((c) => c.target === inside), `${id}: the enemy inside the 3-12`);
assert.ok(!hit.some((c) => c.target === behind), `${id}: not the blocked one off the skill range`);
done(h);
}
});
test('#4 深靛 S1 灯塔守卫者 (PRTS 备注): the energies she stored land with S1\'s attack@atk_scale × ATK, like its main hit', () => {
const id = 'chess_char_1_17_a';
const r = ds.rawChess(id);
const rec = { ...r, talents: r.talents.map((t) => ({ ...t, bb: { ...t.bb, prob: 0 } })) }; // no random 柔光缚目 bind
const h = makeBattle({
seed: 3, autoFinish: false, timeLimit: 120, hooks: ['damaged', 'skillStart'], captureNoisy: true,
defs: { chess: { [id]: rec }, enemies: { e: dummy('e') } },
units: [{ chessId: id, row: 10, col: 4, skillIndex: skillIndexOf(id, 'skchr_indigo_1') }],
});
h.step();
const u = h.unit(id);
const scale = ds.getChess(id, { skillIndex: skillIndexOf(id, 'skchr_indigo_1') }).skill.bb['attack@atk_scale'];
assert.ok(scale > 0 && scale < 1, 'S1 strikes below 100 %');
assert.ok(h.runUntil(() => u.trait.stored === 3, 15), 'three energies stored with no target');
u.skill.gainSp(999);
h.spawn('e', { pos: [10, 6] });
assert.ok(h.runUntil(() => u.skill.active, u.s.interval + 1), 'S1 cast at her next attack check');
h.run(1);
const first = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg.isAttack);
const id0 = first[0].dmg.attackId;
const volley = first.filter((c) => c.dmg.attackId === id0);
assert.equal(volley.length, 1 + 3, 'the S1 attack + its 3 energies');
for (const c of volley) approx(c.amount, u.s.atk * scale, 'S1\'s 攻击倍率 on each');
done(h);
});
@@ -0,0 +1,123 @@
// test/content/feedback5-wv-summon-triggers.test.js — 0.2.0 WV #2: the owner's larger-range rule through summons (the
// owner's decision of 2026-10-06: "a skill whose effect reaches farther than the operator's own range casts when an enemy is
// inside the larger area"). A skill that acts through its owner's summons casts on an enemy inside such an area of a summon
// standing on the field, on top of its data rule (DEFAULT: an enemy in the owner's own range) — kits/shared/summoner.js
// summonTriggerArea (a content trigger range, server/sim/skills.js addTriggerRange with a `{ keys, profile }` entry):
// * 麦哲伦 S1 高效制冷模块 — her drones' ranges (air units too: its 停顿 / 束缚 reach them);
// * 令 S3 宁作吾 — each summon's x-5 (ground enemies only: its pulses "不可对空");
// * 电弧 S2 环形鳞地 — 赛柯's range (ground only: its bullets reach no air unit); S3 手牵手 — 桑特拉's range (air too).
// Run: node --test test/content/feedback5-wv-summon-triggers.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { COLS } from '../../server/sim/constants.js';
const SLOT = { 5: 'chess_char_5_diy1_a', 6: 'chess_char_6_diy1_a' };
const dummy = (key, o = {}) => enemyRec({ key, hp: 1e9, speed: 0, mass: 0, ...o });
const ENEMIES = { enemy_dummy: dummy('enemy_dummy'), enemy_fly: dummy('enemy_fly', { motion: 'FLY' }) };
const MG = 'char_248_mgllan', LING = 'char_2023_ling', RADIAN = 'char_4195_radian';
const DRONE_F = 'token_10005_mgllan_drone1';
const SOUL3 = 'token_10020_ling_soul3';
const TOWER2 = 'token_10052_radian_tower2', TOWER3 = 'token_10053_radian_tower3';
function done(h) {
checkInvariants(h.b);
assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0]));
}
/** `charId` as uid 1 at (9, 2) facing RIGHT (her 3-1: rows 8–10, cols 2–5) with skill `skill`; `pieces` = [[tokenId, r, c]]. */
function field(charId, skill, pieces = [], { tier = 6, elite = true } = {}) {
const h = makeBattle({
defs: { enemies: ENEMIES }, timeLimit: 900, autoFinish: false, seed: 5,
flags: { dpInit: 99, dpPerSec: 0, dpMax: 999, startOpCooldown: 0 }, hooks: ['skillStart', 'skillEnd', 'death'], captureNoisy: true,
units: [{ uid: 1, diy: { slot: SLOT[tier], charId, skillIndex: skill, uniEquipId: null }, elite, row: 9, col: 2 },
...pieces.map(([tokenId, r, c], i) => ({ uid: 10 + i, kind: 'token', tokenId, ownerUid: 1, row: r, col: c, dir: 'RIGHT' }))],
});
h.step();
const u = h.unit(1);
return { h, u, ps: h.b.allyUnits.filter((a) => a.kind === 'token' && a.ownerUnit === u) };
}
/** Fill the skill and run `s` seconds; true when it was cast. */
function casts(h, u, s = 2) {
u.skill.gainSp(999);
h.run(s);
return u.skill.activations > 0;
}
const outOfHerRange = (u, r, c) => !u.rangeKeySet.has(r * COLS + c);
test('#2 麦哲伦 S1: an enemy on a standing drone\'s range (air units too) casts it; none there or with the drone gone: no cast; S2 / S3 get no such area', () => {
{ // a ground enemy on 龙腾.F's x-4, far from her 3-1
const { h, u, ps } = field(MG, 0, [[DRONE_F, 12, 9]]);
assert.equal(u.skill.rule, 'DEFAULT');
assert.ok(ps[0].alive && ps[0].rangeKeySet.has(12 * COLS + 10) && outOfHerRange(u, 12, 10), 'the setup');
h.spawn('enemy_dummy', { pos: [11, 13] }); // outside every area
assert.ok(!casts(h, u), 'nobody in her range or a drone\'s: no cast');
h.spawn('enemy_dummy', { pos: [12, 10] });
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), 'an enemy on the drone\'s range casts S1');
done(h);
}
{ // a flyer there too
const { h, u } = field(MG, 0, [[DRONE_F, 12, 9]]);
h.spawn('enemy_fly', { pos: [11, 10] });
assert.ok(casts(h, u), 'a flyer on the drone\'s range');
done(h);
}
{ // the drone off the field: nothing
const { h, u, ps } = field(MG, 0, [[DRONE_F, 12, 9]]);
h.b.retreat(ps[0], { reason: 'retreat' });
h.spawn('enemy_dummy', { pos: [12, 10] });
assert.ok(!casts(h, u), 'no standing drone: no cast');
done(h);
}
for (const skill of [1, 2]) {
const { h, u } = field(MG, skill, []);
assert.equal(u.skill.triggerRanges.length, 0, `S${skill + 1}`);
done(h);
}
});
test('#2 令 S3: a ground enemy on a standing summon\'s x-5 casts it; a flyer there, or a ground enemy off the x-5, does not; S1 / S2 get no such area', () => {
{ // 弦惊 at (12, 9): its x-5 = (12, 9) and its four side neighbours
const { h, u, ps } = field(LING, 2, [[SOUL3, 12, 9]]);
assert.equal(u.skill.rule, 'DEFAULT');
assert.ok(ps[0].alive && outOfHerRange(u, 11, 9), 'the setup');
h.spawn('enemy_fly', { pos: [11, 9] }); // on the x-5, but air
h.spawn('enemy_dummy', { pos: [11, 10] }); // diagonal: off the x-5
assert.ok(!casts(h, u), 'a flyer on the x-5 / a ground enemy off it: no cast');
h.spawn('enemy_dummy', { pos: [11, 9] });
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), 'a ground enemy on the summon\'s x-5 casts S3');
done(h);
}
for (const skill of [0, 1]) {
const { h, u } = field(LING, skill, []);
assert.equal(u.skill.triggerRanges.length, 0, `S${skill + 1}`);
done(h);
}
});
test('#2 电弧 S2: a ground enemy on 赛柯\'s range casts it (a flyer there does not); S3: an enemy on 桑特拉\'s range, air units too; S1 gets no such area', () => {
{ // 赛柯 at (12, 6) facing RIGHT: its 3-2 = (12, 6) … (12, 9)
const { h, u, ps } = field(RADIAN, 1, [[TOWER2, 12, 6]]);
assert.equal(u.skill.rule, 'DEFAULT');
assert.ok(ps[0].alive && ps[0].rangeKeySet.has(12 * COLS + 9) && outOfHerRange(u, 12, 9), 'the setup');
h.spawn('enemy_fly', { pos: [12, 9] });
assert.ok(!casts(h, u), 'a flyer on 赛柯\'s range: no cast');
h.spawn('enemy_dummy', { pos: [12, 9] });
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), 'a ground enemy on 赛柯\'s range casts S2');
done(h);
}
{ // 桑特拉 at (12, 6) facing RIGHT: its 3-1 = rows 11–13, cols 6–9
const { h, u, ps } = field(RADIAN, 2, [[TOWER3, 12, 6]]);
assert.ok(ps[0].alive && ps[0].rangeKeySet.has(12 * COLS + 9) && outOfHerRange(u, 12, 9), 'the setup');
h.spawn('enemy_dummy', { pos: [11, 13] }); // outside every area
assert.ok(!casts(h, u), 'nobody in an area: no cast');
h.spawn('enemy_fly', { pos: [12, 9] });
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), 'a flyer on 桑特拉\'s range casts S3');
done(h);
}
{
const { h, u } = field(RADIAN, 0, []);
assert.equal(u.skill.triggerRanges.length, 0, 'S1');
done(h);
}
});
+18 -7
View File
@@ -797,7 +797,7 @@ test('星熊 module PRO-X 护身符: DEF +20 % while blocking', () => {
approx(def(null, true), def(null, false), 1e-9); approx(def(null, true), def(null, false), 1e-9);
}); });
test('泥岩 S1 防御力强化·γ型 (TAKE_DAMAGE) and S3 秽壤的血脉 (dormant & invulnerable & blocking, slow; then stun, buffs, hits all blocked)', () => { test('泥岩 S1 防御力强化·γ型 (TAKE_DAMAGE) and S3 秽壤的血脉 (闭锁: invulnerable, no attack, blocks nobody — the enemies she held walk on —, slow; then stun, buffs, hits all blocked)', () => {
for (const id of pair('chess_char_4_18_a')) { for (const id of pair('chess_char_4_18_a')) {
const b1 = D(id, 0).skill.bb, sk = D(id, 2).skill, b3 = sk.bb; const b1 = D(id, 0).skill.bb, sk = D(id, 2).skill, b3 = sk.bb;
let h = battle([U(id, 10, 4, 0)], { enemies: { enemy_hitter: dummy({ key: 'enemy_hitter', atk: 300, bat: 1 }) } }); let h = battle([U(id, 10, 4, 0)], { enemies: { enemy_hitter: dummy({ key: 'enemy_hitter', atk: 300, bat: 1 }) } });
@@ -812,21 +812,32 @@ test('泥岩 S1 防御力强化·γ型 (TAKE_DAMAGE) and S3 秽壤的血脉 (dor
for (let i = 0; i < 3; i++) h.spawn('e_walk', { routeIndex: 0 }); for (let i = 0; i < 3; i++) h.spawn('e_walk', { routeIndex: 0 });
u = h.unit(id); u = h.unit(id);
assert.ok(h.runUntil(() => u.blocking.length === 3, 30), 'blocks three walkers'); assert.ok(h.runUntil(() => u.blocking.length === 3, 30), 'blocks three walkers');
const walkers = u.blocking.slice();
const hp0 = u.hp; const hp0 = u.hp;
assert.ok(u.skill.activate('test', { free: true })); assert.ok(u.skill.activate('test', { free: true }));
const t0 = h.b.time; const t0 = h.b.time;
h.run(b3.sleep - 1); h.step();
// 闭锁 (PRTS 备注 "实际将会进入闭锁状态"; PRTS 异常效果 闭锁 = 强制缴械 + 无敌 + 不可阻挡, 不可阻挡 "无法阻挡/被阻挡,自动解除阻挡")
assert.ok(u.blocking.length === 0 && walkers.every((e) => e.blockedBy !== u), `${id}: 闭锁 lets go of the enemies she held`);
assert.ok(walkers.every((e) => e.findBuff(`mudrok:slow:${u.id}`)), 'enemies around slowed');
const x0 = walkers.map((e) => e.x);
h.run(1);
assert.ok(walkers.every((e, i) => e.removed || e.x < x0[i] - 0.05), `${id}: they walk on`);
h.run(b3.sleep - 2.5);
assert.equal(u.hp >= hp0 - 1e-6, true, `${id}: no damage while dormant`); assert.equal(u.hp >= hp0 - 1e-6, true, `${id}: no damage while dormant`);
assert.equal(u.blocking.length, 3, 'keeps blocking'); assert.equal(u.blocking.length, 0, `${id}: blocks nobody while dormant`);
assert.equal(h.hooksOf('attack').filter((c) => c.attacker === u && c.t > t0).length, 0, 'cannot act'); assert.equal(h.hooksOf('attack').filter((c) => c.attacker === u && c.t > t0).length, 0, 'cannot act');
assert.ok(u.blocking.every((e) => e.findBuff(`mudrok:slow:${u.id}`)), 'enemies around slowed'); const near = [0, 1, 2].map(() => h.spawn('enemy_dummy', { pos: [9, 5] })); // on her tile before she wakes
h.run(1.2); h.step();
assert.ok(u.findBuff('mudrok:awake'), 'awake'); assert.equal(u.blocking.length, 0, `${id}: not blocked before she wakes`);
for (const e of u.blocking) assert.ok(e.s.flags.stun, 'ground enemies around stunned'); assert.ok(h.runUntil(() => !!u.findBuff('mudrok:awake'), 2), 'awake');
h.run(0.2);
for (const e of near) assert.ok(e.s.flags.stun, 'ground enemies around stunned');
approx(u.s.atk, u.base.atk * (1 + b3.atk) * (u.findBuff('mudrok:module') ? 1 + (u.def.traitBb.atk ?? 0) : 1), 1e-6, 'ATK'); approx(u.s.atk, u.base.atk * (1 + b3.atk) * (u.findBuff('mudrok:module') ? 1 + (u.def.traitBb.atk ?? 0) : 1), 1e-6, 'ATK');
approx(u.s.interval, (u.base.bat + b3.base_attack_time) * 100 / u.s.aspd, 1e-6, 'BAT −0.3 s'); approx(u.s.interval, (u.base.bat + b3.base_attack_time) * 100 / u.s.aspd, 1e-6, 'BAT −0.3 s');
assert.ok(h.runUntil(() => h.hooksOf('attack').some((c) => c.attacker === u && c.t > t0), 5)); assert.ok(h.runUntil(() => h.hooksOf('attack').some((c) => c.attacker === u && c.t > t0), 5));
assert.equal(h.hooksOf('attack').find((c) => c.attacker === u && c.t > t0).targets.length, 3, 'attacks every blocked enemy'); assert.equal(h.hooksOf('attack').find((c) => c.attacker === u && c.t > t0).targets.length, 3, 'attacks every blocked enemy');
assert.ok(h.runUntil(() => u.blocking.length === 3, 4), `${id}: she blocks again once awake (their stun over)`);
h.runUntil(() => !u.skill.active, sk.duration); h.runUntil(() => !u.skill.active, sk.duration);
assert.ok(!u.findBuff('mudrok:awake')); assert.ok(!u.findBuff('mudrok:awake'));
checkInvariants(h.b); checkInvariants(h.b);
+54 -3
View File
@@ -216,7 +216,8 @@ test('S1 取势 (AUTO, SP_FULL): the triggering enemy is 停顿 6.5 s and takes
const sk = skillOf(tier, elite, S1); const sk = skillOf(tier, elite, S1);
assert.deepEqual([sk.spCost, sk.bb.cnt, sk.bb['attack@atk_scale'], sk.bb['attack@sluggish']], elite ? [20, 2, 1.2, 6.5] : [21, 2, 1.05, 6.5], `T${tier}`); assert.deepEqual([sk.spCost, sk.bb.cnt, sk.bb['attack@atk_scale'], sk.bb['attack@sluggish']], elite ? [20, 2, 1.2, 6.5] : [21, 2, 1.05, 6.5], `T${tier}`);
const { h, u, st } = field({ tier, elite, skill: 0, stones: [[10, 6], [10, 7]] }); const { h, u, st } = field({ tier, elite, skill: 0, stones: [[10, 6], [10, 7]] });
assert.deepEqual([u.skill.kind, u.skill.rule, u.mem.wang.stock], ['instant', 'SP_FULL', 6]); // her stock: 6 less the 2 placed 棋子 (the owner's decision of 2026-10-06 — see the stock test below)
assert.deepEqual([u.skill.kind, u.skill.rule, u.mem.wang.stock], ['instant', 'SP_FULL', 4]);
const e = h.spawn('enemy_dummy', { pos: [10, 7] }); const e = h.spawn('enemy_dummy', { pos: [10, 7] });
h.step(); h.step();
const slug = h.hooksOf('statusApplied').find((c) => c.target === e && c.status === 'sluggish'); const slug = h.hooksOf('statusApplied').find((c) => c.target === e && c.status === 'sluggish');
@@ -238,7 +239,7 @@ test('S1 取势 (AUTO, SP_FULL): the triggering enemy is 停顿 6.5 s and takes
h.step(); h.step();
h.step(); h.step();
assert.equal(e2.buffs.filter((b) => b.key === 'wang:qushi').length, 2, `T${tier}: independent`); assert.equal(e2.buffs.filter((b) => b.key === 'wang:qushi').length, 2, `T${tier}: independent`);
// the active part: +2 stock (6 → 7: the cap), then 阻回 // the active part: +2 stock (6 → 7: the cap — both pieces are off the field, their cards back in her hand), then 阻回
u.skill.gainSp(999); u.skill.gainSp(999);
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), `T${tier}: cast at full SP`); assert.ok(h.runUntil(() => u.skill.activations === 1, 1), `T${tier}: cast at full SP`);
assert.equal(u.mem.wang.stock, 7, `T${tier}: 最多拥有7枚`); assert.equal(u.mem.wang.stock, 7, `T${tier}: 最多拥有7枚`);
@@ -248,6 +249,56 @@ test('S1 取势 (AUTO, SP_FULL): the triggering enemy is 停顿 6.5 s and takes
} }
}); });
test('her stock (the owner\'s decision of 2026-10-06): the placed 棋子 occupy it like ones she deployed; a 棋子 that goes off goes back into it at once and its return takes one again; at the cap one that goes off is lost to the cap, so 阻回 lifts with its return and S1 / S2 keep casting', () => {
for (const skill of [0, 1]) {
const S = `S${skill + 1}`;
const { h, u, st } = field({ tier: 6, elite: true, skill, stones: [[10, 6], [10, 7]] });
const w = u.mem.wang;
assert.equal(w.stock, 6 - 2, `${S}: 6 less the 2 placed`);
const a = at(st, 10, 6), b = at(st, 10, 7);
let e = h.spawn('enemy_dummy', { pos: [10, 7] });
h.step();
assert.ok(!b.alive, `${S}: set off`);
assert.equal(w.stock, 5, `${S}: its card is back in her stock at once`);
h.b.kill(e, null);
assert.ok(h.runUntil(() => b.alive, 5), `${S}: back on its tile`);
assert.equal(w.stock, 4, `${S}: its return takes one`);
// two casts: 4 → 6 → 7 (8, over the cap); 阻回 at the cap only
u.skill.gainSp(999);
h.step();
assert.deepEqual([u.skill.activations, w.stock], [1, 6], `${S}: 立即获得两枚棋子`);
h.step();
assert.ok(!u.s.flags.noSp, `${S}: below the cap, no 阻回`);
u.skill.gainSp(999);
h.step();
assert.deepEqual([u.skill.activations, w.stock], [2, 7], `${S}: 最多拥有7枚`);
h.step();
assert.ok(u.s.flags.noSp, `${S}: 阻回 at the cap`);
const sp0 = u.skill.sp;
h.run(2);
assert.equal(u.skill.sp, sp0, `${S}: no SP under 阻回`);
// at the cap: the piece that goes off is lost to the cap, its return takes one ⇒ 阻回 lifts, SP comes back
e = h.spawn('enemy_dummy', { pos: [10, 6] });
h.step();
assert.ok(!a.alive, `${S}: set off at the cap`);
assert.equal(w.stock, 7, `${S}: still the cap`);
h.b.kill(e, null);
assert.ok(h.runUntil(() => a.alive, 5), `${S}: back`);
assert.equal(w.stock, 6, `${S}: its return took one`);
h.step();
assert.ok(!u.s.flags.noSp, `${S}: 阻回 lifted`);
const sp1 = u.skill.sp;
h.run(2);
assert.ok(u.skill.sp > sp1, `${S}: her SP recovers again`);
done(h);
}
// TRP-X: 7 placed 棋子 (its deploy limit) on a stock of 6 ⇒ 0, never below
const { h, u, st } = field({ tier: 6, elite: true, mod: X, skill: 0, stones: [[10, 3], [10, 4], [10, 5], [10, 6], [10, 7], [10, 8], [10, 9]] });
assert.ok(st.length === 7 && st.every((s) => s.alive), 'TRP-X: all 7 deploy');
assert.equal(u.mem.wang.stock, 0, 'TRP-X: 7 placed on a stock of 6');
done(h);
});
test('S2 连星 (AUTO, SP_FULL): a triggered piece hits every enemy on its connected lines (its tile ± 3; air too) for 420 % / 480 % ATK arts and slows them 35 % / 40 % for 6 s — each slow on its own timer, added up, speed ≥ 0.1', () => { test('S2 连星 (AUTO, SP_FULL): a triggered piece hits every enemy on its connected lines (its tile ± 3; air too) for 420 % / 480 % ATK arts and slows them 35 % / 40 % for 6 s — each slow on its own timer, added up, speed ≥ 0.1', () => {
for (const [tier, elite] of [[5, false], [6, true]]) { for (const [tier, elite] of [[5, false], [6, true]]) {
const sk = skillOf(tier, elite, S2); const sk = skillOf(tier, elite, S2);
@@ -308,7 +359,7 @@ test('S3 天下劫 (MANUAL, ACTIVE_RANGE 4-12, 20 bullets): passive — the piec
assert.equal(u.skill.attackOverride()?.noAttack, true, `T${tier}: 停止攻击`); assert.equal(u.skill.attackOverride()?.noAttack, true, `T${tier}: 停止攻击`);
assert.equal(u.mem.wang.stock, 7, `T${tier}: +8, capped`); assert.equal(u.mem.wang.stock, 7, `T${tier}: +8, capped`);
const fl = placed(h).map(([r, c]) => `${r},${c}`); const fl = placed(h).map(([r, c]) => `${r},${c}`);
assert.equal(fl.length, 7, `T${tier}: 6 + 8 − 7 over the cap`); assert.equal(fl.length, 5, `T${tier}: (6 − 2 placed) + 8 − 7 over the cap`);
assert.deepEqual(fl.slice(0, 2).sort(), ['10,7', '9,5'], `T${tier}: the enemies' tiles first (${fl})`); assert.deepEqual(fl.slice(0, 2).sort(), ['10,7', '9,5'], `T${tier}: the enemies' tiles first (${fl})`);
assert.ok(h.hooksOf('damaged').some((c) => c.target === a && c.source === u && c.dmg.tags.includes('wang:stone')), `T${tier}: they set off`); assert.ok(h.hooksOf('damaged').some((c) => c.target === a && c.source === u && c.dmg.tags.includes('wang:stone')), `T${tier}: they set off`);
// inside her range a 棋子 comes back onto an enemy ("可部署至敌人所在位置") — and sets off again // inside her range a 棋子 comes back onto an enemy ("可部署至敌人所在位置") — and sets off again
+274 -234
View File
@@ -139,42 +139,42 @@
"killed": 7, "killed": 7,
"total": 8, "total": 8,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 2303707, "bossHpLeft": 2323494,
"rngDraws": 882, "rngDraws": 861,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":602609,"bossDmg":333143,"heal":20719,"deaths":0,"layerGains":{}}, "p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":591887,"bossDmg":312274,"heal":19641,"deaths":0,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":913438,"bossDmg":360254,"heal":6843,"deaths":1,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":878330,"bossDmg":361136,"heal":6843,"deaths":1,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,186260,1132,15,78,3], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,176503,1132,0,72,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,723,12774,265,37,1], ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,723,12906,183,34,1],
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,26917,2238,333,59,14], ["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,26692,1028,261,54,14],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,13424,0,0,85,1], ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,13640,0,0,80,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,76572,0,1021,32,9], ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,59962,0,1515,33,6],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,73698,0,0,44,4], ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,72626,0,0,43,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,0,26447,0,0,45,2], ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,0,28364,0,0,57,3],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,19,4,164686,2047,1463,55,21], ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,19,4,174454,2047,1463,57,21],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,17579,24,3], ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,16175,20,3],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,33883,1393,44,34,2], ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,38923,1393,44,40,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",599.39,1,12,0,0,3929,1201,0,2], ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",599.39,1,12,0,0,3929,1201,0,2],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,49,1,259634,511,511,291,8], ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,48,1,241366,511,511,284,8],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",1898.31,1,8,0,2347,1249,764,15,6], ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",1898.31,1,8,0,2347,1249,764,15,6],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,48,1,116398,2806,1366,164,11], ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,47,1,101686,2806,1366,162,10],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1931.91,1,10,0,56922,1270,424,53,3], ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1931.91,1,10,0,56922,1270,424,53,3],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,264370,0,0,175,2], ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,275392,0,0,175,2],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,0,0,0,0,0], ["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,0,0,0,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1636.54,1,18,1,51064,3170,2575,44,4], ["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1636.54,1,18,1,44668,3170,2575,41,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,41,0,69587,1991,2,105,28], ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,41,0,62779,1991,2,96,26],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,93116,0,0,61,17], ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,93170,0,0,61,17],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null], ["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null] ["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
], ],
"enemies": {"enemy_1005_yokai_2":[6,6,0,3187,62190],"enemy_1430_lrrook":[2,1,1,0,760564],"enemy_9017_achunt":[2,0,0,31321,696293]}, "enemies": {"enemy_1005_yokai_2":[6,6,0,3187,62190],"enemy_1430_lrrook":[2,1,1,0,734721],"enemy_9017_achunt":[2,0,0,30243,676506]},
"hooks": [1,47,6000,1396,1412,2975,3026,5410,9,23,141,136,23,38184,1056,0,10,1,1,9,0,13,0], "hooks": [1,46,6000,1359,1379,2883,2933,5284,9,22,136,129,20,37832,1034,0,10,1,1,9,0,12,0],
"events": {"atk":1421,"deploy":37,"die":22,"dmg":3018,"engage":17,"fx":634,"heal":55,"leak":1,"skill":277,"spawn":32,"status":527}, "events": {"atk":1384,"deploy":36,"die":21,"dmg":2925,"engage":17,"fx":608,"heal":50,"leak":1,"skill":265,"spawn":32,"status":505},
"fx": {"aoe":38,"appear":2,"blink":14,"burst":13,"catShield":1,"charge":6,"counter":23,"crit":98,"dilemma":3,"disappear":2,"explosion":17,"frostNova":28,"garrisonGrant":1,"iceSpike":41,"overclock":2,"pull":3,"quadShot":2,"ripple":14,"sentry":5,"sermon":1,"shadowWeave":10,"shield":6,"soul":6,"sp":10,"stealth":8,"swordRain":105,"swordStorm":11,"tornado":4,"tornadoPulse":17,"weightless":4,"zone":139}, "fx": {"aoe":38,"appear":2,"blink":13,"burst":12,"catShield":1,"charge":6,"counter":20,"crit":105,"dilemma":3,"disappear":2,"explosion":17,"frostNova":26,"garrisonGrant":1,"iceSpike":53,"overclock":2,"pull":3,"quadShot":2,"ripple":20,"sentry":5,"sermon":1,"shadowWeave":10,"shield":5,"soul":6,"sp":8,"stealth":8,"swordRain":91,"swordStorm":10,"tornado":4,"tornadoPulse":16,"weightless":4,"zone":114},
"snaps": "fd90838e 1a32ad53 fa1fb93e d4cb9fb5 9a558a4f 03f789d7 0b46d96b 279a307a f3103ed5 faa4fc49 0d6b6cef 9490511c 094c0736 c2256243 08e7fbb2 8ae85b69 5490f959 95e4ec90 acf8698a 8140cf80" "snaps": "fd90838e 1a32ad53 fa1fb93e d4cb9fb5 9a558a4f 03f789d7 0b46d96b 279a307a f3103ed5 faa4fc49 6fb7ae55 ce7f1071 c23efccb a5e804fd d5357397 7ac01e1e 1e658876 0bdd07d6 825d32ce b6f61819"
}, },
"boss-boss_2-solo": { "boss-boss_2-solo": {
"about": "假想敌:铳 solo pool 750000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "假想敌:铳 solo pool 750000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -222,64 +222,102 @@
"round": 14, "round": 14,
"seed": 1428555614, "seed": 1428555614,
"waveId": "act1autochess_h07_03", "waveId": "act1autochess_h07_03",
"time": 101.33, "time": 103.6,
"ticks": 3040, "ticks": 3108,
"reason": "cleared", "reason": "cleared",
"killed": 17, "killed": 17,
"total": 18, "total": 18,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 0, "bossHpLeft": 0,
"rngDraws": 761, "rngDraws": 781,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":17,"total":18,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":790828,"bossDmg":729593,"heal":1797,"deaths":0,"layerGains":{}}, "p1": {"killed":17,"total":18,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":804545,"bossDmg":740515,"heal":1513,"deaths":0,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1392939,"bossDmg":1270407,"heal":15259,"deaths":0,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1379222,"bossDmg":1259485,"heal":18886,"deaths":0,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3430,1,7,0,0,0,0,0,0], ["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3430,1,7,0,0,0,0,0,0],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,11,0,0,0,0,0,0], ["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,11,0,0,0,0,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,9,0,0,0,0,0,0], ["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,9,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",2131.5,1,13,0,361107,0,0,32,4], ["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",2131.5,1,13,1,353516,0,0,32,4],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,17,1,183468,165,10,36,2], ["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,17,0,193845,39,5,38,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1598,1,19,2,17072,4605,1256,0,2], ["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1533.97,1,19,1,17503,4365,1291,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2307.2,1,21,3,165063,1184,530,48,4], ["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2307.2,1,21,1,176549,712,217,50,4],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,5,0,0,0,0,0,0], ["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,5,0,0,0,0,0,0],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,15,0,11,0,0,4,4], ["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,15,1,10,0,0,3,3],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,23,0,43052,0,0,104,3], ["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,23,0,45158,0,0,106,3],
["p1",11,"token","token_10017_skadi2_dedant",null,null,130.4,0,26,0,0,0,0,0,null], ["p1",11,"token","token_10017_skadi2_dedant",null,null,130.4,0,26,0,0,0,0,0,null],
["p2",12,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",3801.6,1,31,4,225239,0,0,107,2], ["p2",12,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",3801.6,1,31,1,230732,8103,1186,110,2],
["p2",13,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,12,0,15,0,0,2,1], ["p2",13,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,12,1,16446,0,0,3,1],
["p2",14,"op","chess_char_5_01_b",1,"uniequip_002_excu2",521.56,1,10,1,8,2572,480,8,1], ["p2",14,"op","chess_char_5_01_b",1,"uniequip_002_excu2",1653.08,1,10,1,6,1222,360,6,1],
["p2",15,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",5928.45,1,32,0,217377,4502,431,59,2], ["p2",15,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",6215.89,1,32,0,146091,836,0,41,2],
["p2",16,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",3117.74,1,16,0,101420,1458,124,36,2], ["p2",16,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",3117.74,1,16,3,104892,856,166,37,2],
["p2",17,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2392.2,1,20,5,724323,2664,802,199,3], ["p2",17,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2392.2,1,20,2,743197,2712,799,206,3],
["p2",18,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2050.92,1,22,0,0,2006,3059,33,1], ["p2",18,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2050.92,1,22,0,0,2064,2819,24,1],
["p2",19,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1554.12,1,25,0,24405,0,0,47,2], ["p2",19,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1554.12,1,25,2,34678,579,0,67,2],
["p2",20,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,6,2,78481,0,0,8,2], ["p2",20,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,6,4,84942,0,0,11,2],
["p2",21,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1631.88,1,18,0,0,0,10363,34,1], ["p2",21,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1631.88,1,18,0,0,0,13556,24,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null], ["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,1557.36,1,24,0,21054,1020,0,45,1], ["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,24,0,8296,1880,0,17,1],
["p2","~1","token","token_10019_nearl2_sword",null,null,7824,0,41,0,10835,0,0,0,null], ["p2","~1","token","token_10019_nearl2_sword",null,null,7824,0,41,0,9120,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,1880.37,0,42,0,0,0,0,0,null], ["p1","~2","token","token_10030_mlyss_wtrman",null,null,860.02,0,42,0,1348,1020,0,2,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,49,0,0,0,0,0,null], ["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,47,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,1557.36,0,51,0,0,0,0,0,null], ["p1","~3","token","token_10030_mlyss_wtrman",null,null,1557.36,1,51,0,8320,0,0,18,1],
["p2","~2","token","token_10019_nearl2_sword",null,null,6000,0,52,0,10835,0,0,0,null] ["p2","~2","token","token_10019_nearl2_sword",null,null,6480,0,52,0,9120,0,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1557.36,1,54,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_1197_sfshu_2": [2,2,0,0,28809], "enemy_1197_sfshu_2": [2,2,0,0,28809],
"enemy_1198_msfshu_2": [6,6,0,0,24], "enemy_1198_msfshu_2": [6,6,0,0,24],
"enemy_1209_sfden_2": [2,2,0,5522,154793], "enemy_1209_sfden_2": [2,2,0,11003,154793],
"enemy_1210_msfden_2": [2,2,0,0,90], "enemy_1210_msfden_2": [2,2,0,0,90],
"enemy_9021_acduml": [1,1,0,0,2000000], "enemy_9021_acduml": [1,1,0,0,2000000],
"enemy_9023_acdums": [6,5,0,8235,50] "enemy_9023_acdums": [6,5,0,6838,51]
}, },
"hooks": [1,52,3040,808,808,1748,1978,1530,18,26,37,29,207,23013,905,3,19,0,1,17,0,1,0], "hooks": [1,53,3108,805,805,1752,1974,1761,19,26,38,30,214,23319,943,4,19,0,1,18,0,1,0],
"events": {"atk":924,"deploy":29,"die":26,"dmg":1822,"engage":14,"fx":558,"heal":152,"skill":66,"spawn":50,"status":318}, "events": {"atk":900,"deploy":30,"die":26,"dmg":1813,"engage":16,"fx":571,"heal":175,"skill":68,"spawn":51,"status":333},
"fx": {"airstrike":83,"beam":5,"blink":2,"bombard":29,"bombardShell":29,"buff":3,"burst":1,"copy":1,"displace":1,"dp":3,"drone":153,"droneLock":16,"drones":2,"explode":83,"finale":1,"garrisonGrant":1,"lightning":45,"link":2,"lock":29,"lure":2,"phase":11,"radiantSword":2,"slash":11,"snow":11,"split":2,"steal":3,"storm":4,"summon":10,"sunBurst":1,"talent":4,"telegraph":3,"tide":2,"wanted":3}, "fx": {
"snaps": "e7a827a7 3396c380 e8117486 1db9371a 549e90ec e1d51c02 b030ac35 77f14ab3 681cd5f3 f01f899e 1985493f" "airstrike": 76,
"appear": 1,
"beam": 5,
"blink": 2,
"bombard": 30,
"bombardShell": 30,
"buff": 3,
"burst": 1,
"copy": 2,
"disappear": 1,
"displace": 2,
"dp": 3,
"drone": 164,
"droneLock": 13,
"drones": 2,
"explode": 86,
"extraAttack": 1,
"finale": 1,
"garrisonGrant": 1,
"lightning": 42,
"link": 4,
"lock": 30,
"lure": 2,
"phase": 11,
"radiantSword": 2,
"slash": 11,
"snow": 11,
"split": 2,
"steal": 3,
"storm": 4,
"summon": 10,
"sunBurst": 1,
"talent": 4,
"telegraph": 4,
"tide": 3,
"wanted": 3
},
"snaps": "f2137fd0 c04d9051 f10f9b96 29aa5d2c 2dae50e9 376aaf70 de60a0fc 9ba08ca9 8d4ba472 13c48fe0 cfe736f1"
}, },
"boss-boss_3-solo": { "boss-boss_3-solo": {
"about": "假想敌:管 solo pool 500000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b", "about": "假想敌:管 solo pool 500000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
@@ -344,81 +382,81 @@
"time": 200, "time": 200,
"ticks": 6000, "ticks": 6000,
"reason": "forced", "reason": "forced",
"killed": 10, "killed": 9,
"total": 14, "total": 14,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 3196571, "bossHpLeft": 3401883,
"rngDraws": 664, "rngDraws": 496,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":10,"total":14,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":963373,"bossDmg":352189,"heal":27540,"deaths":16,"layerGains":{}}, "p1": {"killed":9,"total":14,"perfect":false,"leaks":3,"leaksUncounted":0,"coins":0,"dmg":832066,"bossDmg":272991,"heal":24644,"deaths":21,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1055788,"bossDmg":651240,"heal":3664,"deaths":16,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":815098,"bossDmg":525125,"heal":3151,"deaths":24,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3439.01,1,64,2,75564,8397,115,9,1], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,51,3,138264,7838,24,22,1],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",1398.68,1,55,0,21340,9850,1188,14,2], ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,5,0,16815,6179,893,10,1],
["p1",3,"op","chess_char_5_14_b",1,"none",1811.48,1,61,0,49407,6992,844,3,8], ["p1",3,"op","chess_char_5_14_b",1,"none",0,0,62,0,49407,10951,912,3,8],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",625.33,1,60,0,4008,10852,126,2,1], ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,59,0,4545,12126,187,2,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",799.26,1,57,5,436442,3258,9173,105,19], ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",751.16,1,66,2,302889,4343,4325,83,13],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",967.4,1,59,0,78457,10700,423,39,4], ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",0,0,60,1,81029,10790,504,39,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,19,1,107923,2424,24,135,4], ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,19,0,91590,2005,24,110,3],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,3,0,76490,1980,369,31,12], ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,56,2,61149,3914,692,30,11],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,53,0,0,4347,15134,31,3], ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,57,0,0,4410,17059,32,3],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",0,0,15,0,113742,3448,144,48,2], ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",0,0,15,0,86379,2071,24,36,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,50,0,1132,6938,196,3,1], ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,50,0,0,6877,135,0,1],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",1582.42,1,52,0,198239,4882,115,197,7], ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",1827.12,1,68,0,125101,9355,115,125,6],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",1921.21,1,63,0,36646,5595,157,54,13], ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,65,0,26395,7425,67,37,9],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",884.56,1,56,0,96817,10407,339,153,6], ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,63,0,103636,9247,100,151,6],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,58,0,224940,4816,131,57,5], ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,55,0,185661,6408,233,40,4],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,54,0,179380,3695,573,161,3], ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,58,0,114013,3511,389,114,2],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,14,0,0,3546,153,0,0], ["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,14,0,0,3417,24,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,62,0,64945,4286,817,75,4], ["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,64,0,61768,6768,1277,69,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,66,1,69829,2713,724,72,22], ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,61,0,53260,4339,361,56,16],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1189.54,1,65,1,183859,2521,459,69,13], ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,67,1,145265,3703,451,68,13],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null], ["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null] ["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
], ],
"enemies": {"enemy_1010_demon":[4,4,0,2365,200612],"enemy_1438_dspred":[10,6,4,20724,815120],"enemy_1521_dslily":[1,0,0,80759,1003429]}, "enemies": {"enemy_1010_demon":[4,4,0,1913,200612],"enemy_1438_dspred":[10,5,5,22724,648434],"enemy_1521_dslily":[1,0,0,93299,798117]},
"hooks": [1,64,6000,1204,1275,3102,3214,5218,42,48,130,128,14,32649,1247,15,15,4,1,44,0,22,8], "hooks": [1,66,6000,1002,1068,2527,2646,4783,54,61,108,107,10,27478,1110,16,15,5,1,57,0,24,2],
"events": {"atk":1422,"deploy":49,"die":44,"dmg":3203,"engage":18,"fx":852,"heal":212,"leak":4,"skill":258,"spawn":37,"status":817}, "events": {"atk":1226,"deploy":51,"die":56,"dmg":2639,"engage":17,"fx":653,"heal":160,"leak":5,"skill":215,"spawn":37,"status":755},
"fx": { "fx": {
"aoe": 22, "aoe": 21,
"appear": 11, "appear": 9,
"blink": 2, "blink": 2,
"burst": 22, "burst": 24,
"charge": 13, "charge": 9,
"counter": 14, "counter": 10,
"crit": 76, "crit": 44,
"dilemma": 5, "dilemma": 4,
"disappear": 11, "disappear": 9,
"displace": 28, "displace": 28,
"dodge": 8, "dodge": 2,
"explosion": 13, "explosion": 13,
"frostNova": 22, "frostNova": 16,
"grow": 1, "grow": 1,
"iceSpike": 106, "iceSpike": 88,
"overclock": 1, "overclock": 1,
"pull": 4, "pull": 4,
"quadShot": 3, "quadShot": 2,
"ripple": 14, "ripple": 9,
"rockfall": 14, "rockfall": 11,
"sentry": 2, "sentry": 4,
"sermon": 2, "sermon": 1,
"shadowWeave": 7, "shadowWeave": 5,
"shield": 4, "shield": 3,
"soul": 2, "soul": 3,
"sp": 15, "sp": 9,
"stealth": 7, "stealth": 6,
"swordRain": 32, "swordRain": 40,
"swordStorm": 6, "swordStorm": 6,
"tentacle": 6, "tentacle": 6,
"tide": 5, "tide": 5,
"tornado": 4, "tornado": 4,
"tornadoPulse": 20, "tornadoPulse": 20,
"weightless": 4, "weightless": 4,
"zone": 346 "zone": 230
}, },
"snaps": "5dcb7728 ddb8e20d 3f32c53b b98db57d a6836556 681bc60e 94680558 f0805b17 5e447379 90906ab2 3cb79e2c 549d2d6d b9ccface 793eaba3 fdaab791 0167ee7d 2bdb20d3 5f084543 14b87276 5f72f51a" "snaps": "55a3d863 705ac967 e815eea0 1a348247 c3de7555 507edda3 3afb1adb 03717bd5 8abcf4ac b28c884f 25956015 b1d7cf6f 1c11c8b3 a11f49b2 b58e3f47 632c829c a5e0d79c da3519b8 783a1f55 c42321eb"
}, },
"boss-boss_4-solo": { "boss-boss_4-solo": {
"about": "盐风主教昆图斯 solo pool 1050000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "盐风主教昆图斯 solo pool 1050000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -614,86 +652,86 @@
"time": 200, "time": 200,
"ticks": 6000, "ticks": 6000,
"reason": "forced", "reason": "forced",
"killed": 5, "killed": 4,
"total": 6, "total": 6,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 2625306, "bossHpLeft": 2680590,
"rngDraws": 696, "rngDraws": 559,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":5,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1185295,"bossDmg":592165,"heal":491,"deaths":3,"layerGains":{}}, "p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1051226,"bossDmg":642651,"heal":3896,"deaths":5,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1405740,"bossDmg":762529,"heal":681,"deaths":11,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1151422,"bossDmg":656759,"heal":1055,"deaths":12,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,2,167588,0,0,41,3], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,2,107043,0,0,26,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,1,0,0,0,0,0,0], ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,1,0,0,0,0,0,0],
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,5,1,54262,0,0,0,15], ["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,5,0,38065,0,0,0,13],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,1,336,0,0,1,1], ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,0,2464,0,0,3,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,306280,0,491,75,12], ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,2,332249,0,1482,73,12],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,209403,0,0,73,6], ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,198299,0,0,75,6],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,15,0,55551,0,0,88,3], ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,15,0,28901,1629,0,50,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,50,0,325660,1611,0,84,29], ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,47,0,287701,3222,0,85,28],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,39,0,5827,3220,0,0,0], ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,49,0,2900,3220,2414,4,1],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,60388,0,0,48,2], ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,53605,0,0,40,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,38,0,0,12169,268,0,1], ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,46,0,0,12182,281,0,1],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,43,0,212750,8595,321,245,6], ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,41,0,136097,9003,729,279,6],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,6,0,0,0,0,0,0], ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,6,0,0,0,0,0,0],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,51,0,70300,8045,25,154,6], ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,43,0,57804,9849,44,187,5],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,233538,0,0,116,6], ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,239542,0,0,118,6],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,486378,0,0,138,2], ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,456796,0,0,132,2],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,0,0,0,0,0], ["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,0,0,0,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,16,0,80747,0,0,167,6], ["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,48,0,37231,4464,0,91,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,47,0,123467,3978,0,112,28], ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,44,0,120920,3978,0,113,27],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,48,0,198560,3319,67,63,12], ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,52,0,103033,3252,0,33,6],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null], ["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null] ["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_10028_vtswd": [4,4,0,0,0], "enemy_10028_vtswd": [5,5,0,0,0],
"enemy_10029_vtshld": [2,2,0,0,0], "enemy_10029_vtshld": [2,2,0,0,0],
"enemy_10030_vtwand": [2,2,0,0,0], "enemy_10030_vtwand": [1,1,0,0,0],
"enemy_10085_hllevi_2": [2,1,1,0,1035729], "enemy_10085_hllevi_2": [2,0,2,0,702625],
"enemy_1010_demon": [4,4,0,12169,200612], "enemy_1010_demon": [4,4,0,15134,200612],
"enemy_9032_aclionk": [1,0,0,28767,1354694] "enemy_9032_aclionk": [1,0,0,35665,1299410]
}, },
"hooks": [1,49,6000,1346,1386,2558,2587,515,27,30,138,136,0,39195,1495,3,15,1,1,23,0,6,0], "hooks": [1,50,6000,1291,1314,2325,2345,811,29,33,125,123,0,36836,1303,4,15,2,1,25,0,5,0],
"events": {"atk":1422,"deploy":34,"die":29,"dmg":2571,"engage":14,"fx":716,"heal":4,"leak":1,"skill":274,"spawn":37,"status":1085}, "events": {"atk":1354,"deploy":35,"die":31,"dmg":2324,"engage":14,"fx":616,"heal":20,"leak":2,"skill":248,"spawn":37,"status":889},
"fx": { "fx": {
"aoe": 51, "aoe": 48,
"appear": 4, "appear": 4,
"blink": 2, "blink": 2,
"burst": 6, "burst": 5,
"crit": 87, "crit": 75,
"dilemma": 6, "dilemma": 6,
"disappear": 4, "disappear": 4,
"displace": 19, "displace": 19,
"equip": 10, "equip": 9,
"explosion": 12, "explosion": 6,
"frostNova": 28, "frostNova": 27,
"garrisonGrant": 1, "garrisonGrant": 1,
"iceSpike": 83, "iceSpike": 44,
"overclock": 1, "overclock": 1,
"pull": 6, "pull": 4,
"quadShot": 2, "quadShot": 2,
"revive": 2, "revive": 2,
"ripple": 18, "ripple": 18,
"sentry": 3, "sentry": 3,
"shadowWeave": 4, "shadowWeave": 2,
"shield": 1, "shield": 1,
"soul": 6, "soul": 5,
"sp": 20, "sp": 18,
"stealth": 6, "stealth": 6,
"sword": 2, "sword": 4,
"swordRain": 49, "swordRain": 21,
"swordStorm": 6, "swordStorm": 5,
"telegraph": 16, "telegraph": 14,
"tornado": 6, "tornado": 4,
"tornadoPulse": 30, "tornadoPulse": 20,
"vest": 2, "vest": 2,
"weightless": 6, "weightless": 6,
"zone": 217 "zone": 228
}, },
"snaps": "cacb813f 39918738 3f7856dd f356867c 0eddd32e ba395ef8 34755e05 0812b2a3 e7dee4b0 6c23b2fa 776da0eb 448f9baa 07467855 055c9e65 0346714b f791a88b deff2a04 42b5f4bd cf3b3ba4 058edc46" "snaps": "a5dd1d4a baa7b7db 7c505a8e a4224e3d d53319fe 5b811158 536cda3e 52938381 3a7db0e7 6d0ad629 52d45c14 98ee7b24 5d759658 0207119c a227cfea dc0605ed 7a6f9851 53a205a7 a69e6594 bc79ec80"
}, },
"boss-boss_6-solo": { "boss-boss_6-solo": {
"about": "阿利斯泰尔,帝国余晖 solo pool 995000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "阿利斯泰尔,帝国余晖 solo pool 995000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -755,58 +793,60 @@
"round": 14, "round": 14,
"seed": 120974514, "seed": 120974514,
"waveId": "act1autochess_h07_07", "waveId": "act1autochess_h07_07",
"time": 153.2, "time": 144.33,
"ticks": 4596, "ticks": 4330,
"reason": "cleared", "reason": "cleared",
"killed": 16, "killed": 16,
"total": 16, "total": 16,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 0, "bossHpLeft": 0,
"rngDraws": 616, "rngDraws": 666,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":16,"total":16,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":657821,"bossDmg":550703,"heal":25237,"deaths":1,"layerGains":{}}, "p1": {"killed":16,"total":16,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":630171,"bossDmg":586902,"heal":20762,"deaths":0,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1815159,"bossDmg":1449297,"heal":19812,"deaths":5,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1842809,"bossDmg":1413098,"heal":34015,"deaths":0,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3430,1,5,0,0,0,0,0,0], ["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",4472.72,1,5,0,0,0,0,0,0],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,7,0,0,0,0,0,0], ["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",3832.46,1,7,0,0,0,0,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,11,0,0,0,0,0,0], ["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",4043.7,1,11,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1470,1,1,1,191195,0,0,83,0], ["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1916.88,1,1,1,185852,0,0,78,0],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,9,0,0,0,0,0,0], ["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",2520.63,1,9,0,0,0,0,0,0],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1598,1,17,0,39305,17679,1300,0,4], ["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",2083.79,1,17,0,32940,15589,1256,0,4],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1442,1,3,0,0,0,0,0,0], ["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1880.37,1,3,0,0,0,0,0,0],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,13,0,145558,481,23170,106,3], ["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",2175.07,1,13,0,113952,0,17614,101,3],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,15,1,214113,862,0,40,40], ["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1723.89,1,15,1,193562,539,0,38,38],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",0,0,19,0,54307,12010,767,92,3], ["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",2468.47,1,19,0,81362,11533,1148,139,4],
["p1",11,"token","token_10017_skadi2_dedant",null,null,130.4,0,22,0,0,0,0,0,null], ["p1",11,"token","token_10017_skadi2_dedant",null,null,130.4,0,22,0,0,0,0,0,null],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,20,0,5792,2876,0,17,1], ["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,20,0,5321,2876,0,18,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",2913.79,1,26,7,380120,3165,0,142,3], ["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4502.02,1,26,4,275746,0,0,93,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,2,0,0,0,0,0,0], ["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",3608.17,1,2,0,0,0,0,0,0],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",2314,1,6,0,0,0,0,0,0], ["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",3017.46,1,6,0,0,0,0,0,0],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",4556.87,1,31,1,173246,2773,0,63,3], ["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",7020.72,1,31,2,91620,12855,0,29,3],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",0,0,51,3,220347,11041,0,35,4], ["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2898.35,1,16,4,305737,8640,0,53,3],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2192.85,1,21,3,701971,6761,5337,235,5], ["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2888.36,1,21,4,841731,3871,1843,250,5],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",1899,1,8,0,0,0,0,0,0], ["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2476.3,1,8,0,0,0,0,0,0],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,12,0,110676,0,0,89,3], ["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1876.46,1,12,0,108450,0,0,89,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,14,1,228800,0,0,62,2], ["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",2009.46,1,14,0,219525,0,0,55,2],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",0,0,49,0,0,4230,14475,21,1], ["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1970.34,1,18,0,0,1120,32171,49,2],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,34,0,4801,1724,0,8,null], ["p1","~1","token","token_10030_mlyss_wtrman",null,null,893.08,0,34,1,6541,1574,743,16,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,41,0,519,1322,0,2,1], ["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,41,0,3698,2070,0,8,1],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,42,0,0,0,0,0,null], ["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,42,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,45,0,2231,1322,0,7,1], ["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,46,0,2081,1322,0,6,1],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,50,0,0,0,0,0,null] ["p1","~2","token","token_10017_skadi2_dedant",null,null,130.4,0,48,0,0,0,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1048.86,1,49,0,3949,1128,0,11,1],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1723.89,1,50,0,912,0,0,2,null]
], ],
"enemies": { "enemies": {
"enemy_1067_snslime": [8,8,0,12674,111795], "enemy_1067_snslime": [8,8,0,9423,111795],
"enemy_1081_sotisd": [4,4,0,5052,257989], "enemy_1081_sotisd": [4,4,0,708,257989],
"enemy_1249_lysdb_2": [4,4,0,0,103196], "enemy_1249_lysdb_2": [4,4,0,0,103196],
"enemy_9033_acdeer": [1,1,0,31851,2000000] "enemy_9033_acdeer": [1,1,0,32950,2000000]
}, },
"hooks": [1,49,4596,1011,1011,2649,2941,1623,27,32,74,70,182,26660,1344,6,17,0,1,26,0,14,0], "hooks": [1,48,4330,1042,1042,2602,2869,2311,20,26,74,65,201,28101,1210,4,17,0,1,19,0,10,0],
"events": {"atk":1140,"deploy":32,"die":32,"dmg":2682,"engage":14,"fx":420,"heal":235,"skill":144,"spawn":44,"status":502}, "events": {"atk":1213,"deploy":31,"die":26,"dmg":2635,"engage":16,"fx":419,"heal":416,"skill":139,"spawn":46,"status":432},
"fx": {"airstrike":63,"beam":3,"blink":2,"burst":14,"column":39,"copy":3,"dp":3,"drone":234,"droneLock":10,"drones":2,"explode":7,"garrisonGrant":1,"link":3,"phase":1,"scorchBurst":6,"shieldBreak":4,"snow":5,"split":1,"steal":4,"sunBurst":4,"talent":4,"tide":4,"wanted":3}, "fx": {"airstrike":72,"beam":3,"blink":2,"burst":10,"column":36,"copy":4,"dp":4,"drone":235,"droneLock":12,"drones":2,"explode":6,"garrisonGrant":1,"link":1,"phase":1,"shieldBreak":4,"snow":5,"split":2,"steal":5,"sunBurst":4,"talent":4,"tide":4,"wanted":2},
"snaps": "527acd3d 92bc6ca9 876af6f6 ea8c9a0e 8e9b8505 e81eae9b 0162cc52 7bfd274c a0189411 57757a4a eef674d5 623bffab 79e4a2a5 f38fb5dc 21a35db2 55c0fad0" "snaps": "3159bc57 5a868dfc 1317f752 766b1ac3 ae4704fd 8b8561f0 0cf3e025 1cc4fae8 2ab8c69e 1c36a709 27dce433 db53cc68 922ea938 44d1d981 4325d4fb"
}, },
"boss-boss_7-solo": { "boss-boss_7-solo": {
"about": "“萨米的意志” solo pool 500000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b", "about": "“萨米的意志” solo pool 500000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
@@ -1179,62 +1219,63 @@
"killed": 12, "killed": 12,
"total": 12, "total": 12,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 6820572, "bossHpLeft": 6905955,
"rngDraws": 999, "rngDraws": 1001,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":378748,"bossDmg":211503,"heal":8474,"deaths":1,"layerGains":{}}, "p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":358279,"bossDmg":203660,"heal":4886,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":735184,"bossDmg":567925,"heal":8676,"deaths":6,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":670269,"bossDmg":490385,"heal":8820,"deaths":8,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,5,0,0,0,0,0,0], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,5,0,0,0,0,0,0],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,9,0,0,0,0,0,0], ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,9,0,0,0,0,0,0],
["p1",3,"op","chess_char_5_14_b",1,"none",3489.5,1,15,0,0,0,0,0,0], ["p1",3,"op","chess_char_5_14_b",1,"none",3489.5,1,15,0,0,0,0,0,0],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,17,2,18743,6114,2287,41,1], ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,17,1,24417,1893,995,40,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,2,158666,0,5115,25,5], ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,2,129281,0,2712,20,4],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,2,60570,957,0,64,6], ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,1,62301,949,20,64,6],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,48,0,95409,3032,116,219,6], ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,51,2,105321,2848,231,226,6],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,3,0,0,0,0,0,0], ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,3,0,0,0,0,0,0],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,7,0,0,0,957,1,1], ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,7,0,0,0,929,2,1],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,13,0,45360,0,0,30,3], ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,13,0,36960,0,0,30,3],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,6,0,0,0,0,0,0], ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,6,0,0,0,0,0,0],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",2149,1,51,0,113544,5807,112,174,5], ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",2149,1,54,0,113346,5807,112,174,5],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,47,0,7612,5859,855,7,3], ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,49,0,4,5859,855,2,2],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,31,0,108979,0,0,249,2], ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,50,0,42778,7059,344,190,5],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1135.93,1,18,5,202412,2387,745,84,5], ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1268.93,1,18,5,204995,2254,745,84,5],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,12,2,158149,0,0,271,4], ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,12,3,160077,0,0,271,4],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,20,1,9410,2337,673,8,2], ["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1449.33,1,20,0,9408,888,673,8,2],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1142.27,1,22,0,50507,7380,6290,122,5], ["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1532.27,1,22,0,50178,6810,6090,122,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,4,0,29201,0,0,31,10], ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,4,1,30075,0,0,34,11],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,8,2,55371,0,0,22,6], ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,8,1,59410,0,0,24,6],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,23,0,0,0,0,0,null], ["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,23,0,0,0,0,0,null],
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,24,0,0,0,0,0,null], ["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,24,0,0,0,0,0,null],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null] ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_1402_tgshd_2": [2,2,0,5169,173864], "enemy_1402_tgshd_2": [2,2,0,6652,173864],
"enemy_9010_acpupp": [2,2,0,3869,160520], "enemy_9010_acpupp": [2,2,0,1619,160520],
"enemy_9021_acduml_2": [1,0,0,343,779428], "enemy_9021_acduml_2": [1,0,0,0,694045],
"enemy_9022_acdumm": [1,0,0,453,0], "enemy_9022_acdumm": [1,0,0,877,0],
"enemy_9023_acdums": [12,12,0,18180,120] "enemy_9023_acdums": [12,12,0,19360,120]
}, },
"hooks": [1,49,6000,1174,1314,2552,2708,5457,23,25,64,60,0,30019,1462,5,18,0,1,25,0,6,0], "hooks": [1,52,6000,1110,1260,2516,2697,5451,25,27,65,62,0,30164,1499,5,18,0,1,27,0,7,1],
"events": {"atk":1269,"deploy":29,"die":25,"dmg":2697,"engage":14,"fx":970,"heal":49,"skill":124,"spawn":42,"status":564}, "events": {"atk":1215,"deploy":32,"die":27,"dmg":2691,"engage":14,"fx":1023,"heal":31,"skill":127,"spawn":42,"status":602},
"fx": { "fx": {
"beam": 13, "beam": 13,
"blink": 2, "blink": 2,
"burst": 6, "burst": 7,
"charge": 3, "charge": 2,
"crit": 126, "crit": 135,
"dilemma": 5, "dilemma": 5,
"dodge": 1,
"ember": 2, "ember": 2,
"explode": 191, "explode": 195,
"explosion": 6, "explosion": 6,
"expose": 182, "expose": 200,
"frostNova": 10, "frostNova": 11,
"garrisonGrant": 1, "garrisonGrant": 1,
"iceSpike": 182, "iceSpike": 190,
"levitate": 5, "levitate": 5,
"phase": 33, "phase": 33,
"pull": 4, "pull": 4,
@@ -1245,19 +1286,18 @@
"shield": 1, "shield": 1,
"shieldBreak": 1, "shieldBreak": 1,
"slippery": 2, "slippery": 2,
"sp": 1,
"splash": 2, "splash": 2,
"stealth": 5, "stealth": 5,
"summon": 8, "summon": 8,
"swordRain": 7, "swordRain": 37,
"swordStorm": 2, "swordStorm": 5,
"telegraph": 2, "telegraph": 2,
"tornado": 5, "tornado": 4,
"tornadoPulse": 20, "tornadoPulse": 20,
"weightless": 6, "weightless": 6,
"zone": 95 "zone": 76
}, },
"snaps": "2d13cea7 e9e5501a 1907eb82 5ba81caf 5ee8b538 524f35a5 1425a1ad a9b7b4be 5b58af92 8c83af8a 2d164054 4c1fa05e ab022cf5 36aac114 a5eadb46 3f794fc7 1c295490 94ffeeec 47451168 f6114af3" "snaps": "b2c8103f 91059bdb 14558c87 9a8c7aca 84358f47 3307ce1d c68d6ed2 f8846121 9987dee1 d785c1eb 47303c18 d7e164d1 f3923884 43b344b2 01ede284 10404dbc a9d562ec 8a8c176e 62612be1 a62fd897"
}, },
"hidden-boss_10-solo": { "hidden-boss_10-solo": {
"about": "假想敌:管 solo pool 1900000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "假想敌:管 solo pool 1900000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
+15 -15
View File
@@ -2460,18 +2460,18 @@
"total": 65, "total": 65,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": null, "bossHpLeft": null,
"rngDraws": 299, "rngDraws": 300,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":27,"total":65,"perfect":false,"leaks":38,"leaksUncounted":0,"coins":4,"dmg":520678,"bossDmg":0,"heal":2131,"deaths":24,"layerGains":{}}}, "players": {"p1":{"killed":27,"total":65,"perfect":false,"leaks":38,"leaksUncounted":0,"coins":4,"dmg":532691,"bossDmg":0,"heal":2131,"deaths":24,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_4_01_a",1,null,0,0,3,0,1745,2638,0,25,1], ["p1",1,"op","chess_char_4_01_a",1,null,0,0,3,0,2694,2638,0,47,1],
["p1",2,"op","chess_char_4_17_a",2,null,0,0,6,0,3603,2849,0,5,1], ["p1",2,"op","chess_char_4_17_a",2,null,0,0,6,0,10584,2849,0,16,1],
["p1",3,"op","chess_char_4_18_a",2,null,0,0,8,1,13840,3440,573,16,1], ["p1",3,"op","chess_char_4_18_a",2,null,0,0,8,1,15145,3440,573,17,1],
["p1",4,"op","chess_char_4_04_a",2,null,0,0,94,2,31268,7040,0,24,4], ["p1",4,"op","chess_char_4_04_a",2,null,0,0,94,2,31268,7040,0,24,4],
["p1",5,"op","chess_char_4_07_a",2,null,0,0,83,0,6702,7655,0,9,1], ["p1",5,"op","chess_char_4_07_a",2,null,0,0,83,0,6702,7655,0,9,1],
["p1",6,"op","chess_char_4_16_a",1,null,857.6,1,96,3,66532,8961,1097,21,9], ["p1",6,"op","chess_char_4_16_a",1,null,857.6,1,96,3,66532,8961,1097,21,9],
["p1",7,"op","chess_char_4_09_a",2,null,969.81,1,87,0,19950,1702,0,21,1], ["p1",7,"op","chess_char_4_09_a",2,null,969.81,1,87,0,19950,1702,0,21,1],
["p1",8,"op","chess_char_3_19_b",1,"none",0,0,4,7,58645,1648,0,68,1], ["p1",8,"op","chess_char_3_19_b",1,"none",0,0,4,7,61423,1648,0,72,1],
["p1",9,"op","chess_char_3_21_b",1,"uniequip_004_archet",0,0,95,6,166061,3180,0,86,9], ["p1",9,"op","chess_char_3_21_b",1,"uniequip_004_archet",0,0,95,6,166061,3180,0,86,9],
["p1",10,"op","chess_char_4_02_a",1,null,0,0,86,2,57313,2856,0,16,1], ["p1",10,"op","chess_char_4_02_a",1,null,0,0,86,2,57313,2856,0,16,1],
["p1",11,"op","chess_char_4_12_a",1,null,0,0,11,4,37850,2214,460,36,1], ["p1",11,"op","chess_char_4_12_a",1,null,0,0,11,4,37850,2214,460,36,1],
@@ -2482,21 +2482,21 @@
], ],
"enemies": { "enemies": {
"enemy_1005_yokai": [24,12,12,0,46985], "enemy_1005_yokai": [24,12,12,0,46985],
"enemy_1005_yokai_2": [27,10,13,8055,87836], "enemy_1005_yokai_2": [27,10,13,8037,87836],
"enemy_1005_yokai_3": [6,1,4,19583,242123], "enemy_1005_yokai_3": [6,1,4,18726,244901],
"enemy_10065_ftzlc": [1,1,0,0,16339], "enemy_10065_ftzlc": [1,1,0,0,16339],
"enemy_10162_mnctpt": [1,0,1,7074,47489], "enemy_10162_mnctpt": [1,0,1,6996,48110],
"enemy_1119_vofsd": [1,1,0,170,47298], "enemy_1119_vofsd": [1,1,0,170,47298],
"enemy_1196_msfyin_2": [1,1,0,0,3], "enemy_1196_msfyin_2": [1,1,0,0,3],
"enemy_1254_lypa_2": [1,0,1,8330,24869], "enemy_1254_lypa_2": [1,0,1,9283,32765],
"enemy_1367_dseed": [1,0,1,0,1582], "enemy_1367_dseed": [1,0,1,0,2300],
"enemy_1517_xi": [1,0,1,6203,6145], "enemy_1517_xi": [1,0,1,6203,6145],
"enemy_9016_acstmr": [1,1,0,0,8] "enemy_9016_acstmr": [1,1,0,0,8]
}, },
"hooks": [1,95,5100,608,608,959,992,323,53,86,31,31,15,9466,320,13,65,33,1,63,0,2,21], "hooks": [1,95,5100,640,640,1007,1043,323,53,86,31,31,21,9432,318,15,65,33,1,63,0,2,21],
"events": {"atk":684,"bounty":2,"deploy":28,"die":53,"dmg":984,"engage":12,"fx":104,"heal":2,"leak":33,"skill":62,"spawn":80,"status":184}, "events": {"atk":725,"bounty":2,"deploy":28,"die":53,"dmg":1036,"engage":12,"fx":105,"heal":2,"leak":33,"skill":62,"spawn":80,"status":184},
"fx": {"beam":4,"buff":2,"burst":2,"coldWind":1,"dodge":21,"dp":1,"explode":1,"iceSpike":1,"overclock":1,"overload":1,"palsy":1,"revive":3,"ripple":1,"rockfall":1,"sentry":6,"shadowWeave":14,"shield":7,"shieldBreak":7,"stone":1,"swordStorm":9,"telegraph":3,"volley":9,"wake":1,"wolfShadow":2,"wolfShadowLost":3,"zone":1}, "fx": {"beam":4,"block":1,"buff":2,"burst":2,"coldWind":1,"dodge":21,"dp":1,"explode":1,"iceSpike":1,"overclock":1,"overload":1,"palsy":1,"revive":3,"ripple":1,"rockfall":1,"sentry":6,"shadowWeave":14,"shield":7,"shieldBreak":7,"stone":1,"swordStorm":9,"telegraph":3,"volley":9,"wake":1,"wolfShadow":2,"wolfShadowLost":3,"zone":1},
"snaps": "b82d753d 9cf4ba99 fcad021b 654db731 d77e82d7 9f8f33f9 017d7bbc 96acf49e 6fb2359b 1c86069b 9ddc6975 6ce15810 e8f0b7f7 11dd93da a2b48e05 18e63893 1e4df384" "snaps": "b82d753d 9cf4ba99 fcad021b 654db731 bb6a3dff 378d28e1 47603a0d 144d6b03 599f2544 522197cf 268e0f04 30a6df9f d0f8683b 3c7d6f84 a71a9576 12b08084 610181fa"
}, },
"roster-041": { "roster-041": {
"about": "pass 2 band 5: chess_char_4_23_a chess_char_4_01_b chess_char_4_19_a chess_char_4_22_a chess_char_4_04_b chess_char_4_07_b chess_char_4_09_b chess_char_4_20_a chess_char_4_24_a chess_char_4_25_a chess_char_4_02_b chess_char_4_12_b", "about": "pass 2 band 5: chess_char_4_23_a chess_char_4_01_b chess_char_4_19_a chess_char_4_22_a chess_char_4_04_b chess_char_4_07_b chess_char_4_09_b chess_char_4_20_a chess_char_4_24_a chess_char_4_25_a chess_char_4_02_b chess_char_4_12_b",