feedback4: merge master — PRs #150 #66 #146 #74 #72 #103 #149 #109 #160

Golden regenerated on the merged code and attributed by reverting each gameplay PR in a scratch tree: #66 alone moves the fights of roster-008/010/012/022/027/028/031/040, the boss pairs (boss_2/4/6, hidden boss_10) and the matches coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, solo-HARD-9-boosted (灵巧's aura no longer comes from merchant exits and retreats; hidden-layer bonds keep their internal layers); #109 changes the skill state of its deploy-timed skills (units / hooks / status events / snapshots); #160 adds the 0.5 s skill events of deploy passives (events.skill only in roster-001/006/025/049). The remaining scenarios listed by the full regenerate are those three effects together.
This commit is contained in:
sganggs committed 2026-10-05 22:41:02 +08:00
commit 02b76989fa
49 files changed
+1974 -620

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"version":1,"hash":"a7a431fb0726","generator":"tools/fetch-assets.mjs","stats":{"files":4016,"bytes":284530642,"chars":138,"charsWithBack":135,"enemies":252,"enemiesWithSpine":251,"tokens":20,"tokensWithSpine":13,"spineModels":529,"bonds":23,"items":59,"bands":40,"skills":317,"ui":601,"sfxUnits":382},"chars":{"char_1012_skadi2":{"avatar":"/assets/char/avatar/char_1012_skadi2.png","avatarE2":"/assets/char/avatar/char_1012_skadi2_2.png","portrait":"/assets/char/portrait/char_1012_skadi2_1.png","portraitE2":"/assets/char/portrait/char_1012_skadi2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.skel","atlas":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.atlas","textures":["/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Skill_1_Loop","end":null,"via":"skill"},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null},"die":"Die","move":null,"stun":{"begin":"Stun_Begin","loop":"Stun","end":null},"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":null},"1":{"begin":"Skill_2_Begin","loop":"Skill_2_Loop","end":null,"index":1,"idle":null},"2":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null}}},"animations":{"Default":0,"Die":1,"Idle":8.767,"Skill_1_Begin":3.2,"Skill_1_Loop":8.767,"Skill_2_Begin":3.933,"Skill_2_Loop":3.333,"Skill_3_Begin":4.4,"Skill_3_Loop":8.767,"Start":1,"Stun":2,"Stun_Begin":1},"events":["OnAttack","OnStart"],"hits":{},"bounds":{"x":-190.586,"y":-25.785,"width":393.516,"height":443.426}}}},"char_1014_nearl2":{"avatar":"/assets/char/avatar/char_1014_nearl2.png","avatarE2":"/assets/char/avatar/char_1014_nearl2_2.png","portrait":"/assets/char/portrait/char_1014_nearl2_1.png","portraitE2":"/assets/char/portrait/char_1014_nearl2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":null,"loop":"Skill_2_Die","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Die":1,"Idle":2.667,"Skill_3":1.5,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_1_Loop_Down":1.5,"Skill_2_Die":1.167,"Skill_3_Down":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_3":[0.5],"Skill_1_Loop":[0.467],"Skill_1_Loop_Down":[0.467],"Skill_3_Down":[0.5]},"bounds":{"x":-400.77,"y":-461.007,"width":720.545,"height":872.242}},"back":{"skel":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End","via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Idle":2.667,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_3":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_1_Loop":[0.467],"Skill_3":[0.5]},"bounds":null}}},"char_1016_agoat2":{"avatar":"/assets/char/avatar/char_1016_agoat2.png","avatarE2":"/assets/char/avatar/char_1016_agoat2_2.png","portrait":"/assets/char/portrait/char_1016_agoat2_1.png","portraitE2":"/assets/char/portrait/char_1016_agoat2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.skel","atlas":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.atlas","textures":["/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":"Skill_3_End","index":2,"idle":"Skill_3_Idle"},"diLine truncated
{"version":1,"hash":"8cb555456b2f","generator":"tools/fetch-assets.mjs","stats":{"files":4019,"bytes":289568237,"chars":138,"charsWithBack":135,"enemies":252,"enemiesWithSpine":251,"tokens":20,"tokensWithSpine":13,"spineModels":529,"bonds":23,"items":59,"bands":40,"skills":317,"ui":601,"sfxUnits":382},"chars":{"char_1012_skadi2":{"avatar":"/assets/char/avatar/char_1012_skadi2.png","avatarE2":"/assets/char/avatar/char_1012_skadi2_2.png","portrait":"/assets/char/portrait/char_1012_skadi2_1.png","portraitE2":"/assets/char/portrait/char_1012_skadi2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.skel","atlas":"/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.atlas","textures":["/assets/spine/op/char_1012_skadi2/front/char_1012_skadi2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Skill_1_Loop","end":null,"via":"skill"},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null},"die":"Die","move":null,"stun":{"begin":"Stun_Begin","loop":"Stun","end":null},"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":null},"1":{"begin":"Skill_2_Begin","loop":"Skill_2_Loop","end":null,"index":1,"idle":null},"2":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":null,"index":2,"idle":null}}},"animations":{"Default":0,"Die":1,"Idle":8.767,"Skill_1_Begin":3.2,"Skill_1_Loop":8.767,"Skill_2_Begin":3.933,"Skill_2_Loop":3.333,"Skill_3_Begin":4.4,"Skill_3_Loop":8.767,"Start":1,"Stun":2,"Stun_Begin":1},"events":["OnAttack","OnStart"],"hits":{},"bounds":{"x":-190.586,"y":-25.785,"width":393.516,"height":443.426}}}},"char_1014_nearl2":{"avatar":"/assets/char/avatar/char_1014_nearl2.png","avatarE2":"/assets/char/avatar/char_1014_nearl2_2.png","portrait":"/assets/char/portrait/char_1014_nearl2_1.png","portraitE2":"/assets/char/portrait/char_1014_nearl2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/front/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":null,"loop":"Skill_2_Die","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Die":1,"Idle":2.667,"Skill_3":1.5,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_1_Loop_Down":1.5,"Skill_2_Die":1.167,"Skill_3_Down":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_3":[0.5],"Skill_1_Loop":[0.467],"Skill_1_Loop_Down":[0.467],"Skill_3_Down":[0.5]},"bounds":{"x":-400.77,"y":-461.007,"width":720.545,"height":872.242}},"back":{"skel":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.skel","atlas":"/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.atlas","textures":["/assets/spine/op/char_1014_nearl2/back/char_1014_nearl2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End"},"attackDown":null,"skill":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":"Skill_1_Begin","loop":"Skill_1_Loop","end":null,"index":0,"idle":"Skill_1_Idle"},"1":{"begin":"Attack_Begin","loop":"Attack_Loop","end":"Attack_End","via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":"Skill_3_Idle"}}},"animations":{"Attack_Begin":0.1,"Attack_End":0.167,"Attack_Loop":1.5,"Default":0,"Idle":2.667,"Skill_1_Begin":0.1,"Skill_1_Idle":1,"Skill_1_Loop":1.5,"Skill_3":1.5,"Skill_3_Idle":1.333,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack_Loop":[0.433],"Skill_1_Loop":[0.467],"Skill_3":[0.5]},"bounds":null}}},"char_1016_agoat2":{"avatar":"/assets/char/avatar/char_1016_agoat2.png","avatarE2":"/assets/char/avatar/char_1016_agoat2_2.png","portrait":"/assets/char/portrait/char_1016_agoat2_1.png","portraitE2":"/assets/char/portrait/char_1016_agoat2_2.png","spine":{"front":{"skel":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.skel","atlas":"/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.atlas","textures":["/assets/spine/op/char_1016_agoat2/front/char_1016_agoat2.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":"Skill_3_Begin","loop":"Skill_3_Loop","end":"Skill_3_End","index":2,"idle":"Skill_3_Idle"},"diLine truncated
+19 -10
View File
@@ -84,7 +84,7 @@ The research JSONs in `docs/research/` (03, 05, 07) define **which** ids are nee
| Token Spine | fexli: the default model, or else the first skin variant (`spine/{tokenId}/{variant}/Spine/`) | `spine/token/{tokenId}/{stem}.*` |
| Enemy Spine (PC build, premultiplied alpha) | isHarryh/Ark-Models `models_enemies/{key}/`, file names from `models_data.json` | `spine/enemy/{enemyId}/{stem}.*` |
| Enemy Spine that no dump carries (灼热源石虫 / 炽焰源石虫) | the local client only (`tools/local-extract/extract.py ENEMY_SPINES`, optional); never downloaded and never required: an overlay of the web alias (`enemies[id].spineLocal`) | `local/spine/enemy/{enemyId}/{stem}.*` (listed in `data/local-assets.json`) |
| BGM | AA2 `voice` branch `audio/sound_beta_2/music/**` | `audio/bgm/{file}.mp3` |
| BGM | AA2 `voice` branch `audio/sound_beta_2/music/**` (大厅/休整期 `act1autochess`, 开战 `act13side/m_bat_kazimierz2_{1,2}` — 骑士之日 / 无畏者; the 开战 track follows the round: `_2` 无畏者 rounds 1–7, `_1` 骑士之日 from round 8) | `audio/bgm/{file}.mp3` |
| SFX (UI, battle, per unit) | AA2 `voice` `audio/sound_beta_2/**`, mapped from `audio_data.json` banks | `audio/sfx/{same sub-path}.mp3` |
| Fonts: Bender Regular and Light, Novecento Wide | TimWangZi/The-font-of-Arknights | `public/fonts/*.{otf,ttf,woff2}`, `public/fonts/fonts.css` |
@@ -153,11 +153,13 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
// group + name of the same picture (the client takes the local one first)
prof: { icon: {caster…warrior}, large: {…}, battlecard: {…, token}, sub: {[subProfessionId]: url} },
audio: {
bgm: { lobby, prep, combat, unite?: { intro?, loop }, boss: { intro?, loop } },
// intro then crossfade to loop (1 s); `unite` = 联防's own track — the official
// escaped_single / escaped_multi levels declare `bgmEvent = corrosion` (卡西米尔 act13d5d0),
// so the rescue phase does not reuse the 作战's track (audio.js bgmKeyFor 'unite', falling
// back to `bgm.combat` for a manifest that lacks it)
bgm: { lobby, prep, combat, combatAlts?: [{ intro?, loop }, …], unite?: { intro?, loop }, boss: { intro?, loop } },
// intro then crossfade to loop (1 s); combatAlts = the 开战 tracks of the mode's own act, chosen by the
// round, not drawn: [0] = m_bat_kazimierz2_1 骑士之日 (rounds 8–13), [1] = m_bat_kazimierz2_2 无畏者
// (rounds 1–7) — audio.js combatTrackFor / bgmKeyFor 'combat:<i>', the same on every client;
// `unite` = 联防's own track — the official escaped_single / escaped_multi levels declare
// `bgmEvent = corrosion` (卡西米尔 act13d5d0), so the rescue phase does not reuse the 作战's track
// (audio.js bgmKeyFor 'unite', falling back to `bgm.combat` for a manifest that lacks it)
bossBgm: { [bossId]: { intro?, loop } }, // per-boss track of its R14/R15 level
sfx: {
ui: { click, back, confirm, tab, pick, drop, error, buy, sell, income, refresh, freeze, levelup,
@@ -268,10 +270,17 @@ Other renderer rules from research 07 §5.4–5.5:
- **Enemy aliases:** `enemies[id].spineAliasOf` means the model belongs to another enemy. Two cases:
- `_2` variants whose official prefab is the base one (鸭爵, 高普尼克, 流泪小子, 圆仔, 假想敌:胄, 假想敌:铳): the base model, as in the game.
- an enemy whose own model no dump carries: 灼热源石虫 / 炽焰源石虫 (`enemy_1305_mhslim` / `_2`) use the plain 源石虫 on
the web (`plan.mjs ENEMY_SPINE_ALIAS`). Their official skeletons only exist in the client's enemy art bundles
(`refs/arts/enm_art_*.ab`), so they are an optional **overlay**, `enemies[id].spineLocal` = `{ group, skel, atlas,
textures, pma, anims, animations, events, hits, bounds }` (file names in the `data/local-assets.json` group
`spine/enemy/{enemyId}`; the rest as a `spine` entry):
the web (`plan.mjs ENEMY_SPINE_ALIAS`). The reason is upstream: isHarryh/Ark-Models *indexes* `1305_mhslim` /
`1305_mhslim_2` but with an **empty `assetList`** — registered, never uploaded — so `arkModel()` finds no files for
them and the alias chain drops to `enemy_1007_slime`, a different enemy rather than a variant of it, which is why
the renderer tints that alias toward the slug's own colours. Their official skeletons only exist in the client's
enemy art bundles (`refs/arts/enm_art_*.ab`); the *mobile* build also ships them as
`enemy_spine/<enemyId>/<enemyId>.{skel,atlas,png}` (straight-alpha pages: no `pma: true` line), but its only public
mirror is a community wiki rather than a GitHub dump, so it is deliberately **not** an asset source — a third-party
site is not added to `sources.mjs` for two models, and the tinted alias stays the web model until a GitHub dump
carries them. The same official model from the local client is the optional **overlay**,
`enemies[id].spineLocal` = `{ group, skel, atlas, textures, pma, anims, animations, events, hits, bounds }` (file
names in the `data/local-assets.json` group `spine/enemy/{enemyId}`; the rest as a `spine` entry):
- `tools/local-extract/extract.py` writes the model to `public/assets/local/spine/enemy/{enemyId}/` (page textures
with their `[alpha]` texture merged in: premultiplied RGB + A like Ark-Models; the atlas gets `size:` and
`pma: true`) and lists its files in `data/local-assets.json`.
+31 -2
View File
@@ -281,7 +281,14 @@ Engine helpers (content must use these, never touch internals): `battle.dealDama
SP: `spType` `INCREASE_WITH_TIME` (+`spRecovery`/s), `INCREASE_WHEN_ATTACK` (+1 per attack), `INCREASE_WHEN_TAKEN_DAMAGE` (+1 per hit taken); starts at `initSp`; no SP gain while a duration skill is active. `maxChargeTime > 1` ⇒ charges.
Auto-trigger rules (`skillTrigger.rule` in data — the official 技能策略, PRTS 卫戍协议/帮助 §作战阶段 技能操作; §20.2): `DEFAULT` (SP ready AND about to attack/heal AND an enemy/injured ally inside the **initial** range, or an enemy the unit blocks; with `trigger.allies` + `hpAtMost` an ally of the skill grid at or below that HP ratio — 塞雷娅 S1 急救, whose cast replaces that attack and is withdrawn with its charge if the ally recovers first), `SKILL_RANGE` (a MANUAL skill with a 技能范围 of its own, not an attack-range change: any living enemy, stealthed ones included, on the skill range, checked every tick — "仅在技能范围内存在敌人(无视其不可选中)时释放技能"; 13 skills / 26 records by the official rule, 14 / 28 in the data with 余 S2's deviation, §22.10), `TAKE_DAMAGE` (on taking damage — any damage, 无来源 included, never a 流失; every MANUAL 重装 skill, §21.12 — but the six of the deliberate deviation, §21.29: 深巡 / 雷蛇 S2, 号角 S2 / S3 and 灰毫 S1 / S2 are `DEFAULT`, `rawRule` keeping the official TAKE_DAMAGE; and 余 S2, `SKILL_RANGE` on its x-1 (§22.10)), `SP_FULL` (immediately — 执旗手 / 战术家 / 吟游者 MANUAL skills, and AUTO DP skills such as 伺夜 S1, 焰尾 S1, 凛御银灰 S1), `CUSTOM_RANGE` (enemy inside the custom grid), `SEARCH` (an enemy inside the initial range, checked every tick — no attack needed; 解放者 / 阵法术师 MANUAL skills), `GDGLOW_SKILL_2` (a valid target anywhere on the field: 荒芜拉普兰德, 纯烬艾雅法拉 S3), special ids. The class rows cover **every MANUAL skill** of the class and never an AUTO skill (AUTO skills keep their own rule — an inference: PRTS names whole classes; the data's `skillIndex 0` / `-1` sentinel argues the other way, §20.2). **Operation cooldown**: the engine auto-casts a MANUAL skill no sooner than `AUTO_OP_COOLDOWN` (3 s) after its previous cast and after the unit's battle-start deployment ("自动操作具有3s冷却,在完成一次操作或作战开始时部署的单位将进入冷却"; `Battle._deploy` initial, `battle.flags.startOpCooldown`); kits with an automatic cast of their own check `skill.opCooling`; a pending "next attack" never takes a second charge. PASSIVE skills are always on.
Timed deployment skills use `kind: 'duration'`, `activateOnDeploy: true`, `spCost: 0`, `spType: 'none'`, `trigger: 'NEVER'`.
Deployment sets landing state, then `reset()` consumes one charge through `activate('deploy')`, then emits `deploy`;
`skillStart` fires before deploy hooks. In 联防 this is a fresh deployment activation; no previous active skill state is restored.
Duration end cleans the window and emits `skillEnd`; it leaves zero charges and `ready=false`, even after SP gifts.
Redeployment opens a new full window. Permanent passives retain `passive`; first-kill recasts of 缄默德克萨斯 explicitly
restart a full window. Special timed kits and their effect rules are described in SIM §7.2.
Auto-trigger rules (`skillTrigger.rule` in data — the official 技能策略, PRTS 卫戍协议/帮助 §作战阶段 技能操作; §20.2): `DEFAULT` (SP ready AND about to attack/heal AND an enemy/injured ally inside the **initial** range, or an enemy the unit blocks; with `trigger.allies` + `hpAtMost` an ally of the skill grid at or below that HP ratio — 塞雷娅 S1 急救, whose cast replaces that attack and is withdrawn with its charge if the ally recovers first), `SKILL_RANGE` (a MANUAL skill with a 技能范围 of its own, not an attack-range change: any living enemy, stealthed ones included, on the skill range, checked every tick — "仅在技能范围内存在敌人(无视其不可选中)时释放技能"; 13 skills / 26 records by the official rule, 14 / 28 in the data with 余 S2's deviation, §22.10), `TAKE_DAMAGE` (on taking damage — any damage, 无来源 included, never a 流失; every MANUAL 重装 skill, §21.12 — but the six of the deliberate deviation, §21.29: 深巡 / 雷蛇 S2, 号角 S2 / S3 and 灰毫 S1 / S2 are `DEFAULT`, `rawRule` keeping the official TAKE_DAMAGE; and 余 S2, `SKILL_RANGE` on its x-1 (§22.10)), `SP_FULL` (immediately — 执旗手 / 战术家 / 吟游者 MANUAL skills, and AUTO DP skills such as 伺夜 S1, 焰尾 S1, 凛御银灰 S1), `CUSTOM_RANGE` (enemy inside the custom grid), `SEARCH` (an enemy inside the initial range, checked every tick — no attack needed; 解放者 / 阵法术师 MANUAL skills), `GDGLOW_SKILL_2` (a valid target anywhere on the field: 荒芜拉普兰德, 纯烬艾雅法拉 S3), special ids. The class rows cover **every MANUAL skill** of the class and never an AUTO skill (AUTO skills keep their own rule — an inference: PRTS names whole classes; the data's `skillIndex 0` / `-1` sentinel argues the other way, §20.2). **Operation cooldown**: the engine auto-casts a MANUAL skill no sooner than `AUTO_OP_COOLDOWN` (3 s) after its previous cast and after the unit's battle-start deployment ("自动操作具有3s冷却,在完成一次操作或作战开始时部署的单位将进入冷却"; `Battle._deploy` initial, `battle.flags.startOpCooldown`); kits with an automatic cast of their own check `skill.opCooling`; a pending "next attack" never takes a second charge. Permanent passive skills are always on; finite deployment effects use the duration lifecycle above.
**SkillSpec** (authored per chess in `sim/content/kits/tierN.js`; values come from the chess's skill blackboard at its level — normal Lv4, elite Lv7 — so a spec is a *function of the blackboard*):
```js
@@ -290,6 +297,7 @@ export default {
chess_char_1_01: (bb, chess) => ({
skill: {
kind: 'duration' | 'ammo' | 'instant' | 'charges' | 'passive' | 'toggle',
activateOnDeploy: false, // optional boolean: consume a deployment charge synchronously in reset
duration: bb.duration, // duration kind
ammo: bb['attack@trigger_time'], // ammo kind
mods: { atkPct: bb.atk, batPct: bb.base_attack_time }, // applied while active
@@ -423,6 +431,8 @@ S→C:
- `m.private` — the recipient's full PlayerState view (§6.2 minus server-only data) on every change.
- `m.field` — field meta when watching starts: `{ fieldId, kind, rect, stageId, units: [UnitInfo], effects? }` (the units on the field, knocked-out operators waiting to redeploy included — `Battle.fieldMeta`, §18.3). The prep-scouting field (`Match.prepFieldMeta`) puts the scouted player's **hand (整备区) and 临时整备区 into `units` as well**: held pieces as units on the hand row (row 7, col = hand slot) and temp pieces on the temp row (row 8, cols 4..8 = the temp slots; PRTS 帮助 counts the temp area with the hand; a held item is a `kind: 'item'` unit the client draws as its floating plate) — the scout renders exactly like the own prep bench, for every watcher alike, the spectator seat's copy equal to a teammate's (§23.19's "never a private view" stays the no-`m.private` rule); the scout signature `_prepScoutSig` carries the hand / temp slots, so a piece moved to another slot is a change — and carries `effects`: the scouted player's effects view (策略 / 机变 / 悬赏 …), the watched player's effects column ready-made
- NOTE: game time in `b.snap`/`b.ev` is carried as **`gt`** (the frame's `t` is the message type).
- Active finite zero-SP timed skills display `sp=timeLeft`, `spMax=duration` and `UF.SKILL`; after end they display
`0/0`. Snapshot display capacity does not change runtime SP/cost.
- `b.snap` — `{ fieldId, gt, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]] , dp, killed, total, down?, elem? }` 20 Hz. Optional, only when non-empty (§18.3): `down: [[id, respawnAt (game s), respawnTime (s), state, row, col]]` — knocked-out operators waiting to redeploy, `state` 0 counting | 1 timer done, waiting for DP | 2 timer done, its tile taken — a safeguard, no ally deploys there (sim/constants.js `DOWN_STATE`); `row, col` = the tile it lies on and comes back on (§21.24); `elem: [[id, element, fill, cooldownEnd (game s), cooldown (s)]]` — the one gauge a unit shows: the fullest (ties by the official element id 神经 < 侵蚀 < 灼燃 < 凋亡), fill with 2 decimals rounded DOWN and kept at 0.01–0.99 outside a burst (§20.8); during a 爆发冷却 the bursting element with fill 1 and the cooldown numbers (else 0, 0)
- `b.ev` — `{ fieldId, ev: [ ... ] }` event tuples: `['spawn', UnitInfo]`, `['atk', srcId, tgtId, projKind]`, `['dmg', tgtId, amount, type]`, `['heal', tgtId, amount]`, `['skill', id, on:0|1]`, `['die', id, reason]` (reason `'killed'`, `'retreat'`, `'expired'` …; only `'killed'` plays an operator's knock-down sound), `['status', id, key, on]`, `['fx', kind, x, y, extra]` (`'hitCap' {id, n}` = a cancelled 限伤 hit, drawn as nothing, §20.12; `extra.form` = the unit's model form from then on (an enemy's mode, a 傀儡师's 替身 §22.11) — `shared/protocol.js fxForm`, state like a spawn: never dropped by the client, §21.4), `['layer', playerId, bondId, n]`, `['bounty', playerId, coins]`, `['deploy', id]`
- `m.toast {kind, text}` · `m.ticker {text, id, type, priority, playerId}` (the strip plays the highest priority first, §21.10) · `m.emote {playerId, id}` · `m.result {...}`
@@ -492,6 +502,8 @@ Look & feel: research 06 §11–§12 (near-black green-grey panels, mint `#4ed8a
Screens: **Title** (season art backdrop, 开始 → nickname), **Lobby** (独立模拟 / 同盟模拟 → create or join; recent rooms), **Room** (4 seats, invite code + copy link, difficulty picker (host), add/remove AI, ready, start), **Briefing** (INFO_CHECK: stage, factions, disabled bonds, banned chess, boss silhouette, ready x/4), **Band draft** (grid of 40 bands with icon/name/LP/effect; turn order list; skip; 查看禁用盟约与干员 under the order list opens the briefing's bond rows, legend and banned operators again in a read-only dialog — one component, `ui/matchInfo.js`, §21.28), **Game** (Pixi field + DOM HUD: top bar with round/phase capsule/LP (live during the own battle, §17.5)/timer/ready; bond strip + popups; left team panel with LP/status/emote bubbles, click to watch; bottom shop bar (level card, 3–5 chess cards, item card, funds, refresh, freeze, collapse); detail panel on click/hover (header with bonds, then 特质 — the operator's own effect — right under it, 特性, stats (live: the battle's own numbers / the start-of-battle ones in prep, §18.5), skill, module, equipped items, talents: §17.5); enemy preview drawer (next round enemies with counts, factions; its 本局信息 tab lists the briefing's disabled bonds and banned operators, §21.28); 机变 overlay (the official card: framed icon + name, the effect text — data `descRaw` — under it, the taker's round avatar at the top-right; two taps, §18.2, §20.7); merge reward overlay (the promotion reward, or a special offer — 凯瑟琳's 定向投放 … — as item / chess cards under its source's label, with the count of offers queued behind it, §21.2); phase banners; ticker (by broadcast priority, highest first; a leader-damage line plays only in its round and a player's newer one replaces the older, §21.10); toasts; emote wheel; settings (volume, quality, show damage numbers)), **Result** (victory/defeat, rounds, per-player lineup, stats, titles, back to room).
盟约栏支持「收起/展开盟约」(GitHub #142):收起时隐藏完整列表并关闭由列表打开的浮层,让原图标区域的指针事件到达棋盘,只保留至少 44×44 CSS px 的恢复按钮。折叠状态在本局阶段变化、观看队友时保留;展开后仍按当前观看对象显示最新盟约。按钮支持 Enter / Space,聚焦按钮时 Space 不触发准备或暂停。
A bond the mode never activates (标准: official `inactiveBondIdList`) reads 本局禁用 on shop / reward cards (struck through), detail chips and the popup — no 在场 count, no 当前效果 (§21.7); a strategy built around one carries a 本局禁用 tag and note in the strategy draft and stays selectable (bands.json `bondIds`, §21.26).
Interactions (research 09 §1.2 / §5, which supersede the first draft): shop card, upgrade and 机变 card are two-tap (select → confirm, §18.2) — a tap anywhere on the card, its confirm strip included, is the card's tap (shop cards have no ⓘ corner; the armed bar sits above the detail card; a shop card that cannot be bought right now opens its detail on tap, §20.7); a press / drop goes by the tile under the pointer (§18.1); drag bench↔board — a legal board drop (also onto the piece's own tile) opens the 4-direction deploy wheel (swipe UP/RIGHT/DOWN/LEFT with the rotated range striped orange; release in the centre, ✕ 点击取消 or Esc cancels); tap a unit's tile ⇒ underframe with **出售 +N** (and **撤退** on the board; items/Arts in the hand / temp: **销毁** — equipped items are locked, §6.2) plus its rotated range; no drag-to-sell; drag an item onto a unit's tile ⇒ equip that unit (both slots used ⇒ the replace dialog `ui/equipReplace.js` shows the incoming and the two equipped items; the player picks the one to replace — it is destroyed — `g.equip replaceUid`; cancel changes nothing); Arts are dropped on a tile and get the direction step; right-click or long-press ⇒ detail (or a tap on a shop card while the bar is locked); `R` refresh, `F` freeze, `D` level-up, `Space` ready (in a solo battle: pause / resume, §14 Solo pause). All actions optimistic-free: UI waits for server state (latency is small).
@@ -1259,7 +1271,7 @@ Eighteen workstreams (WA–WR, each on its own branch from master 7f13473) were
### 21.14 Special Originium slugs and 暴鸰's bomb (D3, D4) — `tools/local-extract/extract.py ENEMY_SPINES`, `tools/assets/local-enemy-spines.json`, `plan.mjs localEnemySpines`, `fetch-assets --local-spines`, `assets.js spineEntry`, `render/units.js ALIAS_TINT / FORMS`, `content/enemies.js kitBombd`, `snapshot.js form`, `style.js PROJ.droneBomb`
- **D3** ("所有特殊源石虫的模型全表现为普通源石虫"), partly real: 高能 / 冰爆 / 简饲源石虫 and “庞贝” always had their own models; 灼热 / 炽焰源石虫 (enemy_1305_mhslim / _2, the ELEMENT slugs, up to 10 a round) were the plain 源石虫 — no web dump has their models (the asset plan aliases them); the official skeletons exist only in the client (refs/arts/enm_art_12.ab).
- **D3** ("所有特殊源石虫的模型全表现为普通源石虫"), partly real: 高能 / 冰爆 / 简饲源石虫 and “庞贝” always had their own models; 灼热 / 炽焰源石虫 (enemy_1305_mhslim / _2, the ELEMENT slugs, up to 10 a round) were the plain 源石虫 — no web dump has their models (the asset plan aliases them); the official skeletons exist only in the client (refs/arts/enm_art_12.ab). Root cause located 2026-10-04: Ark-Models *indexes* `1305_mhslim` / `1305_mhslim_2` with an **empty `assetList`** (registered, never uploaded), so the alias chain falls to `enemy_1007_slime` — a different enemy rather than a variant of it. The *mobile* build ships their own model (`enemy_spine/<enemyId>/<enemyId>.{skel,atlas,png}`, straight-alpha pages), but its only public mirror is a community wiki and not a GitHub dump, so it is deliberately **not** an asset source (docs/ASSETS.md "Enemy aliases"): the tinted alias stays the web model until a GitHub dump carries these two.
- **D3 now**: an optional overlay — the manifest keeps the alias as the web model and `enemies[id].spineLocal` names the files of the data/local-assets.json group `spine/enemy/<id>` with the metadata of the committed `tools/assets/local-enemy-spines.json` (data/assets.json is byte-identical with or without the extraction). The client draws the official model when the local manifest lists every file, else (or on a load failure) the web alias tinted toward the slug's own lava colours (`ALIAS_TINT`: 灼热 orange, 炽焰 red-orange, research 07 §5.6) [ASSUMED look]. The 0.1.1 release bundle carries the models (extracted with `node tools/setup.mjs --local`, the `spine/enemy` job). Audit (249 enemies vs the client's prefabs): no other enemy is drawn with another's model; 伊利昂的木驮兽 starts on skin `Full` in the game and is drawn with `default` (unchanged). `fetch-assets --prune` never deletes `public/assets/local` any more.
- **D4** ("暴鸰的炸弹无法正常投放"), partly real: the bomb hit in the trigger tick, with no projectile, and stayed under the drone. **Official** (prefab enemy_1040_bombd / projectile_bombd; PRTS 暴鸰): Boomb plays Attack, the bomb leaves on OnAttack (frame 8) and flies homing at 5 tiles/s; the cast ends when the bomb is gone, ≥ 0.667 s after the release; buff bomb_s: the bomb-less mode S1 (*_2 clips) + speed ×2 ("技能结束后"); `_immuneStunWhenAffecting` 0.
- **D4 now**: the drone holds still at the trigger; 8 ticks later (8/30 s) the release ('atk' droneBomb + the form 'bombed' — UnitInfo `form`); on landing the target 100 % ATK + the 8 tiles around 100 % splash (a target gone mid-flight: the bomb lands where it was; the drone's death after the release does not stop it); at max(landing, release + 0.667) ×2 speed and it flies on. A stun / freeze / sleep before the release interrupts the cast (no drop; the skill re-arms with its 1 s cooldown).
@@ -2111,3 +2123,20 @@ Decisions of the owner, 2026-10-04: a 联防 devour counts members who enter alr
- **Now**: one attempt at `local` or `assets` is abandoned after `ART_MANIFEST_TIMEOUT_MS`. A timeout or any other failure sets that file `missing` and notifies immediately, which is the existing glyph path. The status stays `missing` through the retry wait (going back to `loading` would blank the button again). A later attempt that succeeds notifies `ready` and the picture replaces the glyph. A 404 or bad JSON is still not retried. Chess and the other text files are not on this clock and still stay `loading` across retries.
- [ASSUMED]: 8 seconds. A response that arrives after that attempt's clock is ignored; the retry is a new request, and the in-flight fetch is not aborted. After the two existing retries the glyph stays until the next `invalidate` or page load.
- **Tests**: `test/ui/emotes.test.js` (stub timer, no network: hung `local` is `missing` before the retry wait, then `ready`; a thrown fetch is the same; a timeout with no retry and a 404 stay `missing`; `chess` arms no timer). `test/ui/playtest3.test.js` data-store retry and `test/client-static.test.js` data.js still pass. No browser render: `EmoteArt` already picks the glyph from `missing`.
---
## 24. Community reports after 0.1.3
Reports after the 0.1.3 release. Each was checked against the official data and PRTS; anything with no source is marked [ASSUMED]. Where each is handled: a skill clip with no Begin and no own Idle plays once (德克萨斯 S2 剑雨) → §24.1.
### 24.1 A skill clip with no Begin and no own Idle plays once — 德克萨斯 S2 剑雨 (player report) — `render/spine.js SpineActor.setSkill / update` (`SKILL_CLIP_MIN`, `skillClipOnce`, `skillEndPending`)
- **Seen** (player report after 0.1.3): 德克萨斯's S2 剑雨 casts with no skill animation — she stays in her idle through the whole cast.
- **Official** [DATA]: her Front model has a single skill clip, `Skill` (2.167 s) — no `Skill_Begin`, no `Skill_Idle`, no `Skill_End`, so the manifest roles are `anims.skill {begin: null, loop: 'Skill', end: null}` and `anims.skills["0"] / ["1"]` are the same clip. Both her skills are instant (`durationType NONE`, duration 0; S1 冲锋号令·γ型, S2 剑雨). The official plays that `Skill` clip on the cast.
- **Cause**: `setSkill(true)` played a clip only when the skill had a Begin clip; with `begin === null` it fell through to `else if (this.mode === 'base') this._play(this._baseName())` — the idle. The loop clip was reachable only through an attack (`_attackClip`), and an instant skill makes no attack inside its activation window: sim `skills.js` fires `['skill', id, 1]` and `['skill', id, 0]` in the same tick (`activate` → `end('instant')`) and only holds the anim code `SKILL` for 0.5 s (`snapshot.js animOf`), so the actor was back on its idle before a frame passed.
- **Now**: a skill clip with neither a Begin nor its own Idle (its loop is not its idle, and it is not borrowed from the attack clip: `via !== 'attack'`) plays that clip once for its own length — `max(SKILL_CLIP_MIN 0.2 s, dur(loop))` — as the skill's own window; a `setSkill(false)` inside that window only marks the transition, so when the window ends the actor plays the skill's End clip (if it has one) or the idle. An instant skill therefore shows its animation instead of a one-frame flash, and a longer skill of the same shape still rests in its idle between attacks while its attacks use the loop, exactly as before. Untouched: a Begin clip with its queued idle / loop, a skill with an idle clip of its own (§23.13), `via: 'attack'` clips, `loop === idle` stances and enemies.
- **Scale**: 90 such clips on 61 Front models (96 clips have no Begin at all — the 6 with an own Idle keep the §23.13 path); 57 of the 90 belong to instant skills.
- **A PASSIVE skill reaches none of this through `activate()`**: `Skill.activate()` returns early for `kind === 'passive'` (`sim/skills.js`); a passive goes through `_startPassive()` (active + mods + `onStart`). Three of the 57 instant clips are passives in the data — 琳琅诗怀雅 S1 仗义疏财 / S2 “见面礼” and 凯瑟琳 S1; 凯瑟琳 S1 turned out to be `kind: instant` in the sim (so it already casts), and the two 琳琅诗怀雅 ones now fire the same window from `_startPassive` — on at the deployment, off after `SKILL_ANIM_WINDOW` (0.5 s) while the passive itself stays active. So all 57 are live.
- **Deploy-time passives with a duration are left alone** (缄默德克萨斯 S1–S3 8–12 s, 野鬃 S1, 伊内丝 S3, 耀骑士临光 S2): the sim keeps a passive active until death, so how long its stance should show is a separate question. A clip that IS the normal attack clip (`anims.skill.loop === anims.attack.loop`: 缄默德克萨斯 S1, 砾, 普罗旺斯, 斯卡蒂 S2) is never played as a skill animation — the new branch requires a distinct clip, so a deploy-time passive on those shows no attack twitch.
- **Tests**: `test/render/feedback4-texas-skill-clip.test.js` (德克萨斯 S2: the clip plays, holds through the same-tick off, then the idle, with no base flash in between; two more real instant entries — `char_140_whitew` S1 日晷, `char_206_gnosis` S2 零度爆发; 银灰 S3 真银斩 — the same shape but long — rests without freezing on the clip's last frame and still attacks with the loop; the deploy-time passives 琳琅诗怀雅 S1/S2 hold their 1 s clip through the sim's 0.5 s window and then idle; a clip that is the attack clip falls through to the base clip; unchanged: 折桠 S2 begin → idle, 耀骑士临光 S3 own idle, 宴 begin → loop). `test/sim/skills.test.js` covers the sim side (the passive window opens at the deployment and closes 0.5 s later, the passive staying active; a timed passive fires nothing). `test/render/feedback3-skill-idle.test.js`, `feedback3-move-deploy.test.js`, `loadout-skill.test.js` and the full suite stay green.
+2
View File
@@ -750,6 +750,8 @@ round was over. The official 1 s `broadcastBeginDelay` is not modelled.
## 6. Testing & tools
* `test/match/lobby-integration.test.js` 的联机轮选使用固定种子 `69`,覆盖 AI 先选走默认策略的情况(GitHub #144)。模拟玩家从当前公开选牌结果中选择未被占用的策略,并断言请求成功;不依赖拒绝后的超时分配。单人和联机用例均通过 `b.start` 确认战斗开始,避免瞬间结束的模拟战斗被公开状态节流而漏掉 `COMBAT` 阶段。
* `test/match/fakeBattle.js` — scriptable DESIGN §5.1 Battle (`FakeBattle.script = (battle) => plan`); inject with
`new Match({ …, BattleClass: FakeBattle })`.
* `test/match/harness.js` — `makeMatch(opts)` (virtual scheduler, captured frames, `drive()` for human decisions,
+2
View File
@@ -94,6 +94,8 @@
干员属于 1–2 个盟约。场上**不同名**的成员达到人数后激活(大多只数棋盘上的,「远见」「奇迹」「投资人」连整备区也算)。
盟约栏挡住棋盘时,点左侧的「收起」隐藏列表,再点「盟约」展开。收起会关闭从盟约栏打开的详情,原来图标覆盖的位置可以直接点选或拖动棋子;本局切换回合、查看队友时保留收起状态。
- **核心盟约**(8 个势力:炎、萨尔贡、维多利亚、谢拉格、拉特兰、阿戈尔、叙拉古、卡西米尔):3 名激活,6 名获得强力的额外效果。
- **附加盟约**(精准、迅捷、灵巧、奥术、坚守、助力、远见、奇迹、投资人、突袭、不屈、调和、协防干员、独行、绝技):多为 2 / 3 名激活;「独行」需要场上**恰好 1 名**,「绝技」按场上的精锐数量计算,没有势力盟约的干员算「协防干员」。
- **本局禁用**:「标准模拟」与官方相同,关闭拉特兰、阿戈尔、卡西米尔、灵巧、奥术、奇迹、投资人、突袭、独行、绝技这 10 个盟约,它们不会激活。你有这些盟约的成员时(奇迹、投资人也算整备区里的成员),盟约栏末尾显示**灰色带 ✕ 的盟约图标和「本局禁用」**,干员卡、盟约标签和盟约详情也这样标注。「远见」「奇迹」「投资人」计入整备区,盟约详情的「成员」人数也一样计入整备区里的成员(临时整备区不算)。
+26 -3
View File
@@ -150,6 +150,8 @@ later redeploys keep those gifts); `carryState: { sp }` a board summon
piece's SP only (PRTS "召唤物仅修改技力"). Nothing else is carried: a skill running at the end of the own combat enters
switched off with the SP it had left — spent at its activation, 0 for a one-charge skill (`unitsEnd.skillActive` is
reported, never carried; community report #34 — it used to restart for free).
Timed deployment skills (`activateOnDeploy`) trigger once from the new deployment, consuming their deployment charge;
they do not resume the previous skill state.
`carryState: { down: true }` = an operator knocked out at the end of the helper's own combat (PRTS 卫戍协议/帮助 "上一阶段为
退场状态的干员强制退场", user playtest #5 item 2): `start()` deploys it with everyone (initial `deploy` fires), then — before
`battleStart` — withdraws it with reason `FORCED_EXIT` (constants.js `'forcedExit'`) and HP 0 (the end-of-phase HP ratio, as after
@@ -830,6 +832,11 @@ or guard with a per-unit flag while dealing it. When the guard trips, the logged
- Kinds: `duration` (mods for `duration` s), `ammo` (mods until `ammo` attacks were made, optional duration cap),
`instant` (onStart + optional one-shot attack override applied to the next attack), `charges` (instant with charges),
`passive` (always on from deployment, no SP), `toggle` (stays on until death once activated).
- Timed deployment skills use `duration`, `activateOnDeploy: true`, `spCost: 0`, `spType: 'none'`, `trigger: 'NEVER'`.
Landing state is set before `reset()` synchronously calls `activate('deploy')`; `skillStart` precedes the `deploy`
hooks. Each deployment grants and consumes one charge. Duration end clears effects and fires `skillEnd('duration')`;
staying deployed or receiving SP never refills that charge. Early removal ends the active window with `death`;
removing an expired skill emits no second end. `activations` accumulates across deployments.
- `end()`: `active` turns false, then **onEnd runs while the skill's mods / range are still applied** (finishers and
end bursts use the skill's stats and range), then they are removed (kept when onEnd re-activated the skill), then
`skillEnd` fires. `onAttack` ctx carries `noAmmo` (set it to true: this attack spends no bullet, no `ammoUsed` — 流明's
@@ -869,6 +876,17 @@ has the generic spec. Summons: `battle.tokenDef(tokenId, unit)` / `battle.spawnT
(token skill of that slot: `variants[owner].bySkill[i]`, module attributes: `.byModule[id]`). Coverage of the selectable
skills: `node tools/kit-coverage.mjs --missing [--tier N] [--strict]`.
Timed deployment kits: 宴 S2 uses `bb.duration` and skill ATK mods, loses current HP once on landing, and converts normal
physical attacks to arts only while S2 is active; S1 has no such conversion. 斯卡蒂 S2 uses its own record's
`s.bb.duration` / `s.bb.atk`; DRE-Y revival in place does not restart it. 野鬃 S1 uses its record's `duration` /
`bb.attack_speed`, with the existing deploy discount talent. 砾 S1/S2 retain their one-second DEF/barrier decay steps
(damaged barrier scales with capacity); skill end removes the decay buff. 伊内丝 S3's first deployment places the sentry
with zero effect time and retreats with a refreshed redeploy timer; later deployments recall it and open the full
ATK/DP window, then stay deployed. 缄默德克萨斯 S1/S2/S3's first kill per deployment heals and explicitly ends/restarts
the skill for a full duration, including a synchronous kill inside its deploy burst; S3 rain is cancelled on end,
while enemy silence/DoT/RES cuts keep their own expiry. 耀骑士临光 S2 ends naturally by retreating with the preceding
operator's faction-based redeploy multiplier; an early death only cleans the skill and shields.
Element conventions of the kits (user playtest #5 #3; official term dictionary: 元素损伤 = the gauge, 元素伤害 = HP damage):
- **元素损伤 dealt** = `{ type: 'element', element, amount }` with the official base: "N%攻击力的…损伤" ⇒ `N × ATK` at that
moment, "伤害N%的…损伤" / "相当于法术伤害N%" ⇒ `N ×` the HP damage just dealt (post-mitigation, `damaged` ctx.amount).
@@ -896,9 +914,10 @@ Element conventions of the kits (user playtest #5 #3; official term dictionary:
```js
{
kind: 'duration'|'ammo'|'instant'|'charges'|'passive'|'toggle',
activateOnDeploy: bool, // optional: reset activates synchronously and consumes a deployment charge
duration, // s (duration kind; optional cap for ammo)
ammo, // attacks (ammo kind)
spCost, initSp, charges, spType: 'time'|'attack'|'hurt', // optional overrides of the data values
spCost, initSp, charges, spType: 'time'|'attack'|'hurt'|'none', // optional overrides of the data values
trigger: 'DEFAULT' | { rule, grid, allies?, hpAtMost? }, // optional override (allies / hpAtMost: an injured ally condition, §7.1)
heal: bool, // heal-type skill for the DEFAULT trigger (default: unit is a healer)
mods: { …mod keys }, // buff while active (instant: only during the pending attack)
@@ -941,8 +960,10 @@ whose skill "恢复…友方/友军…生命" heals the most injured ally in ran
"立即流失N%当前生命" ⇒ self HP loss at start (宴, 风丸); `hp_ratio` + "恢复/回复…生命" ⇒ self heal at start; `force` ⇒ on-hit
displacement by the official 力度 − 重量 rules (拖拽/hookmaster: `pullToFront`; else `push`, directional for 往攻击方向 / 朝部署方向 /
向前 / 身前方向 and 推击手, radial otherwise — §6; only a fallback: hand-written kits follow the client templates
`knockback[dir]` / `knockback[relative]`, e.g. 琳琅诗怀雅 S3's "向前推开" is radial). **Passive** skills only apply stat mods (timed when the text says "N秒内":
宴 +65 % ATK for 14 s) and the self/counter effects — their scales describe procs that need a kit. Instant skills with
`knockback[dir]` / `knockback[relative]`, e.g. 琳琅诗怀雅 S3's "向前推开" is radial). A passive with self stat mods,
positive `bb.duration` and "N秒内" uses a deployment duration (宴 +65 % ATK for 14 s); its mods, targeting, arts conversion
and attack/status overrides follow that window. Other **passive** skills apply permanent stat mods and self/counter
effects — their scales describe procs that need a kit. Instant skills with
mods/targeting but no attack override apply them to the next attack (the skill range is switched in for that attack).
### 7.5 Worked examples (real operators, numbers from blackboards)
@@ -1161,6 +1182,8 @@ Unknown subprofessions fall back to the profession default (test `professions.te
- `snapshot()` → `{ fieldId, t, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]], dp, killed, total, dps?, boss?, down?, elem? }`.
`sp/spMax` show remaining duration/ammo as a draining bar while a timed skill is active (ammo: `ammoLeft / ammoMax`, the
activation's real total — 拉特兰's and 逃犯引渡手续's extra bullets included, community report #35). Units in DIE state stay 0.8 s.
Active finite zero-SP duration skills display `timeLeft/duration` using `duration` as `spMax`; after end they show
`0/0`. This display capacity leaves runtime SP/cost unchanged; active skills carry `UF.SKILL`.
`down: [[id, respawnAt, respawnTime, state, row, col]]` (only when non-empty) = operators lying down waiting to redeploy
(`Battle.isDown(u)`: reason `'killed'` or `FORCED_EXIT` (§1.1 carryState `down`), or a forced exit `'retreat'` /
`'merchant'` (GitHub #60) — every removal but the 突袭 `'raid'` —, not removed for good, deployed at least once, a finite
+2
View File
@@ -58,6 +58,7 @@ Core (isPower, bondType SEASON): 炎 萨尔贡 维多利亚 谢拉格 拉特兰
### 2.2 Layers (层数)
- Each player has one integer layer counter L per bond (23 counters). All start at 0 when the simulation starts, are never reset between rounds and are never reduced (no layer-loss effect exists in the data) [ASSUMED: never reduced].
- `noStack: true` hides layer counts for 调和 / 协防干员 / 独行 / 绝技; it does not prohibit gains or clear stored layers. Their effects depend on member thresholds, but internal layers still participate in active-layer totals and unite ordering. [PRTS 下半盟约记录](https://prts.wiki/w/卫戍协议:盟约_下半/PRTS盟约记录): hidden-layer bonds still accept trait / strategy / equipment layer gains (review correction, 2026-10-04).
- L is kept while the bond is inactive, but bond effects only apply while the bond is active (PRTS 盟约记录).
- Wording contract used by every source: "使已激活的【X】层数+N" -> add only if bond X is active at the moment of the trigger; "(无需激活盟约)" -> add even if X is inactive. "自身所属盟约" = every bond of that operator; "自身已激活的盟约" = only its bonds that are active.
- No layer cap in the data. Caps are per source: max_add_count_per_battle (layers a single garrison instance may add in one battle), max_layer (per round, e.g. 安洁莉娜 12/round). The official cap is in the client, not the data: `AutoChessBattleConst.MAX_GARRISON_STACK = 999`, `AddBondCount` stores `min(L + n, 999)` per bond (research 11 §1). Since 2026-10-01 (DESIGN §20.12) the remake stops each bond at 999 (`shared/constants.js BOND_LAYER_CAP`, `layerGainRoom`); the community agrees (巴哈姆特 12534 "每把都能999层", "沒999層的盟約情況下"; 12316 "999謝").
@@ -614,6 +615,7 @@ Members (14 chess, 12 in current shop pool; by tier in shop: {'1': 2, '2': 2, '3
- **[2 distinct]** 灵巧 members and operators on their 4 orthogonally adjacent tiles: ASPD +(10 + 1*L).
- **[L >= 40]** Area becomes the 8 surrounding tiles.
- 灵巧干员被击倒(`removeReason === 'killed'`)、等待再部署时,仍以倒地位置为中心为周围队友提供攻速加成。主动撤退及联防 `forcedExit` 虽然也留下倒地模型,但不继续提供光环;联防强制离场不算本阶段击倒,重新部署后恢复在场光环(评审修正,2026-10-04)。
- Formulas: `aspd = 10 + L`
- How layers are gained: 溯光星源 休整期结束 +2 灵巧/奥术 per 3 gold spent this round ; 断崖 +3 self & behind; 空弦 +3 self & front; 蒂比 +2; 获得时 灵知 +5. (act1 灵巧 also had a 20-layer shop reward; removed in act2.)
- [ASSUMED] a unit covered by several 灵巧 auras gets the bonus once
+11 -1
View File
@@ -220,12 +220,22 @@
.readybtn.is-busy { opacity: .7; }
/* ---- bond strip ---------------------------------------------------------------------------------------- */
.gm__bonds { position: absolute; left: 1.56rem; top: 1.36rem; right: 5.2rem; pointer-events: none; }
.gm__bonds { position: absolute; left: 1.56rem; top: 1.36rem; right: 5.2rem; pointer-events: none;
display: flex; align-items: flex-start; gap: max(.1rem, 8px); }
/* Only the bond discs (and the +N chip) take the pointer: the strip's box spans the width under the top bar, where the
top board row's units stand (row 12 at 16:9 / 21:9 / 720p / phones, the boss field's row 5 in a boss prep) — it used
to swallow the taps and drags on them (review of user playtest #2 item 8: every own unit reachable). */
.gm__bonds > *, .gm__bonds .bstrip { pointer-events: none; }
.gm__bonds .bslot, .gm__bonds .bstrip__more { pointer-events: auto; }
.gm__bond-list { min-width: 0; }
/* The hidden list leaves no hit area over the board; only this compact tab can reopen it (GitHub #142). */
.gm__bonds .bonds-toggle { pointer-events: auto; flex: none; display: inline-flex; align-items: center; justify-content: center;
gap: max(.03rem, 2px); min-width: 44px; min-height: 44px; padding: .06rem .08rem;
color: var(--text-hi); background: var(--bg-1); border: 1px solid var(--line-2); cursor: pointer;
font-size: max(.14rem, 11px); line-height: 1; }
.bonds-toggle .icon { width: max(.18rem, 14px); height: max(.18rem, 14px); }
.bonds-toggle:hover { color: var(--mint-500); border-color: var(--mint-500); }
.bonds-toggle:focus-visible { outline: 2px solid var(--mint-500); outline-offset: 2px; }
.bstrip { display: flex; align-items: flex-start; gap: .1rem; flex-wrap: nowrap; }
.bstrip.is-frozen .bslot { opacity: .5; filter: saturate(.4); }
.bstrip--empty { display: inline-flex; align-items: center; gap: .12rem; padding: .08rem .16rem; font-size: .15rem; color: var(--text-dim); background: rgba(8, 11, 10, .6); border: 1px dashed var(--line-2); }
+43 -7
View File
@@ -1,7 +1,8 @@
// Audio manager (Web Audio): BGM per phase, UI SFX, per-unit battle SFX. Never throws.
//
// Sources: data/assets.json → audio (docs/ASSETS.md):
// bgm { lobby, prep, combat, boss: { intro?, loop } }, bossBgm { [bossId]: { intro?, loop } },
// bgm { lobby, prep, combat, combatAlts?: [ {intro?, loop}, … ], boss: { intro?, loop } },
// bossBgm { [bossId]: { intro?, loop } },
// sfx.ui { click, buy, sell, refresh, freeze, levelup, merge, equip, ready, timer, yourTurn, … },
// sfx.battle { deploy, tokenDeploy, charDie, tokenDie?, enemyDie, enemyHit, heal, killCoin, … },
// sfx.units { [charId|tokenId|enemyId]: { attack?, hit?, skill?, die?, born?, mix?: { [role]: { p?, vol? } } } }.
@@ -11,6 +12,10 @@
// - Channels: master → { bgm, sfx } gains; volumes from settings (0..1) + mute. Tab hidden ⇒ suspend.
// - BGM: `intro` then `loop` (1 s crossfade); switching tracks fades out/in (0.8 s). The same loop URL
// keeps playing across phases (prep and combat share a track).
// - 开战 BGM: `bgm.combatAlts` are the mode's own battle tracks (塞壬唱片 骑士之日 / 无畏者). The track is fixed per
// round, not drawn: 无畏者 through rounds 1–7 and 骑士之日 from round 8 on (`combatTrackFor`, the official
// schedule), so every client of a match hears the same one, a fight never switches track halfway through and the
// 联防 that follows a 作战 keeps its round's track.
// - Battle SFX from `b.ev` tuples (`handleBattleEvents`): at most MAX_VOICES concurrent unit sounds, at most
// MAX_PER_URL overlapping copies of one sound (the official banks' maxSoundAllowed 2), a per-unit cooldown and a
// per-URL minimum gap (SfxLimiter), so a 60-unit fight stays listenable.
@@ -59,9 +64,11 @@ const SKILL_MODE_FILE = /_(d|h|s)\d*\.mp3$/i;
* BGM key for a route + match phase.
* @param {'title'|'lobby'|'room'|'game'|string} route
* @param {any} pub m.public (may be null)
* @returns {string|null} 'lobby' | 'prep' | 'combat' | 'unite' | 'boss' | 'boss:<bossId>' | null
* @param {0|1|null} [combatTrack] the round's own 开战 track index into `bgm.combatAlts` (combatTrackFor; omitted ⇒
* plain 'combat', i.e. the manifest's default combat track)
* @returns {string|null} 'lobby' | 'prep' | 'combat' | 'combat:<i>' | 'unite' | 'boss' | 'boss:<bossId>' | null
*/
export function bgmKeyFor(route, pub) {
export function bgmKeyFor(route, pub, combatTrack = null) {
if (route !== 'game') return route === 'title' || route === 'lobby' || route === 'room' ? 'lobby' : null;
const phase = pub?.phase;
if (!phase) return 'lobby';
@@ -74,7 +81,8 @@ export function bgmKeyFor(route, pub) {
// resolveBgm falls back to `bgm.combat` when a manifest predates it.
return 'unite';
case PHASE.COMBAT:
return 'combat';
// 开战 BGM: the round's own track — 骑士之日 / 无畏者 are fixed per round, not drawn (combatTrackFor)
return combatTrack == null ? 'combat' : `combat:${combatTrack ? 1 : 0}`;
case PHASE.FINAL_ASSAULT:
return pub.bossId ? `boss:${pub.bossId}` : 'boss';
case PHASE.HIDDEN_CORE:
@@ -85,8 +93,9 @@ export function bgmKeyFor(route, pub) {
}
/**
* Resolve a BGM key to { intro?, loop } URLs from the manifest: `boss:<id>` falls back to the generic boss track and
* `unite` (联防's own track) to `bgm.combat` when the manifest predates it.
* Resolve a BGM key to { intro?, loop } URLs from the manifest: `boss:<id>` falls back to the generic boss track,
* `combat:<i>` to the i-th `bgm.combatAlts` entry (and to `bgm.combat` when the manifest has none), and `unite`
* (联防's own track) to `bgm.combat` when the manifest predates it.
* @param {any} manifest
* @param {string|null} key
* @returns {{ intro: string|null, loop: string }|null}
@@ -96,12 +105,33 @@ export function resolveBgm(manifest, key) {
if (!a || !key) return null;
let t = null;
if (key.startsWith('boss:')) t = a.bossBgm?.[key.slice(5)] || a.bgm?.boss;
else if (key.startsWith('combat:')) t = a.bgm?.combatAlts?.[Number(key.slice('combat:'.length))] || a.bgm?.combat;
else if (key === 'unite') t = a.bgm?.unite || a.bgm?.combat;
else t = a.bgm?.[key];
if (!t || typeof t.loop !== 'string') return null;
return { intro: typeof t.intro === 'string' ? t.intro : null, loop: t.loop };
}
/**
* The last round that plays 无畏者 (1–7); from the next round on it is 骑士之日 (8–13) — the official schedule
* (docs/ASSETS.md "BGM"; the two tracks are the 塞壬唱片 act13side battle themes).
*/
export const COMBAT_TRACK_SWITCH_ROUND = 7;
/**
* The round's own 开战 track index into `bgm.combatAlts` (plan.mjs order: 0 = `m_bat_kazimierz2_1` 骑士之日,
* 1 = `m_bat_kazimierz2_2` 无畏者). The mode does not draw these: the official schedule plays one per round, 无畏者
* through the early rounds (1–7) and 骑士之日 from round 8 to the last normal round (8–13). Everything after that is
* the boss rounds (最终攻势 / 隐秘核心), which have their own tracks and never ask for `combat:<i>`.
* @param {number|null|undefined} round m.public.round
* @returns {0|1|null} null when the round is unknown ⇒ the manifest's plain `combat` track
*/
export function combatTrackFor(round) {
const r = Number(round);
if (!Number.isFinite(r) || r < 1) return null;
return r <= COMBAT_TRACK_SWITCH_ROUND ? 1 : 0;
}
/**
* Official sound class of a battle unit (audio_data `battle.ON_UNIT_DEAD|BORN.<class>` defaults):
* 'enemy' | 'char' (operators: b_char_dead “干员被击倒” / b_char_set) | 'token' (summons: b_char_tokendead /
@@ -718,7 +748,13 @@ export function installAudio(deps) {
if (deps?.settings) audio.setVolumes(deps.settings);
if (typeof deps?.subscribe === 'function' && typeof deps?.getState === 'function') {
const sync = (s) => {
try { audio.playBgm(bgmKeyFor(deps.selectRoute(s), s.match?.public)); } catch { /* ignore */ }
try {
const pub = s.match?.public ?? null;
// 开战 BGM: the round's own track out of bgm.combatAlts (骑士之日 / 无畏者 are fixed per round, not drawn),
// so every client of a room hears the same one, a mid-fight re-render (or a teammate view) never switches,
// and the next round moves on by the table. 联防 shares its round ⇒ same key ⇒ the loop keeps playing.
audio.playBgm(bgmKeyFor(deps.selectRoute(s), pub, combatTrackFor(pub?.round)));
} catch { /* ignore */ }
};
sync(deps.getState());
return deps.subscribe((s, prev) => {
+48 -2
View File
@@ -16,7 +16,12 @@
// setSkill(on) skill begin, then — when the skill has an idle clip of its own (skill.idle,
// not its loop) — that idle between attacks, its loop (the skill's attack clip)
// only on attacks (community report #23: 折桠's S2 jump attack looped with no
// enemy engaged); a skill without one keeps its loop as the stance; end on stop
// enemy engaged); end on stop. A skill clip with NEITHER a Begin nor an own Idle
// (德克萨斯 剑雨 and the other 58 instant skills: anims.skill {begin:null,
// loop:'Skill', end:null}) plays that clip once for its own length: an instant
// skill's flag is off again within the same 0.5 s window (sim skills.js), so
// without this the actor fell through to its idle and played no skill clip at
// all (player report).
// deploy() 'Start' once, then base
// die() die clip once (callers fade out afterwards); a skeleton without one holds its
// idle clip's first frame (GitHub issue #25: the attack loop went on)
@@ -36,6 +41,9 @@ const clampN = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
/** Longest wind-up compression (× the rhythm speed) when the look-ahead is shorter than the natural wind-up. */
export const MAX_WIND_SPEEDUP = 2.5;
/** Floor for the one-shot window of a skill clip with no Begin / own Idle (a very short clip still gets frames). */
const SKILL_CLIP_MIN = 0.2;
/**
* Attack clip timing (pure). `loopDur` / `hit` are clip seconds (strike frame at `hit`), `interval` and `lead` game
* seconds. The clip plays at `ts = loopDur / interval` (one loop per attack); started `lead` before the attack it
@@ -100,6 +108,8 @@ export class SpineActor {
this.endAt = 0; // landing in endRoles (the next form's) when given
this.endRoles = null;
this.skillOn = false;
this.skillClipOnce = false; // skillBegin: the skill's own clip plays once (a clip with no Begin / own Idle)
this.skillEndPending = false; // … and the skill turned off inside it: play its End clip / base when it ends
this.attackUntil = 0;
this.clock = 0;
this.current = '';
@@ -360,6 +370,8 @@ export class SpineActor {
// his S3's 'skill' off event — lets the deploy clip play out (then the plain idle) instead of cutting it with the End
if (!on && this.mode === 'deploy') return;
if (on && sk) {
this.skillEndPending = false;
this.skillClipOnce = false;
if (this.has(sk.begin)) {
this.mode = 'skillBegin';
this._play(sk.begin, false, { mix: 0.08 });
@@ -367,8 +379,25 @@ export class SpineActor {
// then the skill's own idle until an attack plays its loop (community report #23); without one, the loop
const next = this._skillIdle() || (this.has(sk.loop) ? sk.loop : null);
if (next) this._queue(next, true);
} else if (!this.has(sk.idle) && this.has(sk.loop) && sk.via !== 'attack' && sk.loop !== this.roles.attack?.loop && !this._skillIsBuffOnly()) {
// No Begin and no own Idle: the skill's clip IS its animation (德克萨斯 剑雨 — anims.skill {begin:null,
// loop:'Skill', end:null}, the 2.17 s clip; 58 instant skills in all). Play it once for its own length. An
// instant skill turns the flag off inside the same tick (sim skills.js fires 'skill' 1 and 0 together and only
// holds the anim code SKILL for 0.5 s), so this window is what keeps it on screen: the actor used to fall
// through to the base clip and show no skill animation at all (player report).
this.mode = 'skillBegin';
this.skillClipOnce = true;
this._play(sk.loop, false, { mix: 0.08 });
this.skillBeginUntil = this.clock + Math.max(SKILL_CLIP_MIN, this.dur(sk.loop));
} else if (this.mode === 'base') this._play(this._baseName(), true);
} else if (!on && sk) {
// an instant skill switches off while its own one-shot clip runs: let the clip finish — the state machine plays
// its End clip (or the base) when the window ends — instead of cutting it with End / base right now
if (this.skillClipOnce && this.clock < this.skillBeginUntil) {
this.skillEndPending = true;
return;
}
this.skillClipOnce = false;
if (this.has(sk.end)) {
this.mode = 'skillEnd';
this._play(sk.end, false, { mix: 0.08 });
@@ -457,7 +486,24 @@ export class SpineActor {
}
break;
case 'skillBegin':
if (this.clock >= this.skillBeginUntil) { this.mode = 'base'; if (!this.has(this.roles.skill?.loop)) this._play(this._baseName(), true); }
if (this.clock >= this.skillBeginUntil) {
const sk = this.roles.skill;
const once = this.skillClipOnce;
const pending = this.skillEndPending;
this.skillClipOnce = false;
this.skillEndPending = false;
this.mode = 'base';
if (pending && this.has(sk?.end)) {
// the skill switched off inside its own clip: its End clip closes it now
this.mode = 'skillEnd';
this._play(sk.end, false, { mix: 0.08 });
this.skillEndUntil = this.clock + this.dur(sk.end);
} else if (once || pending || !this.has(sk?.loop)) {
// the one-shot clip is over (or the skill has no loop clip at all): rest. A Begin clip with a queued next
// clip (the loop, or the skill's own idle) needs no play here — it is already on the track
this._play(this._baseName(), true);
}
}
break;
case 'skillEnd':
if (this.clock >= this.skillEndUntil) { this.mode = 'base'; this._play(this._baseName(), true); }
+3
View File
@@ -1461,6 +1461,9 @@ export class ItemView {
if (a?.image) a.image(url).then((img) => { if (!this.destroyed && img && this.info.icon === url) this.plate.texture = itemTexture(String(this.info.defId), img, this.info.color || 0x9aa5a0); }, () => {});
}
setWorld(x, y, z = 0) { this.x = x; this.y = y; this.z = z; }
/** A battle snapshot sample (syncBattle syncs every unit of the field): a hand item on a scouted prep board rides
* the units as kind 'item' (Match.prepFieldMeta) — follow its position, there is nothing else to animate. */
sync(s) { this.x = s.x; this.y = s.y; }
update(dt, cam, t) {
const lifted = this.lift > 0;
const p = cam.project(this.x, this.y, lifted ? this.z : this.z + this.lift + 0.12 + Math.sin(t * 2 + this.bob) * 0.03, this.screen);
+15 -2
View File
@@ -200,6 +200,7 @@ function MatchScreen() {
const [watchWho, setWatchWho] = useState(null); // { fieldId, playerId }: the teammate picked with 前往查看
const [drawer, setDrawer] = useState(null); // 'enemies' | 'info' | null
const [bondOpen, setBondOpen] = useState(null); // { id, ownerId, from }: the bond popup and whose bond it shows
const [bondsCollapsed, setBondsCollapsed] = useState(false);
const [detail, setDetail] = useState(null); // detail target
const [collapsed, setCollapsed] = useState(false);
const [rewardMin, setRewardMin] = useState(false);
@@ -1244,8 +1245,20 @@ function MatchScreen() {
live=${liveLpNow} spectator=${spectator} />
<div class="gm__bonds">
<${BondStrip} bonds=${stripBonds} layersDisabled=${layersDisabled} openId=${bondPop && bondPop.ownerId === strip.ownerId ? bondPop.bondId : null}
owner=${strip.name} onOpen=${(id) => openBond(id, strip.ownerId, 'strip')} />
<button type="button" class="bonds-toggle" aria-expanded=${!bondsCollapsed} aria-controls="match-bond-strip"
aria-label=${bondsCollapsed ? '展开盟约' : '收起盟约'} title=${bondsCollapsed ? '展开盟约' : '收起盟约'}
onKeyDown=${(e) => {
// Keep native Space activation here without also firing the global ready / pause shortcut.
if (e.key === ' ') e.stopPropagation();
}}
onClick=${() => {
if (!bondsCollapsed && bondOpen?.from === 'strip') setBondOpen(null);
setBondsCollapsed(!bondsCollapsed);
}}><${Icon} name=${bondsCollapsed ? 'chevronRight' : 'chevronLeft'} /><span>${bondsCollapsed ? '盟约' : '收起'}</span></button>
<div id="match-bond-strip" class="gm__bond-list" hidden=${bondsCollapsed}>
<${BondStrip} bonds=${stripBonds} layersDisabled=${layersDisabled} openId=${bondPop && bondPop.ownerId === strip.ownerId ? bondPop.bondId : null}
owner=${strip.name} onOpen=${(id) => openBond(id, strip.ownerId, 'strip')} />
</div>
</div>
<${TeamPanel} pub=${pub} myId=${myId} watching=${watchingNow} bubbles=${bubbles} onWatch=${watchPlayer} cap=${gd.config?.lpCapPerRound ?? 10} uniteLocal=${uniteLocal}
+3 -3
View File
@@ -153,9 +153,9 @@ function SpectatorBar({ facts, myId, busy, onRemove, onSit }) {
</section>`;
}
function InviteBox({ code }) {
function InviteBox({ code, name, difficulty }) {
const copy = async (what) => {
const ok = await copyText(what === 'code' ? code : inviteLink(code));
const ok = await copyText(what === 'code' ? code : `${inviteLink(code)} ${name}邀请你加入卫戍协议:盟约【${DIFFICULTY_NAMES[difficulty]}】`);
if (ok) toast(what === 'code' ? `已复制同盟密钥 ${code}` : '已复制邀请链接', 'success');
else toast('复制失败,请手动复制', 'warn');
};
@@ -280,7 +280,7 @@ export function RoomScreen() {
<h1 class="topbar__title">${coop ? '同盟模拟' : '独立模拟'}<span class="topbar__sep"></span><${DifficultyTag} difficulty=${room.difficulty} size="lg" /></h1>
</div>
<div class="topbar__right">
${coop ? html`<${InviteBox} code=${room.code} />` : html`<div class="solo-note"><${MicroLabel}>SINGLE OPERATOR<//><span>仅限 1 名博士</span></div>`}
${coop ? html`<${InviteBox} code=${room.code} name=${me.name} difficulty=${room.difficulty} />` : html`<div class="solo-note"><${MicroLabel}>SINGLE OPERATOR<//><span>仅限 1 名博士</span></div>`}
</div>
</header>
+3 -3
View File
@@ -67,7 +67,7 @@ export function BondStrip({ bonds, layersDisabled = false, onOpen, openId = null
const next = nextThreshold(b.count ?? 0, th);
return html`<div key=${b.bondId} role="listitem" data-bond=${b.bondId} data-harmony=${b.harmony > 0 ? b.harmony : null}
class=${cx('bslot', b.active && 'is-active', openId === b.bondId && 'is-open')}>
<${BondDisc} name=${rec?.name || b.bondId} icon=${bondIconUrl(m, b.bondId)} layers=${b.layers ?? 0}
<${BondDisc} name=${rec?.name || b.bondId} icon=${bondIconUrl(m, b.bondId)} layers=${rec?.noStack ? undefined : b.layers ?? 0}
tier=${b.tier ?? 0} maxTier=${Math.max(1, th.length)} active=${!!b.active} size="sm" showName=${true}
layersDisabled=${layersDisabled} onClick=${() => onOpen(b.bondId)}
title=${`${rec?.name || b.bondId} ${b.count ?? 0}/${next ?? th[th.length - 1] ?? '-'}${b.harmony > 0 ? `(含调和 +${b.harmony})` : ''}`} />
@@ -121,7 +121,7 @@ export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember,
<h3 class="bpop__name">${b.name}</h3>
<div class="bpop__facts">
${off ? null : html`<span>在场 <b class="num">${count}</b>${next != null ? html`<small class="num">/${next}</small>` : null}${countsHand ? html`<small>(含整备区)</small>` : null}${harmony ? html`<small class="bpop__hnote" data-harmony=${harmony}>(含${harmonyName} +${harmony})</small>` : null}</span>`}
<span>层数 <b class="num t-mint">${layers}</b></span>
${b.noStack ? html`<span>层数不显示</span>` : html`<span>层数 <b class="num t-mint">${layers}</b></span>`}
<span class=${active ? 't-mint' : 't-lo'}>${active ? `已激活${th.length > 1 ? ` · ${tier} 阶` : ''}` : off ? '本局禁用' : '未激活'}</span>
</div>
</div>
@@ -131,7 +131,7 @@ export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember,
${th.map((n, i) => html`<span key=${i} class=${cx('bpop__tier', i < tier && 'is-on')}><b class="num">${n}</b><small>${b.maxCount != null ? '名及以下' : '名'}</small></span>`)}
</div>
${hasNow ? html`<section class="bpop__sec bpop__sec--now">
<h4>当前效果 <small class="num">(${layers} 层)</small></h4>
<h4>当前效果 ${b.noStack ? null : html`<small class="num">(${layers} 层)</small>`}</h4>
<${RichText} as="p" text=${formatBondEffect(b, layers)} class="bpop__desc" />
</section>` : null}
<section class="bpop__sec">
+9 -4
View File
@@ -10,7 +10,8 @@
// which also ignore power_def_penetrate DEF / power_magic_resist_penetrate RES (defIgnorePct / resIgnorePct)
// 迅捷 swiftShip member skill end → p = min(1, base_prob + prob_per_stack·L): +normal_sp SP; L ≥ power_bond_stack_cnt:
// every operator's skill end rolls p again for +power_sp (members roll both, research [ASSUMED])
// 灵巧 skillfulShip aura: members + operators on their 4 (L ≥ 40: 8) adjacent tiles ASPD +(base + per·L), once per unit
// 灵巧 skillfulShip aura: members + operators on their 4 (L ≥ 40: 8) adjacent tiles ASPD +(base + per·L), once per unit;
// killed members keep providing it from their body tile until redeploy; retreat / forcedExit do not
// 奥术 arcaneShip member arts damage → target arts taken ×(base + per·L) for weak_duration s; tier 2: ×power_weak_scale
// when the target is below hp_ratio at application. ONE instance per target whatever applies it — the
// two players of a pair field compete for it, the strongest wins (battle.applyStrongest, 同名效果取最高:
@@ -219,10 +220,14 @@ function updateAura(battle, st) {
next.clear();
const offs = st.auraWide ? N8 : N4;
for (const m of st.members[ID.skillful]) {
if (!onField(m)) continue;
next.add(m);
const active = onField(m);
// isDown also covers voluntary / forced exits since v0.1.2. Only a kill keeps the aura.
if (!active && (m.removeReason !== 'killed' || !battle.isDown(m))) continue;
if (active) next.add(m);
// A knocked-out member still covers neighbours around the tile where it waits to redeploy.
const [r, c] = active ? [m.tileR, m.tileC] : battle.restTile(m);
for (const [dr, dc] of offs) {
const a = battle.unitAt(m.tileR + dr, m.tileC + dc);
const a = battle.unitAt(r + dr, c + dc);
if (a && a.kind === 'op' && a.ownerId === st.pid && onField(a)) next.add(a);
}
}
+12 -12
View File
@@ -28,7 +28,7 @@
// "附带…凋亡/灼燃/神经损伤" + ep_damage_ratio ⇒ element damage on hit (× damage dealt when the text says "伤害N%的…损伤",
// else × ATK); "屏障" + shield_max_hp_ratio / hp_ratio ⇒ self shield at start decaying over its duration (砾, 新约能天使);
// "立即流失N%当前生命" + hp_ratio ⇒ self HP loss at start (宴, 风丸); hp_ratio + "恢复/回复…生命" ⇒ self heal at start.
// Passive skills only apply stat mods (for bb.duration s when the text says "N秒内": 宴) and the self/counter effects
// Passive skills with stat mods + bb.duration + "N秒内" use a deployment duration; other passives apply stat mods and the self/counter effects
// above — their scales describe procs (bombs, sword rain, counters) that need a hand-authored kit.
// force→onHit displacement with the official 力度 − 重量 rules (Battle.push / pullToFront): a pull "至面前" when the text says
// 拖拽 or for hookmasters, else a push — along the unit's direction when the text says 往攻击方向 (the 推击手 wording, PRTS
@@ -107,10 +107,8 @@ export function genericSkillSpec(sk, bb = sk?.bb ?? {}, def = null) {
if (!sk) return null;
const g = getter(bb);
const ga = attackGetter(bb);
const kind = genericKind(sk, bb);
let kind = genericKind(sk, bb);
const desc = String(sk.description || '');
const passive = kind === 'passive';
const timed = kind === 'duration' || kind === 'ammo' || kind === 'toggle';
// "受到攻击时…造成…" numbers belong to a counter effect (the operator's own, or an ally's: 刺玫 "该角色受到攻击时")
const counterCtx = /受到(敌人的)?攻击时/.test(desc);
const counterText = counterCtx && !/该(角色|干员|单位)受到攻击时/.test(desc);
@@ -149,9 +147,16 @@ export function genericSkillSpec(sk, bb = sk?.bb ?? {}, def = null) {
set('resIgnoreFlat', g('magic_resist_penetrate_fixed'));
set('defIgnoreFlat', g('def_penetrate_fixed'));
// Effect inference keeps the original kind; a deployment window only changes the lifecycle.
const passive = kind === 'passive';
const timed = kind === 'duration' || kind === 'ammo' || kind === 'toggle';
const passiveTimed = passive && Object.keys(mods).length && num(bb.duration) > 0 && /\d+(\.\d+)?秒内/.test(desc) ? num(bb.duration) : 0;
if (passiveTimed) kind = 'duration';
// ---- targeting / attack override (never for passives: their scales describe procs)
const targeting = {};
const attack = {};
if (passiveTimed && /攻击[^。;]*(造成|变为|变成)[^。;]*法术伤害|伤害类型变为法术/.test(desc)) attack.dmgType = 'arts';
const mt = ga('max_target');
if (!passive && mt !== undefined && mt > 0) targeting.maxTargets = Math.floor(mt);
const ext = g('ability_range_forward_extend');
@@ -273,13 +278,12 @@ export function genericSkillSpec(sk, bb = sk?.bb ?? {}, def = null) {
id: sk.id,
name: sk.name,
kind,
duration: kind === 'duration' ? sk.duration : (kind === 'ammo' && sk.duration > 0 ? sk.duration : undefined),
duration: kind === 'duration' ? (passiveTimed || sk.duration) : (kind === 'ammo' && sk.duration > 0 ? sk.duration : undefined),
ammo,
};
// passive stat buffs limited in time ("部署后…在14秒内攻击力+65%", 宴) become a timed buff at each deployment
const passiveTimed = passive && Object.keys(mods).length && num(bb.duration) > 0 && /\d+(\.\d+)?秒内/.test(desc) ? num(bb.duration) : 0;
if (Object.keys(mods).length && !passiveTimed) spec.mods = mods;
if (passiveTimed) Object.assign(spec, { activateOnDeploy: true, spCost: 0, spType: 'none', trigger: 'NEVER' });
if (Object.keys(mods).length) spec.mods = mods;
if (Object.keys(targeting).length) spec.targeting = targeting;
// instant/charges skills act on the next attack: mods/targeting without an explicit attack still need one
if (!Object.keys(attack).length && (kind === 'instant' || kind === 'charges') && (spec.mods || spec.targeting)) spec.attack = {};
@@ -288,10 +292,6 @@ export function genericSkillSpec(sk, bb = sk?.bb ?? {}, def = null) {
// ---- start / end effects
const starts = [];
const ends = [];
if (passiveTimed) {
const m = { ...mods };
starts.push(({ battle, unit }) => battle.addBuff(unit, { key: `generic:passive:${sk.id ?? 'skill'}`, duration: passiveTimed, mods: m, tags: ['skill'] }));
}
const hr = g('hp_ratio');
if (hr !== undefined && hr > 0 && hr <= 1 && /立即流失\d+(\.\d+)?%(的)?当前生命/.test(desc)) {
starts.push(({ battle, unit }) => { const loss = unit.hp * hr; if (loss > 0 && unit.hp - loss >= 1) battle.loseHp(unit, loss, { source: unit }); });
+5 -9
View File
@@ -901,7 +901,6 @@ export default {
// hp_recovery_per_sec_by_max_hp_ratio × max HP per second (the 生命回复速度 attribute: works under her 武者 no-heal).
chess_char_1_18_a: (bb, chess) => {
const t = talentBb(chess, 0);
const buffKey = 'utage:s2';
const s1 = skillBbOf(chess, 'skchr_utage_1');
return {
skills: {
@@ -913,16 +912,16 @@ export default {
},
},
skill: {
kind: 'passive',
kind: 'duration', activateOnDeploy: true, duration: num(bb.duration), spCost: 0, spType: 'none', trigger: 'NEVER',
mods: { atkPct: num(bb.atk) },
onStart({ battle, unit }) {
const loss = unit.hp * num(bb.hp_ratio);
if (loss > 0 && unit.hp - loss >= 1) battle.loseHp(unit, loss, { source: unit });
battle.addBuff(unit, { key: buffKey, duration: num(bb.duration), mods: { atkPct: num(bb.atk) }, tags: ['skill'], visible: true });
battle.fx('aoe', { x: unit.x, y: unit.y, radius: 1, id: unit.id, skill: 'breach' });
},
},
talents: [{ install(battle, unit) {
onHitBy(battle, unit, ({ dmg }) => { if (dmg.isAttack && dmg.type === 'phys' && unit.findBuff(buffKey)) dmg.type = 'arts'; });
onHitBy(battle, unit, ({ dmg }) => { if (dmg.isAttack && dmg.type === 'phys' && unit.skill?.id === 'skchr_utage_2' && unit.skill.active) dmg.type = 'arts'; });
const maxAs = num(t.min_attack_speed), minHp = num(t.min_hp_ratio);
if (maxAs > 0 && minHp < 1) {
battle.on('tick', () => {
@@ -965,11 +964,8 @@ export default {
return {
skills: {
skchr_wildmn_1: {
kind: 'passive',
onStart({ battle, unit }) {
const d = num(r1?.duration);
if (d > 0) battle.addBuff(unit, { key: 'wildmn:s1', duration: d, mods: { aspd: num(r1?.bb?.attack_speed) }, tags: ['skill'], visible: true });
},
kind: 'duration', activateOnDeploy: true, duration: num(r1?.duration), spCost: 0, spType: 'none', trigger: 'NEVER',
mods: { aspd: num(r1?.bb?.attack_speed) },
},
},
skill: {
+4 -2
View File
@@ -547,7 +547,7 @@ export default {
const t = talentBb(chess, 0);
return {
skill: {
kind: 'passive',
kind: 'duration', activateOnDeploy: true, duration: num(bb.duration, 10), spCost: 0, spType: 'none', trigger: 'NEVER',
onStart({ battle, unit }) {
const total = unit.s.maxHp * num(bb.hp_ratio);
const dur = num(bb.duration, 10);
@@ -566,10 +566,11 @@ export default {
});
battle.fx('shield', { x: unit.x, y: unit.y, id: unit.id });
},
onEnd({ battle, unit }) { battle.removeBuff(unit, 'gravel:rats'); },
},
skills: {
skchr_gravel_1: {
kind: 'passive',
kind: 'duration', activateOnDeploy: true, duration: num(bb.duration), spCost: 0, spType: 'none', trigger: 'NEVER',
onStart({ battle, unit }) {
const v = num(bb.def), dur = num(bb.duration);
if (!(v > 0) || !(dur > 0)) return;
@@ -584,6 +585,7 @@ export default {
});
battle.fx('buff', { x: unit.x, y: unit.y, id: unit.id, kind: 'def' });
},
onEnd({ battle, unit }) { battle.removeBuff(unit, 'gravel:shadow'); },
},
},
talents: [{ install(battle, unit) {
+2 -4
View File
@@ -608,10 +608,8 @@ const KITS = {
skills: altSkills(chess, d, bb, {
'skcom_quickattack[3]': statSkill,
skchr_skadi_2: (s) => ({
kind: 'passive',
onStart({ battle, unit }) { // "部署后N秒内攻击力+X" (a passive starts at every deployment)
battle.addBuff(unit, { key: 'skill:skadi_wave', duration: num(s.bb.duration, 20), mods: { atkPct: num(s.bb.atk) }, visible: true });
},
kind: 'duration', activateOnDeploy: true, duration: num(s.bb.duration, 20), spCost: 0, spType: 'none', trigger: 'NEVER',
mods: { atkPct: num(s.bb.atk) },
}),
}),
talents: [
+63 -31
View File
@@ -495,21 +495,9 @@ const kits = {
},
}),
skchr_ines_3: () => ({
kind: 'passive',
kind: 'duration', duration: s3Dur, spCost: 0, spType: 'none', trigger: 'NEVER',
onStart({ battle, unit }) {
if (!unit.mem.inesS3Placed) {
// 首次部署: place a 影哨 (her talent's sentry, left on retreat) and leave; 立刻刷新再部署时间 — the redeploy
// (auto, paying her DP cost like every redeploy) follows as soon as it is affordable
unit.mem.inesS3Placed = true;
battle.after(0, () => {
if (!unit.alive || !unit.deployed) return;
battle.retreat(unit, { reason: 'retreat' });
unit.respawnAt = battle.time;
battle.fx('sentry', { x: unit.x, y: unit.y, id: unit.id });
}, { owner: unit });
return;
}
battle.addBuff(unit, { key: 'ines:s3', duration: s3Dur, mods: { atkPct: num(bb.atk) }, visible: true });
battle.addBuff(unit, { key: 'ines:s3', mods: { atkPct: num(bb.atk) }, visible: true });
// 立刻收回影哨: the sentry flies back to her and hits ≤ max_target enemies on its way (within
// projectile_range of the segment, [ASSUMED] nearest to its start first)
const from = unit.mem.inesSentryAt;
@@ -533,6 +521,7 @@ const kits = {
}
battle.fx('beam', { x: ax, y: ay, tx: bx, ty: by, id: unit.id });
},
onEnd({ battle, unit }) { battle.removeBuff(unit, 'ines:s3'); },
}),
}),
skill: {
@@ -602,11 +591,27 @@ const kits = {
],
install(battle, unit) {
if (S3) {
battle.on('deploy', (c) => {
if (c.unit !== unit || c.move) return;
if (unit.mem.inesS3Placed) {
unit.skill.activate('deploy');
return;
}
// 首次部署 only places a 影哨 and retreats; it is not a skill activation.
// 立刻刷新再部署时间: redeploy as soon as its DP cost is affordable.
unit.mem.inesS3Placed = true;
battle.after(0, () => {
if (!unit.alive || !unit.deployed) return;
battle.retreat(unit, { reason: 'retreat' });
unit.respawnAt = battle.time;
battle.fx('sentry', { x: unit.x, y: unit.y, id: unit.id });
}, { owner: unit });
}, { owner: unit });
// where the talent's 影哨 stays (every leave but an expiry), for the recall of the next deployment
battle.on('death', (c) => { if (c.unit === unit && c.reason !== 'expired') unit.mem.inesSentryAt = { x: unit.x, y: unit.y }; }, { owner: unit, priority: 5 });
// 技能期间每对一个敌人造成伤害就获得1点部署费用
battle.on('damaged', (c) => {
if (c.source !== unit || c.target.side !== 'enemy' || !(c.amount > 0) || c.type === 'element' || !unit.findBuff('ines:s3')) return;
if (c.source !== unit || c.target.side !== 'enemy' || !(c.amount > 0) || c.type === 'element' || !unit.skill?.active || !(unit.skill.timeLeft > 0)) return;
battle.addDp(unit.ownerId, num(bb.cost, 1));
}, { owner: unit });
}
@@ -1214,17 +1219,12 @@ const kits = {
const g = grid(def.skill?.rangeGrid) || [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]];
const S1 = isSel(def, 'skchr_texas2_1'), S2 = isSel(def, 'skchr_texas2_2');
const dur = num(def.skill?.duration, S1 ? 11 : S2 ? 8 : 6);
const talentAtk = (battle, unit) => battle.addBuff(unit, { key: 'texas2:rainAtk', duration: dur, mods: { atkPct: num(t0.atk, 0.2) } });
const castS1 = (battle, unit) => {
if (!unit.alive || !unit.deployed) return;
talentAtk(battle, unit);
battle.addBuff(unit, { key: 'texas2:drizzle', duration: dur, mods: { atkPct: num(bb.atk) }, visible: true });
battle.fx('swordRain', { x: unit.x, y: unit.y, id: unit.id });
};
const castS2 = (battle, unit) => {
if (!unit.alive || !unit.deployed) return;
talentAtk(battle, unit);
battle.addBuff(unit, { key: 'texas2:shower', duration: dur, mods: { atkPct: num(bb.atk) }, visible: true });
const mr = num(bb.magic_resistance, 0);
for (const e of targetsInGrid(battle, unit, g)) {
battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale, 1.5), type: 'arts', isSkill: true, tags: ['skill', 'burst'] });
@@ -1235,16 +1235,14 @@ const kits = {
};
const castS3 = (battle, unit) => {
if (!unit.alive || !unit.deployed) return;
talentAtk(battle, unit);
for (const e of targetsInGrid(battle, unit, g)) {
for (let i = 0; i < 2 && e.alive; i++) battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb['appear.atk_scale'], 1.15), type: 'arts', isSkill: true, tags: ['skill', 'burst'] });
if (e.alive) battle.applyStatus(e, 'stun', { duration: num(bb['appear.stun'], 1.5), source: unit });
}
battle.fx('swordStorm', { x: unit.x, y: unit.y, id: unit.id });
unit.mem.rainUntil = battle.time + dur;
unit.mem.rainTimer?.cancel();
unit.mem.rainTimer = battle.every(Math.max(0.1, num(bb['texas2_s_3[sword].interval'], 1)), (b, sched) => {
if (!unit.alive || !unit.deployed || b.time > unit.mem.rainUntil + 1e-9) { sched.cancel(); return; }
if (!unit.alive || !unit.deployed || !unit.skill?.active) { sched.cancel(); return; }
const list = targetsInGrid(b, unit, g);
sortEnemyTargets(b, unit, list, null);
for (const e of list.slice(0, Math.max(1, Math.floor(num(bb.max_target, 2))))) {
@@ -1253,28 +1251,62 @@ const kits = {
b.fx('swordRain', { x: e.x, y: e.y, id: e.id });
}
}, { owner: unit });
// Duration advances by dt in the ally phase; align the rain with that clock so the final wave lands before end.
unit.mem.rainTimer.due -= battle.dt;
};
const cast = S1 ? castS1 : S2 ? castS2 : castS3;
const skill = {
kind: 'duration', activateOnDeploy: true, duration: dur, spCost: 0, spType: 'none', trigger: 'NEVER',
mods: { atkPct: num(t0.atk, 0.2) + (S1 || S2 ? num(bb.atk) : 0) },
onStart({ battle, unit, reason }) {
if (reason === 'kill' && unit.mem.texasRecastPending) {
unit.mem.texasRecastPending = false; // the synchronous burst already performed this recast
return;
}
unit.mem.texasCasting = true;
cast(battle, unit);
unit.mem.texasCasting = false;
},
onEnd({ unit, reason }) {
if (reason === 'recast' && unit.mem.texasRecastPending) return;
unit.mem.rainTimer?.cancel();
},
};
return {
skill: { kind: 'passive', onStart({ battle, unit }) { cast(battle, unit); } },
skill,
skills: alt(def, {
skchr_texas2_1: () => ({ kind: 'passive', onStart({ battle, unit }) { cast(battle, unit); } }),
skchr_texas2_2: () => ({ kind: 'passive', onStart({ battle, unit }) { cast(battle, unit); } }),
skchr_texas2_1: () => skill,
skchr_texas2_2: () => skill,
}),
// S2: 攻击变为二连击 while the passive lasts
trait: S2 ? { hitsFn: (b, u) => (u.findBuff('texas2:shower') ? 2 : 1) } : undefined,
trait: S2 ? { hitsFn: (b, u) => (u.skill?.active ? 2 : 1) } : undefined,
talents: [
{ install(battle, unit) { // 德克萨斯传统 (2nd half): first kill of each deployment ⇒ full heal + recast
// (the passive's deploy burst runs inside skill.reset, BEFORE the `deploy` hook: the per-deployment state is
// therefore reset when she leaves the field, so a kill by the deploy burst itself counts)
battle.on('death', (c) => { if (c.unit === unit) unit.mem.texasKilled = false; }, { owner: unit });
battle.on('death', (c) => {
if (c.unit !== unit) return;
unit.mem.texasKilled = unit.mem.texasCasting = unit.mem.texasRecastPending = false;
}, { owner: unit });
battle.on('kill', (c) => {
if (c.killer !== unit || !unit.alive || c.victim.side !== 'enemy' || unit.mem.texasKilled) return;
unit.mem.texasKilled = true;
battle.removeBuff(unit, 'texas2:swordplay');
battle.heal(unit, unit, unit.s.maxHp * num(t0.hp_ratio, 1), { self: true });
cast(battle, unit);
if (unit.mem.texasCasting) {
unit.mem.texasRecastPending = true;
cast(battle, unit); // preserve the burst's synchronous reentry and damage order
} else {
unit.skill.end('recast');
unit.skill.activate('kill', { free: true });
}
}, { owner: unit });
battle.on('skillStart', (c) => {
if (c.unit !== unit || !unit.mem.texasRecastPending) return;
// Complete the lifecycle after observers received the outer start; the burst already recast above.
unit.skill.end('recast');
unit.skill.activate('kill', { free: true });
}, { owner: unit, priority: -2000 });
} },
{ install(battle, unit) { // 德克萨斯剑术: until her first kill after each deployment: ASPD +8, −25 % damage taken
battle.on('deploy', (c) => {
@@ -1290,7 +1322,7 @@ const kits = {
const dot = num(bb['attack@texas2_s_1[dot].dot_damage'], 260), dotIv = Math.max(0.1, num(bb['attack@texas2_s_1[dot].interval'], 1));
battle.on('damaged', (c) => {
const e = c.target;
if (c.source !== unit || !c.dmg?.isAttack || e.side !== 'enemy' || !e.alive || !unit.findBuff('texas2:drizzle')) return;
if (c.source !== unit || !c.dmg?.isAttack || e.side !== 'enemy' || !e.alive || !unit.skill?.active) return;
battle.applyStatus(e, 'silence', { duration: sil, source: unit });
battle.addBuff(e, { key: `texas2:drizzleDot:${unit.id}`, duration: dotDur + 1e-6, interval: dotIv, source: unit, refresh: 'extend', // a re-hit refreshes it, the per-second ticks keep their rhythm
onTick: ({ unit: t }) => battle.dealDamage(unit, t, { amount: dot, type: 'arts', isSkill: true, tags: ['skill', 'dot'] }) });
@@ -1299,7 +1331,7 @@ const kits = {
if (S2) {
// 攻击…造成法术伤害 while the passive lasts (the double hit is the kit trait's hitsFn)
battle.on('hit', (c) => {
if (c.source === unit && c.dmg.isAttack && c.dmg.type === 'phys' && unit.findBuff('texas2:shower')) c.dmg.type = 'arts';
if (c.source === unit && c.dmg.isAttack && c.dmg.type === 'phys' && unit.skill?.active) c.dmg.type = 'arts';
}, { owner: unit, priority: 50 });
}
const a = num(tb.atk, 0); // module (elite): ATK +10 % with no ally on the 4 adjacent tiles
+12 -11
View File
@@ -2679,23 +2679,24 @@ function nearl2(bb, chess, def) {
// (whole hits negated); then she withdraws and this redeploy time is ×respawn_time (×1 when the operator deployed
// right before her is 【卡西米尔】)
skchr_nearl2_2: {
kind: 'passive',
kind: 'duration', activateOnDeploy: true, duration: num(def?.skill?.duration, 22), spCost: 0, spType: 'none', trigger: 'NEVER',
mods: { atkPct: num(bb.atk) },
onStart({ battle, unit, skill }) {
const dur = num(skill.duration, num(def?.skill?.duration, 22));
if (!(dur > 0)) return;
const prev = lastOps.map?.get(unit.ownerId);
const combo = !!prev && prev !== unit && hasBond(prev, 'kazimierzShip');
battle.addBuff(unit, { key: 'nearl2:night', mods: { atkPct: num(bb.atk) }, duration: dur, visible: true });
unit.mem.nearl2Combo = !!prev && prev !== unit && hasBond(prev, 'kazimierzShip');
const hits = Math.floor(num(bb.times));
if (hits > 0) battle.addBuff(unit, { key: 'nearl2:shield', shieldHits: hits, duration: dur, visible: true });
const seq = unit.deploySeq;
battle.after(dur, () => {
if (!live(unit) || unit.deploySeq !== seq) return;
battle.retreat(unit, { reason: 'retreat' });
const mul = combo ? num(bb['nearl2_s_2[withdraw][combo].respawn_time'], 1) : num(bb.respawn_time, 1);
if (Number.isFinite(unit.respawnAt) && mul !== 1) unit.respawnAt = battle.time + (unit.respawnAt - battle.time) * mul;
battle.fx('disappear', { x: unit.x, y: unit.y, id: unit.id, combo });
}, { owner: unit });
},
onEnd({ battle, unit, reason }) {
battle.removeBuff(unit, 'nearl2:shield');
if (reason !== 'duration' || !live(unit)) return;
const combo = unit.mem.nearl2Combo;
battle.retreat(unit, { reason: 'retreat' });
const mul = combo ? num(bb['nearl2_s_2[withdraw][combo].respawn_time'], 1) : num(bb.respawn_time, 1);
if (Number.isFinite(unit.respawnAt) && mul !== 1) unit.respawnAt = battle.time + (unit.respawnAt - battle.time) * mul;
battle.fx('disappear', { x: unit.x, y: unit.y, id: unit.id, combo });
},
},
};
+22 -1
View File
@@ -52,6 +52,12 @@ const TRIGGER_PROFILE = Object.freeze({ canHitFly: true });
/** Every tile of the stage (GDGLOW_SKILL_2: the whole field). */
const ALL_TILES = new Set(Array.from({ length: ROWS * COLS }, (_, i) => i));
/**
* The 'skill' animation window the sim reports to the client: `unit.skillAnimUntil` (snapshot.js `animOf` → ANIM.SKILL,
* and the `['skill', id, 1]` / `0` events). An instant cast and a deploy-time passive both use it.
*/
const SKILL_ANIM_WINDOW = 0.5;
export class SkillRuntime {
/**
* @param {object} battle
@@ -211,12 +217,27 @@ export class SkillRuntime {
this.gainSp(carry && Number.isFinite(carry.sp) ? carry.sp : this.initSp, 'init', true);
// a free (spCost 0) non-passive skill is available once per deployment
if (this.spCost <= 0) this.charges = this.maxCharges;
if (this.spec.activateOnDeploy) this.activate('deploy');
}
_startPassive() {
this.active = true;
this._applyMods();
this._call('onStart', { reason: 'passive' });
// A deploy-time passive (琳琅诗怀雅 S1 仗义疏财 / S2 “见面礼”: kind 'passive' with no duration) never goes through
// `activate()`, so the client saw no 'skill' event at all and its model never played the skill clip the manifest
// carries for it (player report follow-up: the 57 instant clips with no Begin / own Idle — 2 of them passives;
// 凯瑟琳 S1 is `kind: instant` in the sim and already casts). Fire the same bounded window an instant cast uses, so
// the actor plays that clip once and then goes back to its idle with attacks on the normal clip. A passive WITH a
// duration (缄默德克萨斯 S1–S3, 野鬃 S1, 伊内丝 S3, 耀骑士临光 S2) is left alone: the sim keeps it active until
// death, so "how long should its stance show" is a separate question.
if (!this.noSkill && !(this.duration > 0)) {
const b = this.battle;
const u = this.unit;
b._ev(['skill', u.id, 1]);
u.skillAnimUntil = b.time + SKILL_ANIM_WINDOW;
b.after(SKILL_ANIM_WINDOW, () => { if (u.alive) b._ev(['skill', u.id, 0]); }, { owner: u });
}
}
_applyMods() {
@@ -441,7 +462,7 @@ export class SkillRuntime {
if (this.spec.mods || this.spec.flags || this.spec.targeting) this._applyMods();
}
b._ev(['skill', u.id, 1]);
u.skillAnimUntil = b.time + 0.5;
u.skillAnimUntil = b.time + SKILL_ANIM_WINDOW;
this._call('onStart', { reason });
if (b._hooks.skillStart) b.emit('skillStart', { unit: u, skill: this, reason });
// bullets added in skillStart (拉特兰's ×(1.05 + 0.015 × layers), 逃犯引渡手续, talents): the bar's full mark
+5 -2
View File
@@ -89,13 +89,16 @@ export function animOf(u, t) {
/** Snapshot tuple for one unit. */
export function unitTuple(u, t) {
const sk = u.skill;
const spMax = sk && !sk.noSkill ? sk.spCost : 0;
let spMax = sk && !sk.noSkill ? sk.spCost : 0;
let sp = sk && !sk.noSkill ? sk.sp : 0;
if (sk && sk.active && sk.isTimed) {
// show remaining duration/ammo as a draining bar — ammo out of the activation's real total (base + bullets added:
// 拉特兰, 逃犯引渡手续, refills; community report #35), so every bullet shortens it
if (sk.kind === 'ammo') sp = spMax * (sk.ammoLeft / Math.max(1, sk.ammoMax || sk.ammo || 0, sk.ammoLeft));
else if (Number.isFinite(sk.timeLeft) && sk.duration > 0) sp = spMax * (sk.timeLeft / sk.duration);
else if (Number.isFinite(sk.timeLeft) && sk.duration > 0) {
if (spMax === 0) spMax = sk.duration;
sp = spMax * (sk.timeLeft / sk.duration);
}
}
// hp is rounded up (a living unit never shows 0) but never above the rounded max HP
const maxHp = Math.max(1, Math.round(u.s.maxHp));
+134 -2
View File
@@ -71,6 +71,28 @@ test('迅捷: member skill end +12 SP (p=1 at high L), ≥40 layers every operat
assert.equal(off.s_x, 0);
});
test('迅捷 / 突袭: SP gifts after end do not recharge a zero-SP deployment skill or trigger a ready raid', () => {
const h = makeBattle({
defs: { chess: { t_deploy: chessRec({ id: 't_deploy', bonds: ['swiftShip', 'raidShip'], skill: { spCost: 0 } }) } },
units: [{ chessId: 't_deploy', row: 10, col: 4 }],
bonds: { swiftShip: bond(1, 1000), raidShip: bond(1) }, captureNoisy: true,
kits: { t_deploy: () => ({ skill: {
kind: 'duration', activateOnDeploy: true, duration: 2, spCost: 0, spType: 'none', trigger: 'NEVER',
} }) },
});
h.b.start();
const u = h.unit('t_deploy'), seq = u.deploySeq;
h.run(5);
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['duration']);
assert.ok(h.hooksOf('spGain').some((c) => c.unit === u), 'swift end handler attempted SP gain');
assert.equal(u.skill.sp, 0);
assert.equal(u.skill.charges, 0);
assert.equal(u.skill.ready, false);
assert.equal(u.skill.activations, 1);
assert.equal(u.deploySeq, seq, 'skill end alone does not meet raid readiness');
checkInvariants(h.b);
});
test('迅捷 / 不屈 proc chances: p = min(1, base + per·L) at each layer count', () => {
const sw = bondBb('swiftShip'), ind = bondBb('indomShip');
close(procChance(sw, 0), 0.20);
@@ -83,7 +105,7 @@ test('迅捷 / 不屈 proc chances: p = min(1, base + per·L) at each layer coun
close(procChance(ind, 1000), 1, 'capped');
});
test('灵巧: aura on members + 4 neighbours (once), 8 tiles at ≥40 layers, follows death and relocation', () => {
test('灵巧: aura on members + 4 neighbours (once), 8 tiles at ≥40 layers, survives death and follows relocation', () => {
const defs = { chess: { k_1: op('k_1', ['skillfulShip']), k_2: op('k_2', ['skillfulShip']), k_n: op('k_n', []), k_d: op('k_d', []), k_f: op('k_f', []) } };
const h = makeBattle({
defs, bonds: { skillfulShip: bond(1, 5) },
@@ -107,7 +129,117 @@ test('灵巧: aura on members + 4 neighbours (once), 8 tiles at ≥40 layers, fo
assert.equal(a('k_n'), 100, 'relocated out of the aura');
h.b.dealDamage(null, h.unit('k_1'), { amount: 1e9, type: 'true' });
h.step(2);
assert.equal(a('k_d'), 100, 'aura source died');
assert.equal(a('k_d'), 150, 'downed aura source still buffs neighbours');
checkInvariants(h.b);
});
for (const layers of [5, 40]) {
test(`灵巧: downed sources keep one aura at ${layers} layers, update live layers and restore it on redeploy`, () => {
const defs = { chess: {
k_1: op('k_1', ['skillfulShip']), k_2: op('k_2', ['skillfulShip']),
k_n: op('k_n', []), k_d: op('k_d', []),
} };
const h = makeBattle({
defs, bonds: { skillfulShip: bond(1, layers) },
units: [
{ chessId: 'k_1', row: 10, col: 4 }, { chessId: 'k_2', row: 10, col: 6 },
{ chessId: 'k_n', row: 10, col: 5 }, { chessId: 'k_d', row: 11, col: 5 },
],
});
h.step();
const first = h.unit('k_1'), second = h.unit('k_2'), neighbour = h.unit('k_n'), diagonal = h.unit('k_d');
assert.equal(neighbour.s.aspd, 110 + layers, 'overlapping sources buff once');
h.b.kill(first);
assert.ok(h.b.isDown(first));
assert.equal(neighbour.s.aspd, 110 + layers, 'no attack speed drop on death');
h.b.kill(second);
assert.ok(h.b.isDown(second));
h.run(1);
assert.equal(neighbour.s.aspd, 110 + layers, 'all sources down: polling keeps the aura');
assert.equal(diagonal.s.aspd, layers >= 40 ? 110 + layers : 100, '4 / 8 tile range still applies');
assert.equal(neighbour.buffs.filter((b) => b.key === 'bond:skillfulShip').length, 1);
gainLayers(h.b, { playerId: 'p1', bonds: 'skillfulShip', n: 40 - layers + 2 });
h.step(2);
assert.equal(neighbour.s.aspd, 152, 'downed sources use live layer values');
assert.equal(diagonal.s.aspd, 152, 'downed sources widen their aura at 40 layers');
h.b.kill(neighbour);
assert.ok(h.b.redeploy(neighbour, { free: true }));
assert.equal(neighbour.s.aspd, 152, 'recipient redeployed next to a downed source');
assert.ok(h.b.redeploy(first, { free: true }));
assert.equal(first.s.aspd, 152, 'source regains its own bonus on redeploy');
assert.equal(neighbour.s.aspd, 152, 'redeploy does not stack the aura');
h.b.retreat(first);
assert.equal(neighbour.s.aspd, 152, 'remaining downed source still covers the recipient');
assert.ok(h.b.redeploy(second, { free: true }));
h.b.retreat(second);
assert.equal(neighbour.s.aspd, 100, 'withdrawn sources no longer provide an aura');
checkInvariants(h.b);
});
}
test('灵巧: a downed source provides its aura around the body tile when it returns home after death', () => {
const defs = { chess: {
k_1: op('k_1', ['skillfulShip']), k_2: op('k_2', ['skillfulShip']),
k_home: op('k_home', []), k_away: op('k_away', []), k_tile: op('k_tile', []),
} };
const h = makeBattle({
defs, bonds: { skillfulShip: bond(1, 5) },
units: [
{ chessId: 'k_1', row: 10, col: 4 }, { chessId: 'k_2', row: 12, col: 3 },
{ chessId: 'k_home', row: 10, col: 5 }, { chessId: 'k_away', row: 10, col: 8 },
{ chessId: 'k_tile', row: 10, col: 7 },
],
});
h.step();
h.b.retreat(h.unit('k_tile'), { permanent: true });
assert.ok(h.b.relocate(h.unit('k_1'), 10, 7));
h.run(0.4);
assert.equal(h.unit('k_home').s.aspd, 100);
assert.equal(h.unit('k_away').s.aspd, 115);
h.b.kill(h.unit('k_1'));
assert.deepEqual(h.unit('k_1').body, [10, 4]);
assert.equal(h.unit('k_home').s.aspd, 115, 'aura follows the downed body back home');
assert.equal(h.unit('k_away').s.aspd, 100, 'last living tile no longer provides an aura');
checkInvariants(h.b);
});
for (const reason of ['retreat', 'forcedExit', 'merchant']) {
test(`灵巧: ${reason} leaves a down model without preserving the killed-source aura`, () => {
const h = makeBattle({
defs: { chess: { k_1: op('k_1', ['skillfulShip']), k_n: op('k_n', []) } },
bonds: { skillfulShip: bond(1, 5) },
units: [{ chessId: 'k_1', row: 10, col: 4 }, { chessId: 'k_n', row: 10, col: 5 }],
});
h.step();
const source = h.unit('k_1'), recipient = h.unit('k_n');
assert.equal(recipient.s.aspd, 115);
h.b.retreat(source, { reason });
assert.ok(h.b.isDown(source), 'a down model is not proof of being killed');
assert.equal(source.removeReason, reason);
assert.equal(recipient.s.aspd, 100, 'aura stops immediately on retreat');
assert.ok(h.b.redeploy(source, { free: true }));
assert.equal(recipient.s.aspd, 115, 'the living source restores its aura');
h.b.kill(source);
assert.equal(recipient.s.aspd, 115, 'a subsequent real kill keeps the aura');
checkInvariants(h.b);
});
}
test('灵巧: unite carry-down forcedExit starts without an aura and restores it on redeploy', () => {
const h = makeBattle({
kind: 'unite',
defs: { chess: { k_1: op('k_1', ['skillfulShip']), k_n: op('k_n', []) } },
bonds: { skillfulShip: bond(1, 40) },
units: [{ chessId: 'k_1', row: 10, col: 4, carryState: { down: true } }, { chessId: 'k_n', row: 11, col: 5 }],
});
h.step();
const source = h.unit('k_1'), recipient = h.unit('k_n');
assert.equal(source.removeReason, 'forcedExit');
assert.ok(h.b.isDown(source));
assert.equal(recipient.s.aspd, 100, 'forcedExit is not a kill in this battle');
assert.ok(h.b.redeploy(source, { free: true }));
assert.equal(recipient.s.aspd, 150, 'the redeployed source covers all eight neighbours');
checkInvariants(h.b);
});
+18
View File
@@ -165,6 +165,24 @@ test('炎国短刀: each skill activation ATK +5 % / +8 % (one multiplier), at m
}
});
test('炎国短刀: timed deployment skill adds one stack per deployment, including carry.skillActive', () => {
const id = 'chess_char_1_19_a';
for (const [item, per] of [[A('3_04'), 0.05], [B('3_04'), 0.08]]) {
const h = makeBattle({ units: [{ chessId: id, row: 10, col: 4, skillIndex: 0, items: [item], carryState: { skillActive: true } }] });
h.b.start();
const u = h.unit(id);
assert.equal(u.skill.activations, 1);
close(u.s.atk, u.base.atk * (1 + per), 'one deployment stack');
h.run(u.skill.duration + 1);
close(u.s.atk, u.base.atk * (1 + per), 'no extra stack on end');
h.b.retreat(u);
assert.ok(h.b.redeploy(u, { free: true }));
assert.equal(u.skill.activations, 2);
close(u.s.atk, u.base.atk * (1 + per * 2), 'one more on redeployment');
checkInvariants(h.b);
}
});
test('迅捷作战粮: on deploy SP +3 / +6, plus as much per other operator sharing a bond', () => {
for (const [id, each] of [[A('3_05'), 3], [B('3_05'), 6]]) {
const ops = { t_op: op('t_op', { bonds: ['swiftShip'] }), t_a: op('t_a', { bonds: ['swiftShip'] }), t_b: op('t_b', { bonds: ['yanShip'] }) };
+54 -4
View File
@@ -444,6 +444,8 @@ test('1_18 宴 S1 分神: no attacks, block 0 (releases), DEF +def, regenerates
assert.equal(u.s.blockCnt, 0);
assert.equal(u.blocking.length, 0);
assert.equal(e.blockedBy, null, 'blocked enemy released');
h.b.dealDamage(u, e, { amount: 100, type: 'phys', isAttack: true });
assert.equal(dealt(h, u).at(-1).type, 'phys', 'active S1 does not inherit S2 arts conversion');
approx(u.s.def, u.base.def * (1 + bb.def), `${id} DEF`);
approx(u.s.hpRegen, u.s.maxHp * bb.hp_recovery_per_sec_by_max_hp_ratio, 'regen');
u.hp = u.s.maxHp * 0.3;
@@ -462,23 +464,71 @@ test('1_18 宴 S1 分神: no attacks, block 0 (releases), DEF +def, regenerates
}
});
test('1_19 野鬃 S1 骑枪刺击 (PASSIVE): ASPD +attack_speed for `duration` s after every deployment', () => {
test('1_18 宴 S2: deployment loses current HP once; ATK and normal-attack arts conversion end with the duration', () => {
for (const id of pair('18')) {
const s = rec(id, 'skchr_utage_2'), bb = s.bb;
const h = run({ defs: { chess: noGarrison(id), enemies: { e: dummy('e') } },
units: [entry(id, s.skillId, { row: 10, col: 4, carryState: { hpPct: 0.5, skillActive: true } })] });
h.b.start();
const u = h.unit(id);
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.activations, 1);
assert.equal(u.skill.charges, 0);
approx(u.hp, u.s.maxHp * 0.5 * (1 - bb.hp_ratio), 'one loss from carried current HP');
approx(u.s.atk, u.base.atk * (1 + bb.atk));
const e = h.spawn('e', { pos: [10, 8] });
const hit = () => { h.b.dealDamage(u, e, { amount: 100, type: 'phys', isAttack: true }); return dealt(h, u).at(-1); };
assert.equal(hit().type, 'arts');
assert.ok(!hit().dmg.isSkill, 'normal attack marker retained');
h.run(bb.duration / 2);
approx(u.skill.timeLeft, bb.duration / 2);
h.run(bb.duration / 2 + 0.1);
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
approx(u.s.atk, u.base.atk);
assert.equal(hit().type, 'phys');
assert.deepEqual(ended(h, u).map((c) => c.reason), ['duration']);
const hp = u.hp;
h.run(3);
approx(u.hp, hp, 'no second loss while staying deployed');
h.b.retreat(u);
assert.ok(h.b.redeploy(u, { free: true }));
assert.equal(u.skill.activations, 2);
approx(u.hp, u.s.maxHp * (1 - bb.hp_ratio), 'new deployment loses current HP once');
done(h);
}
});
test('1_19 野鬃 S1 骑枪刺击: ASPD +attack_speed for `duration` s after every deployment', () => {
for (const id of pair('19')) {
const s = rec(id, 'skchr_wildmn_1');
const h = run({ defs: { chess: noGarrison(id) }, units: [entry(id, s.skillId, { row: 9, col: 5 })] });
const u = sel(h, id, s.skillId);
assert.equal(u.skill.kind, 'passive');
h.step();
h.b.start();
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.active, true);
assert.equal(u.skill.activations, 1);
assert.equal(u.skill.charges, 0);
assert.equal(u.skill.ready, false);
assert.equal(started(h, u)[0].reason, 'deploy');
approx(u.s.aspd, u.base.aspd + s.bb.attack_speed, `${id} ASPD after deployment`);
h.run(s.duration - 0.5);
h.run(s.duration / 2);
approx(u.skill.timeLeft, s.duration / 2);
assert.deepEqual(h.snapshot().units.find((t) => t[0] === u.id).slice(5, 7), [s.duration / 2, s.duration]);
h.run(s.duration / 2 - 0.5);
approx(u.s.aspd, u.base.aspd + s.bb.attack_speed, 'still up');
h.run(1);
approx(u.s.aspd, u.base.aspd, `expired after ${s.duration} s`);
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
assert.deepEqual(ended(h, u).map((c) => c.reason), ['duration']);
h.b.kill(u, null);
h.step();
assert.ok(h.b.redeploy(u, { free: true }));
h.step();
approx(u.s.aspd, u.base.aspd + s.bb.attack_speed, 'again after the redeployment');
assert.equal(u.skill.activations, 2);
assert.equal(ended(h, u).length, 1, 'expired skill does not end again on death');
done(h);
}
});
+8
View File
@@ -342,12 +342,20 @@ test('2_12 砾 S1 影袭: at deployment DEF +def decaying to 0 over `duration` s
h.run(0.5);
approx(u.s.def, u.base.def * (1 + aura + bb.def), `${id} full bonus`);
assert.equal(u.s.shield, 0, 'no 鼠群 barrier');
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.active, true);
assert.equal(u.skill.ready, false);
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], bb.duration);
h.run(1);
approx(u.s.def, u.base.def * (1 + aura + bb.def * (bb.duration - 1) / bb.duration), 'one step down after 1 s');
h.run(0.4);
approx(u.s.def, u.base.def * (1 + aura + bb.def * (bb.duration - 1) / bb.duration), 'steps once per second');
h.run(bb.duration);
approx(u.s.def, u.base.def * (1 + aura), 'gone');
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
assert.equal(u.findBuff('gravel:shadow'), null);
assert.equal(h.hooksOf('skillEnd').filter((c) => c.unit === u && c.reason === 'duration').length, 1);
done(h);
}
});
+21 -6
View File
@@ -368,19 +368,34 @@ test('3_05 斯卡蒂 S1 迅捷打击·γ型 ATK/ASPD; S2 跃浪击 ATK + for `du
const g = makeBattle({ timeLimit: 60, units: [U(id, 'skchr_skadi_2', 10, 4)] });
const v = g.unit(id);
g.step();
assert.equal(v.skill.kind, 'passive');
assert.equal(v.skill.kind, 'duration');
assert.equal(v.skill.active, true);
assert.equal(v.skill.charges, 0);
assert.equal(v.skill.ready, false);
assert.equal(g.hooksOf('skillStart')[0].reason, 'deploy');
approx(v.s.atk, v.base.atk * (1 + b2.atk + own));
if (id.endsWith('_b')) {
const left = v.skill.timeLeft;
g.b.dealDamage(null, v, { type: 'true', amount: 1e7 });
assert.equal(v.alive, true, 'DRE-Y revives in place during the window');
assert.equal(v.skill.activations, 1);
approx(v.skill.timeLeft, left, 1e-9, 'revival preserves the remaining skill time');
}
g.run(b2.duration - 0.5);
approx(v.s.atk, v.base.atk * (1 + b2.atk + own));
g.run(1);
approx(v.s.atk, v.base.atk * (1 + own), 1e-6, 'gone after its duration');
assert.equal(v.skill.active, false);
assert.equal(v.skill.ready, false);
assert.deepEqual(g.hooksOf('skillEnd').map((c) => c.reason), ['duration']);
// redeployed ⇒ again
g.b.dealDamage(null, v, { type: 'true', amount: 1e7 });
if (!v.alive) {
g.b.redeploy(v, { free: true });
g.step();
approx(v.s.atk, v.base.atk * (1 + b2.atk + own), 1e-6, 'every deployment');
}
assert.equal(v.skill.activations, 1, 'DRE-Y in-place revival does not deploy or restart S2');
if (v.alive) g.b.retreat(v);
assert.ok(g.b.redeploy(v, { free: true }));
approx(v.skill.timeLeft, b2.duration);
assert.equal(v.skill.activations, 2);
approx(v.s.atk, v.base.atk * (1 + b2.atk + own), 1e-6, 'every deployment');
done(g);
}
// DRE-X: ×atk_scale on blocked enemies; no DRE-Y revive
+87 -9
View File
@@ -265,6 +265,13 @@ test('伊内丝 S3 独影归途: first deployment leaves a sentry and retreats;
const sk = D(id, 2).skill, bb = sk.bb;
const h = battle([U(id, 10, 5, 2)], { flags: { dpInit: 99, dpPerSec: 0 } });
const u = h.unit(id);
h.b.start();
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.active, false, 'first deployment does not activate the skill');
assert.equal(u.skill.activations, 0);
assert.equal(h.hooksOf('skillStart').filter((c) => c.unit === u).length, 0, 'placing a sentry emits no skillStart');
assert.equal(u.skill.timeLeft, 0, 'first deployment places the sentry without a full effect window');
assert.equal(u.findBuff('ines:s3'), null);
const near = [];
for (let i = 0; i < 3; i++) near.push(h.spawn('enemy_dummy', { pos: [10, 6] }), h.spawn('enemy_dummy', { pos: [10, 4] }));
const far = h.spawn('enemy_dummy', { pos: [10, 8] });
@@ -273,9 +280,14 @@ test('伊内丝 S3 独影归途: first deployment leaves a sentry and retreats;
assert.ok(left && left.reason === 'retreat', `${id}: leaves right after the first deployment`);
assert.ok(h.runUntil(() => u.alive && u.deployed, 2), 'redeploy timer refreshed: back at once');
const buff = u.findBuff('ines:s3');
assert.ok(buff, 'passive buff on the redeploy');
assert.ok(buff, 'ATK effect on the redeploy');
approx(buff.mods.atkPct, bb.atk, 1e-9);
approx(buff.timeLeft, sk.duration, 0.1);
approx(u.skill.timeLeft, sk.duration, 0.1);
assert.equal(u.skill.active, true);
assert.equal(u.skill.activations, 1);
assert.equal(h.hooksOf('skillStart').filter((c) => c.unit === u).length, 1, 'the redeployment starts the skill once');
assert.equal(u.skill.ready, false);
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], sk.duration);
const recall = dmgBy(h, u, tagged('sentryRecall'));
assert.equal(recall.length, bb.max_target, `${id}: recall hits ≤ ${bb.max_target} of 6`);
assert.ok(recall.every((c) => near.includes(c.target)) && !recall.some((c) => c.target === far));
@@ -287,6 +299,15 @@ test('伊内丝 S3 独影归途: first deployment leaves a sentry and retreats;
assert.ok(hits > 0);
assert.equal(h.b.getPlayer('p1').dp, dpAt + hits * bb.cost, 'one DP per damage while the passive lasts');
assert.equal(h.hooksOf('death').filter((c) => c.unit === u).length, 1, 'only the first deployment leaves');
h.run(sk.duration);
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
assert.equal(u.findBuff('ines:s3'), null);
const dpEnd = h.b.getPlayer('p1').dp;
h.run(2);
assert.equal(h.b.getPlayer('p1').dp, dpEnd, 'no skill DP after the actual window ends');
assert.equal(h.hooksOf('death').filter((c) => c.unit === u).length, 1, 'stays deployed after the window');
assert.deepEqual(h.hooksOf('skillEnd').filter((c) => c.unit === u).map((c) => c.reason), ['duration']);
checkInvariants(h.b);
}
});
@@ -611,10 +632,11 @@ test('缄默德克萨斯 S1 细雨无声: passive ATK up for its duration; hits
const h = battle([U(id, 10, 4, 0)]);
h.step();
const u = h.unit(id);
const buff = u.findBuff('texas2:drizzle');
assert.ok(buff, `${id}: passive active on deployment`);
approx(buff.mods.atkPct, bb.atk, 1e-9);
assert.ok(u.findBuff('texas2:rainAtk'), 'talent 德克萨斯传统 ATK during the passive');
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.active, true);
assert.equal(u.skill.charges, 0);
approx(u.skill.spec.mods.atkPct, bb.atk + D(id, 0).talents[0].bb.atk, 1e-9, 'skill and 德克萨斯传统 ATK');
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], sk.duration);
const e = h.spawn('enemy_dummy', { pos: [10, 5] });
h.run(3);
assert.ok(e.s.flags.silence, 'hit target silenced (失去特殊能力)');
@@ -623,7 +645,10 @@ test('缄默德克萨斯 S1 细雨无声: passive ATK up for its duration; hits
assert.ok(dot.length >= 2, `a DoT tick every second although she re-hits faster (${dot.length})`);
for (const c of dot) approx(c.amount, bb['attack@texas2_s_1[dot].dot_damage'], 1e-9, 'fixed arts per second (RES 0)');
h.run(sk.duration);
assert.ok(!u.findBuff('texas2:drizzle'), 'ends with the skill duration');
assert.equal(u.skill.active, false, 'ends with the skill duration');
assert.equal(u.skill.ready, false);
assert.equal(h.hooksOf('skillEnd').filter((c) => c.unit === u && c.reason === 'duration').length, 1);
assert.ok(e.s.flags.silence && e.findBuff(`texas2:drizzleDot:${u.id}`), 'applied enemy effects keep their own duration');
h.run(bb['attack@silence'] + 1);
assert.ok(!e.s.flags.silence && !e.findBuff(`texas2:drizzleDot:${u.id}`), 'no new silence / DoT after the passive');
}
@@ -657,8 +682,8 @@ test('缄默德克萨斯 S2 阵雨连绵: deploy burst atk_scale arts + RES down
approx(burst[0].amount, u.s.atk * bb.atk_scale * 0.5, 1e-6, 'arts burst vs RES 50');
approx(e.s.res, 50 * (1 + bb.magic_resistance), 1e-6, 'RES down');
assert.equal(far.s.res, 50);
approx(u.findBuff('texas2:shower').mods.atkPct, bb.atk, 1e-9);
approx(u.findBuff('texas2:shower').timeLeft, sk.duration, 1e-6);
approx(u.skill.spec.mods.atkPct, bb.atk + D(id, 1).talents[0].bb.atk, 1e-9);
approx(u.skill.timeLeft, sk.duration, 1e-6);
h.run(2);
const hits = dmgBy(h, u, (c) => c.dmg.isAttack);
assert.ok(hits.length >= 2 && hits.every((c) => c.type === 'arts'), 'arts attacks');
@@ -666,6 +691,8 @@ test('缄默德克萨斯 S2 阵雨连绵: deploy burst atk_scale arts + RES down
for (const c of hits) per.set(c.dmg.attackId, (per.get(c.dmg.attackId) ?? 0) + 1);
assert.ok([...per.values()].every((v) => v === 2), 'double hits');
h.run(sk.duration);
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
const n0 = dmgBy(h, u, (c) => c.dmg.isAttack).length;
h.run(3);
const late = dmgBy(h, u, (c) => c.dmg.isAttack).slice(n0);
@@ -673,6 +700,57 @@ test('缄默德克萨斯 S2 阵雨连绵: deploy burst atk_scale arts + RES down
}
});
test('缄默德克萨斯 S1/S2/S3: first kill reopens a full window, including a kill inside the deployment burst', () => {
for (const id of pair('chess_char_4_16_a')) for (const index of [0, 1, 2]) {
const life = [];
const h = battle([U(id, 10, 4, index)], { extra: { setup(b) {
for (const name of ['skillStart', 'skillEnd']) b.on(name, (c) => life.push([name, c.reason]), { priority: 1000 });
} } });
h.b.start();
const u = h.unit(id), sk = u.skill;
h.run(2);
u.hp = u.s.maxHp / 2;
const e = h.spawn('enemy_dummy', { pos: [10, 5] });
h.b.kill(e, u);
assert.equal(u.mem.texasKilled, true);
approx(u.hpRatio, 1);
assert.equal(u.findBuff('texas2:swordplay'), null);
assert.equal(sk.activations, 2);
approx(sk.timeLeft, sk.duration);
assert.equal(sk.charges, 0);
assert.equal(sk.ready, false);
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['recast']);
h.b.kill(h.spawn('enemy_dummy', { pos: [10, 5] }), u);
assert.equal(sk.activations, 2, 'only first kill recasts');
h.run(sk.duration + 0.1);
assert.equal(sk.active, false);
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['recast', 'duration']);
assert.deepEqual(h.hooksOf('skillStart').map((c) => c.reason), ['deploy', 'kill']);
h.b.retreat(u);
const lifeAt = life.length;
const weak = h.spawn('enemy_dummy', { pos: [10, 5] });
weak.hp = 1;
assert.ok(h.b.redeploy(u, { free: true }));
if (index === 0) h.b.kill(weak, u); // S1 has no deployment burst
assert.equal(u.mem.texasKilled, true);
assert.equal(sk.activations, 4, 'new deployment and first-kill recast, including synchronous burst reentry');
assert.deepEqual(life.slice(lifeAt), [['skillStart', 'deploy'], ['skillEnd', 'recast'], ['skillStart', 'kill']]);
assert.equal(u.findBuff('texas2:swordplay'), null);
approx(sk.timeLeft, sk.duration);
if (index === 2) {
const target = h.spawn('enemy_dummy', { pos: [10, 5] });
h.run(sk.duration + 0.1);
const waves = new Set(dmgBy(h, u, tagged('swordRain')).filter((c) => c.target === target).map((c) => c.t));
assert.equal(waves.size, sk.duration, 'reentrant deployment keeps a full set of rain waves');
const n = dmgBy(h, u, tagged('swordRain')).length;
h.run(2);
assert.equal(dmgBy(h, u, tagged('swordRain')).length, n, 'rain stops at skill end');
assert.ok(target.alive);
}
checkInvariants(h.b);
}
});
test('缄默德克萨斯 module none: no ATK +10 % when alone (EXE-Y default has it)', () => {
const has = (m) => { const h = battle([U('chess_char_4_16_b', 10, 4, null, m)]); h.run(0.5); return !!h.unit('chess_char_4_16_b').findBuff('texas2:module'); };
assert.equal(has(null), true);
+21 -2
View File
@@ -999,7 +999,11 @@ test('6_17 耀骑士临光 S2 逐夜烁光: on deploy ATK + and 3 护盾 layers
const u = h.unit(id);
usesSkill(u, sid);
h.step();
approx(u.findBuff('nearl2:night').mods.atkPct, bb.atk);
approx(u.skill.spec.mods.atkPct, bb.atk);
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.active, true);
assert.equal(u.skill.ready, false);
assert.equal(h.snapshot().units.find((t) => t[0] === u.id)[6], dur);
assert.equal(u.findBuff('nearl2:shield').shieldHits, bb.times);
h.run(bb.times * 1.2 + 1);
const taken = h.hooksOf('damaged').filter((c) => c.target === u);
@@ -1009,15 +1013,30 @@ test('6_17 耀骑士临光 S2 逐夜烁光: on deploy ATK + and 3 护盾 layers
assert.ok(h.runUntil(() => !u.alive, dur + 1));
approx(h.b.time, dur, 'withdraws when it ends', 0.05);
approx(u.respawnAt - h.b.time, u.base.respawnTime * bb.respawn_time, 'this redeploy ×1.25', 1e-3);
assert.equal(u.removeReason, 'retreat');
assert.equal(u.skill.active, false);
assert.deepEqual(h.hooksOf('skillEnd').filter((c) => c.unit === u).map((c) => c.reason), ['duration']);
done(h);
const k = run({ defs: { chess: { kaz: plain('kaz', { bonds: ['kazimierzShip'] }) } }, units: [U(id, sid, 10, 4), { chessId: 'kaz', row: 12, col: 4 }] });
const ku = k.unit(id);
k.step(2);
assert.ok(ku.alive && ku.findBuff('nearl2:night'));
assert.ok(ku.alive && ku.skill.active);
assert.ok(k.runUntil(() => !ku.alive, dur + 1));
approx(ku.respawnAt - k.b.time, ku.base.respawnTime, '卡西米尔 before her: no extension', 1e-3);
done(k);
const dead = run({ units: [U(id, sid, 10, 4, { moduleId: 'none' })] });
dead.step();
const du = dead.unit(id);
dead.b.kill(du);
const respawnAt = du.respawnAt;
approx(respawnAt - dead.b.time, du.base.respawnTime, 'early death keeps ordinary redeployment');
assert.equal(du.findBuff('nearl2:shield'), null);
dead.run(dur + 1);
assert.equal(du.respawnAt, respawnAt, 'no delayed retreat or multiplier after death');
assert.deepEqual(dead.hooksOf('skillEnd').filter((c) => c.unit === du).map((c) => c.reason), ['death']);
done(dead);
}
});
+13
View File
@@ -942,6 +942,10 @@ test('2_12 砾: 鼠群 barrier hp_ratio × max HP decaying over `duration` s;
assert.equal(u.base.cost, raw(id).stats.cost + t.cost);
h.step();
const full = u.s.maxHp * bb.hp_ratio;
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.active, true);
assert.equal(u.skill.ready, false);
assert.deepEqual(h.snapshot().units.find((t) => t[0] === u.id).slice(5, 7), [Math.round(u.skill.timeLeft * 10) / 10, bb.duration]);
approx(u.s.shield, full, 'full barrier', 1e-2);
h.run(bb.duration / 2 + 0.5);
approx(u.s.shield, full / 2, 'half decayed', 1e-2);
@@ -949,6 +953,9 @@ test('2_12 砾: 鼠群 barrier hp_ratio × max HP decaying over `duration` s;
approx(u.s.shield, full / 2, 'PRTS: the capacity steps once per second', 1e-2);
h.run(bb.duration / 2);
assert.equal(u.s.shield, 0);
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
assert.equal(u.findBuff('gravel:rats'), null);
done(h);
// PRTS "受到伤害后,剩余容量根据屏障最大容量等比变化": a damaged barrier shrinks in proportion, not to the capacity
const hd = run({ units: [{ chessId: id, row: 9, col: 5 }] });
@@ -960,6 +967,12 @@ test('2_12 砾: 鼠群 barrier hp_ratio × max HP decaying over `duration` s;
hd.run(bb.duration / 2 + 0.5);
approx(ud.s.shield, cap0 * 0.4 / 2, 'remaining × capacity ratio (a plain cap would keep 40 %)', 1e-2);
assert.ok(ud.hp === ud.s.maxHp);
hd.b.retreat(ud);
assert.equal(ud.findBuff('gravel:rats'), null);
assert.equal(ud.skill.active, false);
assert.ok(hd.b.redeploy(ud, { free: true }));
approx(ud.skill.timeLeft, bb.duration);
approx(ud.s.shield, cap0, 'fresh shield on redeployment');
done(hd);
const idb = 'chess_char_2_12_b', tb = tal(idb);
const cheapRec = chessRec({ id: 't_cheap', stats: { cost: tb['cond.cost'] } });
+4 -2
View File
@@ -645,7 +645,8 @@ test('缄默德克萨斯 S3 (passive): deploy burst 2 × 115 % arts + 1.5 s stun
assert.equal(waves.size, D(id).skill.duration, 'one rain per second while the passive lasts');
assert.ok([...waves.values()].every((n) => n === bb.max_target), '≤ 2 distinct targets');
approx(rain[0].amount, u.base.atk * (1 + t0.atk) * bb.atk_scale, 1e-6);
assert.equal(u.findBuff('texas2:rainAtk'), null, 'ATK bonus ends with the passive');
assert.equal(u.skill.active, false, 'ATK bonus ends with the skill');
approx(u.s.atk, u.base.atk);
});
test('缄默德克萨斯: first kill ⇒ full heal + recast; 剑术 buff removed', () => {
@@ -660,7 +661,8 @@ test('缄默德克萨斯: first kill ⇒ full heal + recast; 剑术 buff removed
assert.ok(u.mem.texasKilled);
approx(u.hpRatio, 1, 1e-3, 'full heal');
assert.equal(u.findBuff('texas2:swordplay'), null);
assert.ok(u.findBuff('texas2:rainAtk'), 'passive recast');
assert.equal(u.skill.active, true, 'skill recast');
assert.equal(u.skill.activations, 2);
});
test('星熊 S2 (passive): DEF +13 % and 65 % ATK thorns; 战术装甲 negates ~25 % of hits; 重装 aura DEF +6 %', () => {
+38 -1
View File
@@ -11,6 +11,14 @@
// Without the extraction the web alias is drawn tinted toward the slug's own colours (render/units.js ALIAS_TINT,
// research 07 §5.6 "a hue shift" [ASSUMED look]), so source installs still tell them apart from the plain slug.
// 高能 / 冰爆 / 简饲源石虫 and “庞贝” always had their own models (checked in headless Chrome).
//
// Root cause (2026-10-04): Ark-Models *indexes* 1305_mhslim / 1305_mhslim_2 with an EMPTY `assetList` — registered,
// never uploaded — so `arkModel()` finds nothing and the alias chain drops to enemy_1007_slime, a *different* enemy
// rather than a variant of it. The mobile build does ship their own model (straight-alpha pages, no `pma: true` line),
// and it is reachable, but its only public mirror is a community wiki rather than a GitHub dump: adding it would put a
// non-GitHub host into `sources.mjs` for two models, which the reviewer of this change asked not to do. So the tinted
// alias stays the web model, the local extraction stays the overlay, and the last block below locks both halves
// (only these two enemies borrow a different enemy's skeleton; every asset source stays a GitHub dump).
import { test, describe, before, after } from 'node:test';
import assert from 'node:assert/strict';
@@ -19,7 +27,8 @@ import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { buildPlan } from '../tools/assets/plan.mjs';
import { buildPlan, ENEMY_SPINE_ALIAS } from '../tools/assets/plan.mjs';
import { RAW } from '../tools/assets/sources.mjs';
import { resolveTemplate, collectLeaves } from '../tools/assets/manifest.mjs';
import { findLocalEnemyModels, localEnemySpineMeta, loadLocalEnemySpines, LOCAL_ENEMY_SPINES_FILE, localEnemySpineGroup } from '../tools/assets/spine.mjs';
import { indexAudio } from '../tools/assets/audio.mjs';
@@ -174,6 +183,34 @@ describe('D3: the official slug models are an optional overlay of the web alias'
});
});
describe('D3 root cause: only the two slugs borrow a different enemy\'s skeleton, and no non-GitHub source is added', () => {
test('plan: ENEMY_SPINE_ALIAS covers exactly the two slugs (Ark-Models indexes them with an empty assetList)', () => {
const p = plan(undefined);
assert.deepEqual(Object.keys(ENEMY_SPINE_ALIAS).sort(), [...SLUGS].sort(), 'exactly the slugs are aliased by hand');
for (const id of SLUGS) assert.equal(ENEMY_SPINE_ALIAS[id], 'enemy_1007_slime', `${id}: the plain 源石虫`);
assert.ok(p.notes.some((n) => n.includes('enemy_1305_mhslim') && /aliased/.test(n)),
'the plan says so in its notes, so a run without the model is explainable');
});
test('data/assets.json: every other spineAliasOf stays inside its own variant family', () => {
const borrowed = [];
for (const [id, e] of Object.entries(MANIFEST.enemies)) {
if (!e.spineAliasOf) continue;
const base = /^(enemy_\d+_[a-z0-9]+?)_\d+$/i.exec(id); // enemy_2001_duckmi_2 → enemy_2001_duckmi
if (!SLUGS.includes(id) && (!base || base[1] !== e.spineAliasOf)) borrowed.push(`${id} → ${e.spineAliasOf}`);
}
assert.deepEqual(borrowed, [],
'the six _2 aliases draw the base enemy their own prefab uses; only the two slugs borrow a different enemy');
});
test('every asset source stays a GitHub dump (no third-party mirror for two models)', () => {
for (const [name, url] of Object.entries(RAW)) {
assert.match(url, /^https:\/\/raw\.githubusercontent\.com\//, `RAW.${name} is a raw.githubusercontent.com URL`);
}
assert.ok(!/prts|torappu/i.test(JSON.stringify(RAW)), 'no community-wiki host among the download sources');
});
});
describe('D3 client: the official model only when data/local-assets.json lists it', () => {
const webOf = (id) => MANIFEST.enemies[id].spine; // the plain 源石虫's model (spineAliasOf enemy_1007_slime)
+60 -60
View File
@@ -42,7 +42,7 @@
],
"enemies": {"enemy_1019_jshoot":[5,5,0,513,10368],"enemy_1043_zomsbr":[18,18,0,351,54677],"enemy_1299_ymkilr":[1,1,0,2251,9792]},
"hooks": [1,35,1535,152,152,200,200,1064,24,24,3,1,0,6482,0,19,24,0,1,24,0,0,0],
"events": {"atk":152,"deploy":8,"die":24,"dmg":200,"fx":4,"skill":4,"spawn":35,"status":1},
"events": {"atk":152,"deploy":8,"die":24,"dmg":200,"fx":4,"skill":6,"spawn":35,"status":1},
"fx": {"buff":1,"coin":3},
"snaps": "6a513536 c150215b 81084fd8 9870355a f707b691 df63fdcd"
},
@@ -236,17 +236,17 @@
["p1",1,"op","chess_char_1_04_b",1,"uniequip_002_udflow",32931.36,1,2,0,0,0,0,0,0],
["p1",2,"op","chess_char_2_07_b",1,"uniequip_002_ghost",27707.68,1,3,0,0,4635,4635,0,0],
["p1",3,"op","chess_char_3_05_b",2,"uniequip_002_skadi",18673.58,1,11,11,135708,36837,0,45,1],
["p1",4,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,17,0,0,1619,0,0,0],
["p1",4,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,17,0,0,1619,0,0,2],
["p1",5,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",20727.84,1,6,4,186331,0,0,12,2],
["p1",6,"op","chess_char_3_09_b",1,"uniequip_002_haini",17395.56,1,7,0,23243,0,0,20,1],
["p1",7,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,8,4,34187,0,0,35,15],
["p1",8,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,1,0,0,0,0,0,0]
],
"enemies": {"enemy_1229_darmy":[10,10,0,4638,104509],"enemy_1229_darmy_2":[7,7,0,18088,95800],"enemy_9007_acelem":[2,2,0,4609,179159]},
"hooks": [1,29,2221,148,148,256,276,251,20,21,19,19,0,4154,485,13,19,0,1,20,5,3,0],
"events": {"atk":167,"deploy":10,"die":21,"dmg":276,"fx":34,"layer":5,"skill":37,"spawn":27,"status":132},
"hooks": [1,29,2221,148,148,256,276,251,20,21,21,20,0,4154,485,13,19,0,1,20,5,3,0],
"events": {"atk":167,"deploy":10,"die":21,"dmg":276,"fx":34,"layer":5,"skill":41,"spawn":27,"status":132},
"fx": {"beam":2,"blink":1,"burst":3,"devour":4,"dilemma":2,"frostNova":15,"garrison":5,"swordStorm":2},
"snaps": "066fec91 ab36cdb7 ec262f21 c49cc94d 7299d69b c5c80506 eb36b938 9265751a"
"snaps": "066fec91 aec41fca b75071e0 478fe370 a68e442a 1b0b0907 cc527129 08c4150f"
},
"bond-emptyShip-low": {
"about": "协防干员 low: tier 1 layers 1; chess_char_4_01_a chess_char_4_04_a chess_char_2_14_a chess_char_3_10_a chess_char_3_20_a chess_char_3_21_a chess_char_4_02_a chess_char_4_06_a",
@@ -316,7 +316,7 @@
],
"enemies": {"enemy_1045_hammer_2":[2,0,2,7798,28315],"enemy_1061_zomshd":[11,11,0,983,179484]},
"hooks": [1,23,3551,281,281,569,560,7994,12,14,14,13,0,5016,14,31,13,2,1,14,0,0,2],
"events": {"atk":376,"deploy":8,"die":12,"dmg":552,"fx":86,"heal":58,"leak":2,"skill":27,"spawn":23,"status":38},
"events": {"atk":376,"deploy":8,"die":12,"dmg":552,"fx":86,"heal":58,"leak":2,"skill":29,"spawn":23,"status":38},
"fx": {"block":7,"dodge":2,"flame":1,"garrisonGrant":1,"rockslide":6,"shieldBreak":8,"slash":26,"thorns":33,"truesilver":2},
"snaps": "48d2a0bc 7a5c72a3 804d5560 8c6ca634 c9bf651b 26081476 486ac1ff 38cf6fca 1c52bb36 3480567c b20bd7bb 65dfc07e"
},
@@ -343,7 +343,7 @@
["p1",1,"op","chess_char_1_20_a",1,null,2198,1,4,0,0,0,0,0,0],
["p1",2,"op","chess_char_4_11_a",1,null,2144,1,5,0,0,0,0,0,0],
["p1",3,"op","chess_char_4_12_a",2,null,1311.76,1,7,2,11330,687,245,24,1],
["p1",4,"op","chess_char_2_12_a",1,null,668.98,1,34,1,945,1545,0,22,0],
["p1",4,"op","chess_char_2_12_a",1,null,668.98,1,34,1,945,1545,0,22,2],
["p1",5,"op","chess_char_4_09_a",1,null,1909.15,1,10,8,39617,228,0,16,2],
["p1",6,"op","chess_char_4_10_a",1,null,1029.36,1,6,6,16627,259,0,17,1],
["p1",7,"op","chess_char_4_13_a",1,null,0,0,9,0,13012,1587,0,22,5],
@@ -355,10 +355,10 @@
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1158_divman":[15,15,0,2779,57024],"enemy_1160_hvyslr":[2,2,0,1458,23040],"enemy_1161_tidmag":[1,1,0,1346,6912]},
"hooks": [1,32,1636,158,158,248,250,168,21,21,10,9,0,5617,221,3,18,0,1,21,1,0,7],
"events": {"atk":195,"deploy":11,"die":21,"dmg":245,"fx":44,"layer":1,"skill":18,"spawn":31,"status":97},
"hooks": [1,32,1636,158,158,248,250,168,21,21,12,10,0,5617,221,3,18,0,1,21,1,0,7],
"events": {"atk":195,"deploy":11,"die":21,"dmg":245,"fx":44,"layer":1,"skill":22,"spawn":31,"status":97},
"fx": {"dilemma":2,"displace":8,"dodge":7,"explosion":1,"frostNova":5,"garrison":1,"levitate":7,"pull":1,"revive":1,"shield":2,"slippery":1,"splash":2,"tornado":1,"tornadoPulse":5},
"snaps": "8bec34f2 142c5955 2bedbb0a 140b218e 6f2460a4 10452c6f"
"snaps": "8db24db5 69482d02 bf502995 575947d9 47ac9ccf a8929550"
},
"bond-indomShip-high": {
"about": "不屈 high: tier 2 layers 999; chess_char_1_20_b chess_char_3_16_b chess_char_4_23_b chess_char_5_01_b chess_char_2_12_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -372,28 +372,28 @@
"time": 190,
"ticks": 5700,
"reason": "timeout",
"killed": 14,
"killed": 13,
"total": 22,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 299,
"rngDraws": 306,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":14,"total":22,"perfect":false,"leaks":8,"leaksUncounted":0,"coins":0,"dmg":263498,"bossDmg":0,"heal":69427,"deaths":20,"layerGains":{}}},
"players": {"p1":{"killed":13,"total":22,"perfect":false,"leaks":9,"leaksUncounted":0,"coins":0,"dmg":304172,"bossDmg":0,"heal":65354,"deaths":22,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_1_20_b",1,"uniequip_002_liskam",2892,1,50,2,37051,31400,0,95,6],
["p1",2,"op","chess_char_3_16_b",1,"uniequip_002_snakek",2987.31,1,5,0,0,0,0,0,0],
["p1",1,"op","chess_char_1_20_b",1,"uniequip_002_liskam",2866.18,1,50,2,31083,30784,0,80,7],
["p1",2,"op","chess_char_3_16_b",1,"uniequip_002_snakek",3072.32,1,5,0,0,0,0,0,0],
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,7,0,0,0,0,0,0],
["p1",4,"op","chess_char_5_01_b",1,"uniequip_002_excu2",423.76,1,43,8,145703,33989,15901,154,8],
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1904.37,1,42,1,15616,31855,0,196,0],
["p1",6,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,49,3,65128,4333,172,64,4],
["p1",7,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,47,0,0,7103,34403,0,2],
["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,46,0,0,11651,18951,34,4]
["p1",4,"op","chess_char_5_01_b",1,"uniequip_002_excu2",1354.58,1,52,8,196467,35988,16517,200,9],
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1242.67,1,44,1,15616,31855,0,196,4],
["p1",6,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,48,2,61006,4211,0,58,3],
["p1",7,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,49,0,0,7103,29081,0,2],
["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,47,0,0,11651,19756,33,4]
],
"enemies": {"enemy_1166_dusbr_2":[14,12,2,28157,144069],"enemy_1170_dushld_2":[4,2,2,45909,73715],"enemy_9011_acrefr":[4,0,2,46265,45714]},
"hooks": [1,50,5700,833,833,1192,1171,382,34,40,24,26,81,4003,211,60,22,6,1,34,0,0,21],
"events": {"atk":1226,"deploy":28,"die":34,"dmg":1168,"fx":164,"heal":353,"leak":6,"skill":47,"spawn":30,"status":543},
"fx": {"bandage":4,"dodge":21,"extraAttack":42,"garrisonGrant":1,"mote":39,"redistribute":30,"revive":17,"reweave":2,"sandChains":4,"shield":4},
"snaps": "bcca108c 58ad920b bbf7027a 2b945c18 dc91804a 008b26e3 cd75becd c3107a45 7a8b798c 0f3dbb0f e2ba5ade 34847497 7f065901 79837eeb 58f4e601 0b2a0461 1bafcde2 6b8a0480 d949aaa7"
"enemies": {"enemy_1166_dusbr_2":[14,11,3,27453,136496],"enemy_1170_dushld_2":[4,1,3,36563,52797],"enemy_9011_acrefr":[4,1,2,57576,114879]},
"hooks": [1,52,5700,834,834,1143,1124,403,35,43,29,28,102,4003,202,60,22,8,1,35,0,0,19],
"events": {"atk":1176,"deploy":30,"die":35,"dmg":1121,"fx":177,"heal":356,"leak":8,"skill":57,"spawn":30,"status":530},
"fx": {"bandage":4,"dodge":19,"extraAttack":54,"garrisonGrant":1,"mote":40,"redistribute":30,"revive":19,"reweave":2,"sandChains":3,"shield":4,"spGift":1},
"snaps": "a5613700 74b53427 dd5cc97e 0a644be4 18999580 50032d82 eacff749 082c6c93 279f01a1 f348cb6e 5e51d837 27e023a3 3cdea049 dadd297d 9129f525 42525d2f 77ff9862 aa26c61a 0b186db9"
},
"bond-investShip-low": {
"about": "投资人 low: tier 1 layers 1; chess_char_4_17_a chess_char_4_18_a chess_char_5_14_a chess_char_4_19_a chess_char_3_04_a chess_char_4_16_a chess_char_2_19_a chess_char_4_20_a",
@@ -420,17 +420,17 @@
["p1",3,"op","chess_char_5_14_a",1,null,1440.77,1,5,2,9885,1432,1321,28,5],
["p1",4,"op","chess_char_4_19_a",2,null,0,0,8,0,1476,1560,0,15,1],
["p1",5,"op","chess_char_3_04_a",1,null,976.8,1,9,1,6666,3012,0,28,0],
["p1",6,"op","chess_char_4_16_a",2,null,938,1,29,6,21523,1609,55,22,0],
["p1",6,"op","chess_char_4_16_a",2,null,938,1,29,6,21523,1609,55,22,4],
["p1",7,"op","chess_char_2_19_a",1,null,593.08,1,6,4,12320,1153,880,25,2],
["p1",8,"op","chess_char_4_20_a",2,null,1217,1,7,5,12397,155,0,20,1],
["-","~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]
],
"enemies": {"enemy_1158_divman":[15,15,0,1502,44550],"enemy_1160_hvyslr":[2,2,0,4278,18000],"enemy_1161_tidmag":[1,1,0,3141,5400]},
"hooks": [1,29,2220,218,218,320,333,873,22,20,10,11,0,6407,56,13,18,0,1,24,0,1,4],
"events": {"atk":230,"deploy":9,"die":20,"dmg":330,"fx":56,"heal":15,"skill":20,"spawn":28,"status":91},
"hooks": [1,29,2220,218,218,320,333,873,22,20,14,14,0,6407,56,13,18,0,1,24,0,1,4],
"events": {"atk":230,"deploy":9,"die":20,"dmg":330,"fx":56,"heal":15,"skill":32,"spawn":28,"status":91},
"fx": {"aoe":6,"burst":1,"coin":3,"dodge":4,"featherArrow":1,"flame":1,"garrisonGrant":1,"revive":2,"riposte":2,"rockslide":1,"shieldBreak":3,"sp":1,"swordRain":24,"swordStorm":4,"zone":2},
"snaps": "2c541cfb 0ae48a43 925d647c 53a13217 eac29867 1fbdf53e c6fbac92 c060336e"
"snaps": "d6fd923e 40279e79 d9530f36 e7c8fb9d c7652603 66aea5b2 e03bbfae a99a96e2"
},
"bond-investShip-high": {
"about": "投资人 high: tier 1 layers 999; chess_char_5_14_b chess_char_5_05_b chess_char_5_06_b chess_char_3_04_b chess_char_5_07_b chess_char_2_19_b chess_char_5_02_b chess_char_5_03_b",
@@ -466,7 +466,7 @@
],
"enemies": {"enemy_1229_darmy":[10,10,0,418,48384],"enemy_1229_darmy_2":[7,7,0,662,44352],"enemy_1275_dwlock_2":[3,3,0,4287,48384]},
"hooks": [1,31,2291,130,130,170,354,1,20,21,7,5,47,3964,0,9,20,0,1,64,0,1,0],
"events": {"atk":132,"deploy":8,"die":21,"dmg":203,"fx":62,"skill":12,"spawn":31,"status":3},
"events": {"atk":132,"deploy":8,"die":21,"dmg":203,"fx":62,"skill":14,"spawn":31,"status":3},
"fx": {"anchor":2,"aoe":2,"beam":2,"burst":1,"coin":4,"ember":1,"explode":1,"revive":2,"splash":47},
"snaps": "a56e679b 7e159567 a5350d42 7ffa012b 9ceba6f2 a8bde46f 89918fc2 d5e5149a"
},
@@ -496,7 +496,7 @@
["p1",2,"op","chess_char_4_23_a",2,null,2179,1,6,0,0,0,0,0,0],
["p1",3,"op","chess_char_4_22_a",2,null,2022,1,8,5,42886,0,0,18,1],
["p1",4,"op","chess_char_1_19_a",1,null,0,0,9,1,15530,5545,0,35,1],
["p1",5,"op","chess_char_2_12_a",1,null,1434.21,1,26,4,17031,4565,0,46,0],
["p1",5,"op","chess_char_2_12_a",1,null,1434.21,1,26,4,17031,4565,0,46,2],
["p1",6,"op","chess_char_4_21_a",1,null,1271,1,4,0,0,0,5478,29,1],
["p1",7,"op","chess_char_4_24_a",1,null,1726,1,5,3,7778,0,0,13,1],
["p1",8,"op","chess_char_4_25_a",2,null,1108.57,1,11,0,0,3265,4852,0,1],
@@ -505,10 +505,10 @@
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1116_liprr":[11,11,0,3450,69696],"enemy_1116_liprr_2":[4,4,0,394,34560],"enemy_1121_lifbos":[1,1,0,7192,23040]},
"hooks": [1,28,1921,209,209,376,376,95,18,18,6,6,0,6440,13,21,16,0,1,18,2,0,0],
"events": {"atk":246,"deploy":9,"die":18,"dmg":356,"fx":58,"heal":94,"layer":2,"skill":11,"spawn":27,"status":50},
"hooks": [1,28,1921,209,209,376,376,95,18,18,8,7,0,6440,13,21,16,0,1,18,2,0,0],
"events": {"atk":246,"deploy":9,"die":18,"dmg":356,"fx":58,"heal":94,"layer":2,"skill":15,"spawn":27,"status":50},
"fx": {"displace":15,"garrison":2,"healField":1,"liberate":3,"redistribute":14,"reweave":1,"sandChains":1,"shield":2,"slash":18,"truesilver":1},
"snaps": "a6ab2e4e c18a7431 c13b3465 2a640c59 b5b62b0b 8a01b038 d8303854"
"snaps": "c0ba6ff7 bba0d7f8 f42e9c18 f6f0af84 b51edddf 58a55b0c 987b2ee8"
},
"bond-kazimierzShip-high": {
"about": "卡西米尔 high: tier 2 layers 999; chess_char_2_18_b chess_char_3_12_b chess_char_5_08_b chess_char_4_19_b chess_char_1_19_b chess_char_2_12_b chess_char_3_17_b chess_char_5_10_b",
@@ -535,17 +535,17 @@
["p1",3,"op","chess_char_5_08_b",2,"uniequip_002_horn",133.03,1,7,4,75927,4063,0,57,2],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,8,1,21430,0,0,19,2],
["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,9,4,52455,1971,0,26,1],
["p1",6,"op","chess_char_2_12_b",1,"uniequip_002_gravel",0,0,35,9,60167,4646,0,50,0],
["p1",6,"op","chess_char_2_12_b",1,"uniequip_002_gravel",0,0,35,9,60167,4646,0,50,2],
["p1",7,"op","chess_char_3_17_b",1,"uniequip_002_shotst",3441.6,1,4,7,87837,0,0,31,3],
["p1",8,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,6,0,2640,0,1421,10,1],
["-","~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]
],
"enemies": {"enemy_10124_uashld_2":[2,2,0,4508,124416],"enemy_1043_zomsbr":[23,23,0,20,145812],"enemy_1061_zomshd":[5,5,0,4181,132243]},
"hooks": [1,41,2701,239,239,406,518,2221,33,33,11,12,0,5249,874,19,30,0,1,44,0,0,8],
"events": {"atk":239,"deploy":9,"die":33,"dmg":397,"fx":65,"heal":22,"skill":21,"spawn":40,"status":128},
"hooks": [1,41,2701,239,239,406,518,2221,33,33,13,12,0,5249,874,19,30,0,1,44,0,0,8],
"events": {"atk":239,"deploy":9,"die":33,"dmg":397,"fx":65,"heal":22,"skill":25,"spawn":40,"status":128},
"fx": {"aoe":28,"displace":17,"dodge":8,"flame":2,"overload":2,"riposte":3,"shield":2,"volley":3},
"snaps": "583d54d4 420cae93 0eae4034 b3dab284 876fdc6c 80243c68 e7d53549 b12a372a 07a56c65"
"snaps": "1f83614e bead82e1 cf903f9e dcc2392a 63ec0242 18d85986 45dac5d7 c55ace88 05326cdb"
},
"bond-kjeragShip-low": {
"about": "谢拉格 low: tier 1 layers 1; chess_char_1_02_a chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_2_05_a chess_char_3_11_a chess_char_4_26_a chess_char_3_01_a",
@@ -586,7 +586,7 @@
],
"enemies": {"enemy_1000_gopro_2":[19,19,0,0,29070],"enemy_1325_cbgpro":[5,5,0,0,17100],"enemy_1325_cbgpro_2":[3,3,0,0,12420]},
"hooks": [1,43,1318,64,64,120,120,0,27,31,3,3,0,5234,1,0,27,0,1,27,1,0,0],
"events": {"atk":64,"deploy":13,"die":31,"dmg":120,"fx":37,"layer":1,"skill":6,"spawn":43,"status":2},
"events": {"atk":64,"deploy":13,"die":31,"dmg":120,"fx":37,"layer":1,"skill":8,"spawn":43,"status":2},
"fx": {"buff":1,"coin":8,"explode":4,"garrison":1,"garrisonGrant":1,"spGift":13,"strike":2,"summon":5,"volley":2},
"snaps": "75591eda 13ef5faa 3682393e a23e5fa6 5d4daa5d"
},
@@ -970,7 +970,7 @@
["p1",2,"op","chess_char_4_18_a",1,null,4587.2,1,5,1,3522,0,0,5,1],
["p1",3,"op","chess_char_2_09_a",1,null,0,0,13,6,51195,2479,400,29,1],
["p1",4,"op","chess_char_4_19_a",2,null,0,0,9,0,6131,1560,0,21,1],
["p1",5,"op","chess_char_1_18_a",1,null,0,0,10,0,20642,4230,1700,34,0],
["p1",5,"op","chess_char_1_18_a",1,null,0,0,10,0,20642,4230,1700,34,1],
["p1",6,"op","chess_char_4_13_a",1,null,1587,1,4,2,10004,0,0,17,6],
["p1",7,"op","chess_char_4_14_a",1,null,1276,1,6,4,15116,0,0,12,3],
["p1",8,"op","chess_char_4_20_a",2,null,1217,1,8,4,19533,0,0,17,0],
@@ -978,10 +978,10 @@
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
],
"enemies": {"enemy_1116_liprr":[11,11,0,27,69696],"enemy_1116_liprr_2":[4,4,0,324,34560],"enemy_1118_lidbox_2":[2,2,0,6654,21888]},
"hooks": [1,28,1632,185,185,252,250,65,20,21,12,13,0,5675,255,14,17,0,1,20,4,0,3],
"events": {"atk":239,"deploy":9,"die":21,"dmg":225,"fx":30,"heal":38,"layer":4,"skill":24,"spawn":27,"status":66},
"hooks": [1,28,1632,185,185,252,250,65,20,21,13,13,0,5675,255,14,17,0,1,20,4,0,3],
"events": {"atk":239,"deploy":9,"die":21,"dmg":225,"fx":30,"heal":38,"layer":4,"skill":28,"spawn":27,"status":64},
"fx": {"aoe":1,"blink":1,"dodge":3,"explosion":3,"flame":1,"frostNova":6,"garrison":4,"garrisonGrant":1,"liberate":5,"riposte":3,"rockslide":1,"shieldBreak":1},
"snaps": "cc3198c7 cbdff753 5e2c14a8 aea9b708 b55cd946 e9f57efc"
"snaps": "d63c71d9 d887e4cb 55b00fa0 66fcfa30 9df5bcce 63545fa4"
},
"bond-raidShip-high": {
"about": "突袭 high: tier 1 layers 999; chess_char_4_17_b chess_char_4_18_b chess_char_2_09_b chess_char_4_19_b chess_char_1_18_b chess_char_4_14_b chess_char_4_20_b chess_char_4_21_b",
@@ -1009,7 +1009,7 @@
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,5,0,0,0,0,0,0],
["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",19662.2,1,14,17,316017,6932,2620,42,2],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,10,0,0,0,0,0,0],
["p1",5,"op","chess_char_1_18_b",1,"uniequip_002_utage",18074.87,1,31,7,166645,7700,1820,26,0],
["p1",5,"op","chess_char_1_18_b",1,"uniequip_002_utage",18074.87,1,31,7,166645,7700,1820,26,3],
["p1",6,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,4392,0,0,1,1],
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,0,21807,0,0,16,0],
["p1",8,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,9,0,0,0,0,0,0],
@@ -1018,10 +1018,10 @@
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1010_demon":[7,7,0,3616,130637],"enemy_1422_lrsldr_2":[14,14,0,259,191601],"enemy_1425_lrcmra_2":[3,3,0,5726,186624]},
"hooks": [1,38,1911,99,99,109,112,68,24,27,3,4,0,1925,0,14,24,0,1,24,1,0,0],
"events": {"atk":107,"deploy":11,"die":27,"dmg":111,"fx":9,"heal":62,"layer":1,"skill":5,"spawn":35,"status":14},
"hooks": [1,38,1911,99,99,109,112,68,24,27,6,5,0,1925,0,14,24,0,1,24,1,0,0],
"events": {"atk":107,"deploy":11,"die":27,"dmg":111,"fx":9,"heal":62,"layer":1,"skill":13,"spawn":35,"status":8},
"fx": {"aoe":3,"blink":3,"explosion":1,"garrison":1,"garrisonGrant":1},
"snaps": "da6cc8a1 81726914 ba9ba211 361a7568 de4dcfef fafe142a 5836ddb6"
"snaps": "c2c35b01 bcc47598 01104ea6 bc1ad03d c117d4a0 c04f551c 785ecf20"
},
"bond-sargonShip-low": {
"about": "萨尔贡 low: tier 1 layers 1; chess_char_2_08_a chess_char_1_12_a chess_char_4_22_a chess_char_2_06_a chess_char_4_21_a chess_char_4_25_a chess_char_4_26_a chess_char_3_01_a",
@@ -1126,7 +1126,7 @@
],
"enemies": {"enemy_1010_demon":[3,3,0,7576,25920],"enemy_1010_demon_2":[1,1,0,5601,17280],"enemy_1071_dftman":[15,15,0,880,48384]},
"hooks": [1,27,2214,233,233,292,318,0,24,24,4,3,0,4409,27,28,19,0,1,24,0,0,0],
"events": {"atk":233,"deploy":8,"die":24,"dmg":318,"fx":50,"skill":7,"spawn":27,"status":53},
"events": {"atk":233,"deploy":8,"die":24,"dmg":318,"fx":50,"skill":11,"spawn":27,"status":53},
"fx": {"aoe":8,"coin":3,"crit":7,"dp":1,"strike":31},
"snaps": "a194c978 d501eda8 b671f9fb e2590e39 2ab25c9c 6714c2bf ff63f649 9794310b"
},
@@ -1164,7 +1164,7 @@
],
"enemies": {"enemy_1158_divman":[20,20,0,7809,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1341,59720]},
"hooks": [1,39,2292,311,311,481,484,2,30,30,6,5,0,3813,40,4,26,0,1,35,3,0,0],
"events": {"atk":312,"deploy":11,"die":30,"dmg":484,"fx":26,"heal":2,"layer":3,"skill":11,"spawn":37,"status":73},
"events": {"atk":312,"deploy":11,"die":30,"dmg":484,"fx":26,"heal":2,"layer":3,"skill":13,"spawn":37,"status":73},
"fx": {"aoe":3,"bondProc":8,"crit":5,"dp":4,"garrison":3,"wolfShadowLost":3},
"snaps": "d1ead0d3 cda7c00c 8843b429 bd54e255 1de8d432 e6f225a5 7cba25d9 d80a750d"
},
@@ -1439,7 +1439,7 @@
["p1",2,"op","chess_char_4_17_a",1,null,2803.99,1,5,1,2550,45,0,6,0],
["p1",3,"op","chess_char_4_18_a",1,null,4587.2,1,8,2,7589,15,15,8,2],
["p1",4,"op","chess_char_4_12_a",2,null,0,0,10,4,17392,2076,322,16,1],
["p1",5,"op","chess_char_4_16_a",2,null,1246,1,33,2,11205,1294,48,22,0],
["p1",5,"op","chess_char_4_16_a",2,null,1246,1,33,2,11205,1294,48,22,3],
["p1",6,"op","chess_char_1_01_b",1,"uniequip_002_inside",1421,1,6,7,20882,0,0,33,1],
["p1",7,"op","chess_char_4_13_a",1,null,1587,1,7,3,7646,0,0,19,5],
["p1",8,"op","chess_char_4_14_a",1,null,1276,1,9,2,24907,0,0,15,3],
@@ -1448,10 +1448,10 @@
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1249_lysdb_2":[5,5,0,1434,34560],"enemy_1251_lysyta":[15,15,0,108,47520],"enemy_1328_cbjedi":[1,1,0,1887,11520]},
"hooks": [1,33,1712,141,141,224,218,227,24,23,12,13,14,6339,521,16,21,0,1,24,0,0,0],
"events": {"atk":141,"deploy":9,"die":23,"dmg":214,"fx":67,"heal":2,"skill":24,"spawn":32,"status":71},
"hooks": [1,33,1712,141,141,224,218,227,24,23,15,15,14,6339,521,16,21,0,1,24,0,0,0],
"events": {"atk":141,"deploy":9,"die":23,"dmg":214,"fx":67,"heal":2,"skill":32,"spawn":32,"status":71},
"fx": {"block":1,"displace":21,"explosion":3,"frostNova":5,"pull":1,"rockslide":2,"shieldBreak":9,"sp":1,"swordRain":12,"swordStorm":3,"thorns":3,"tornado":1,"tornadoPulse":5},
"snaps": "8bc49724 8e12b52c 7d0bf1a9 f3414303 d9b907c0 f2540f9f"
"snaps": "d4d5e757 c6e256bb 6ea24a32 119afe24 54871beb d1707192"
},
"bond-suntShip-high": {
"about": "绝技 high: tier 2 layers 999; chess_char_1_02_b chess_char_1_04_b chess_char_4_19_a chess_char_1_01_b chess_char_1_03_b chess_char_1_06_b chess_char_4_20_a chess_char_4_21_a",
@@ -1593,7 +1593,7 @@
],
"enemies": {"enemy_1116_liprr":[11,11,0,389,69696],"enemy_1116_liprr_2":[4,4,0,319,34560],"enemy_1118_lidbox_2":[2,0,2,13211,18290]},
"hooks": [1,27,1907,236,236,388,417,42,19,21,6,5,0,6837,0,20,17,2,1,20,0,0,0],
"events": {"atk":236,"deploy":8,"die":19,"dmg":417,"fx":57,"heal":32,"leak":2,"skill":10,"spawn":27,"status":30},
"events": {"atk":236,"deploy":8,"die":19,"dmg":417,"fx":57,"heal":32,"leak":2,"skill":12,"spawn":27,"status":30},
"fx": {"aoe":7,"coin":3,"counter":3,"liberate":5,"overload":1,"revive":1,"strike":36,"taunt":1},
"snaps": "0e312a6e 45b64065 23e7a18c 93f3311a 14f12d67 80b532e0 88f0c499"
},
@@ -1696,8 +1696,8 @@
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,3,0,0,0,0,0,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,4,0,0,0,0,0,0],
["p1",3,"op","chess_char_3_03_b",1,"uniequip_002_swire",1898,1,5,6,24070,0,0,23,1],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",157.8,1,8,4,31620,1960,0,27,3],
["p1",5,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,31,4,46126,3321,83,40,0],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",157.8,1,8,4,31547,1960,0,27,3],
["p1",5,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,31,4,46199,3321,83,40,4],
["p1",6,"op","chess_char_2_02_b",1,"uniequip_002_silent",1614,1,6,0,0,1084,3098,8,2],
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,4,35502,1704,0,22,1],
["p1",8,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,10,0,0,2478,2168,14,1],
@@ -1706,10 +1706,10 @@
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
],
"enemies": {"enemy_1234_dsubrl_2":[2,2,0,9847,32256],"enemy_1433_dsbasi_2":[16,16,0,700,105062]},
"hooks": [1,32,2169,163,163,249,300,41,22,23,8,9,0,2963,17,18,18,0,1,22,0,0,12],
"events": {"atk":167,"deploy":12,"die":23,"dmg":300,"fx":94,"heal":24,"skill":15,"spawn":29,"status":31},
"hooks": [1,32,2169,163,163,249,300,41,22,23,12,11,0,2963,17,18,18,0,1,22,0,0,12],
"events": {"atk":167,"deploy":12,"die":23,"dmg":300,"fx":94,"heal":24,"skill":25,"spawn":29,"status":31},
"fx": {"dodge":12,"featherArrow":1,"flame":3,"garrisonGrant":1,"healField":1,"pulse":43,"riposte":10,"summon":2,"swordRain":17,"swordStorm":4},
"snaps": "d4da3ac7 da4fa3ba f837253c 7102ead4 bb579ed6 fb16fd1f a232d050 343c58c6"
"snaps": "849e61b3 b054e595 7736283f c3ff5997 f8df75cd d3a1062a ce887350 42803555"
},
"bond-yanShip-low": {
"about": "炎 low: tier 1 layers 1; chess_char_3_16_a chess_char_3_03_a chess_char_3_18_a chess_char_1_03_a chess_char_2_04_a chess_char_3_14_a chess_char_3_17_a chess_char_3_19_a token_10028_vigil_wolf",
@@ -1792,7 +1792,7 @@
],
"enemies": {"enemy_1019_jshoot_2":[7,7,0,1544,47029],"enemy_1381_winman":[9,9,0,0,129890],"enemy_1389_winbab_2":[5,5,0,5384,87091]},
"hooks": [1,40,2378,128,128,168,212,1108,22,26,11,11,6,5334,1,8,21,0,1,23,0,0,0],
"events": {"atk":128,"deploy":16,"die":26,"dmg":209,"fx":31,"heal":8,"skill":22,"spawn":40,"status":3},
"events": {"atk":128,"deploy":16,"die":26,"dmg":209,"fx":31,"heal":8,"skill":26,"spawn":40,"status":3},
"fx": {"coin":8,"downed":1,"explode":4,"firewall":1,"revive":1,"splash":6,"summon":5,"yanyouFlame":3,"yanyouSummon":2},
"snaps": "9de1a377 006ae259 36d64b47 07539063 05e6c9d6 a572b9f9 d780cb71 6f89109e"
}
+161 -160
View File
@@ -141,42 +141,42 @@
"killed": 7,
"total": 8,
"unspawned": 0,
"bossHpLeft": 2351155,
"rngDraws": 452,
"bossHpLeft": 2350060,
"rngDraws": 418,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":598271,"bossDmg":306867,"heal":14230,"deaths":2,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":776432,"bossDmg":337583,"heal":4361,"deaths":1,"layerGains":{}}
"p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":586039,"bossDmg":301935,"heal":15824,"deaths":2,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":798436,"bossDmg":343471,"heal":3544,"deaths":2,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,2,202006,1042,0,75,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,13291,12043,413,57,1],
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,31499,800,173,55,19],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,13838,0,0,82,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,85052,0,467,46,8],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,2,202006,1042,20,75,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,13291,13409,413,57,1],
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,30220,1028,162,56,18],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,10185,0,0,83,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,85052,0,540,46,8],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,60506,0,0,33,3],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,43,1,13302,2989,220,11,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,40,0,128014,1611,0,47,17],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,12957,17,2],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,43,1,13270,2989,220,10,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,40,1,120745,1611,0,41,16],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,14468,18,2],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,50762,0,0,32,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,47,0,0,3787,211,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,12,0,30214,0,0,40,3],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,49,1,285870,0,0,307,8],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2292.97,1,14,0,2151,1249,1159,13,6],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,48,0,71148,4006,678,131,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",679.32,1,16,0,80697,2860,761,68,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,1389,0,0,14,1],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,61439,0,0,49,2],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",454.22,1,18,0,100127,3088,1553,154,43],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,20,3,143398,0,0,75,17],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",1729.97,1,47,0,0,4971,481,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,12,0,31398,0,0,44,3],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,49,1,285207,0,0,307,8],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,14,0,2151,2782,399,13,6],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,48,0,72409,4006,678,141,8],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",316.22,1,16,0,86815,3101,640,76,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,1319,0,0,13,1],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,72378,0,0,61,2],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,18,0,103544,1346,1346,153,45],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,20,2,143215,0,0,73,17],
["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]
],
"enemies": {"enemy_1005_yokai_2":[6,6,0,2913,62190],"enemy_1430_lrrook":[2,1,1,0,668216],"enemy_9017_achunt":[2,0,0,30560,648845]},
"hooks": [1,47,6000,1333,1338,2540,2570,3469,10,23,144,146,20,39193,1512,0,10,1,1,11,0,10,3],
"events": {"atk":1386,"deploy":37,"die":22,"dmg":2565,"fx":493,"heal":37,"leak":1,"skill":283,"spawn":32,"status":510},
"fx": {"aoe":42,"appear":2,"blink":12,"burst":10,"catShield":1,"charge":6,"counter":20,"crit":21,"dilemma":5,"disappear":2,"dodge":3,"explosion":17,"frostNova":43,"iceSpike":11,"overclock":1,"pull":3,"ripple":14,"sentry":5,"sermon":1,"shadowWeave":10,"shield":5,"slippery":2,"soul":3,"sp":13,"stealth":8,"swordRain":71,"swordStorm":8,"tornado":3,"tornadoPulse":15,"weightless":3,"zone":133},
"snaps": "b55c9e85 fe86ba61 d5ad74eb 3631cc13 dae4c9bb c23490c2 6c4949f1 0787344a 879646ab bb0c0a44 3ac1478f 5188dc51 e50ed9ce 423d53b9 c0c1c143 cb676814 d3d18054 2b6e5391 4358b6bb b2c6ef0d"
"enemies": {"enemy_1005_yokai_2":[6,6,0,2913,62190],"enemy_1430_lrrook":[2,1,1,0,677032],"enemy_9017_achunt":[2,0,0,33371,649940]},
"hooks": [1,47,6000,1359,1364,2576,2608,2946,11,24,152,147,20,38829,1514,0,10,1,1,12,0,10,1],
"events": {"atk":1412,"deploy":37,"die":23,"dmg":2604,"fx":485,"heal":39,"leak":1,"skill":299,"spawn":32,"status":512},
"fx": {"aoe":40,"appear":2,"blink":12,"burst":10,"catShield":1,"charge":6,"counter":20,"crit":15,"dilemma":5,"disappear":2,"dodge":1,"explosion":17,"frostNova":45,"iceSpike":10,"overclock":1,"pull":3,"ripple":14,"sentry":5,"sermon":1,"shadowWeave":10,"shield":5,"slippery":2,"soul":3,"sp":14,"stealth":8,"swordRain":71,"swordStorm":8,"tornado":3,"tornadoPulse":15,"weightless":3,"zone":133},
"snaps": "bded3f8f 24cbefe9 671c8869 999a4ff1 8528a501 ba0ffb54 819c81f5 c480501c 5f51d940 f5f9be37 3b6dcc0d 59143ccc e8b26cd0 42745026 c22fe961 29db481b 69c7d37e caa1e5a5 11718d27 2f9da877"
},
"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",
@@ -211,7 +211,7 @@
],
"enemies": {"enemy_1249_lysdb_2":[5,5,0,7713,107495],"enemy_1430_lrrook":[2,0,2,0,69826],"enemy_9017_achunt":[1,0,0,20369,130032]},
"hooks": [1,21,6000,384,384,992,1392,437,8,10,26,25,0,11439,66,5,8,2,1,12,0,2,2],
"events": {"atk":407,"deploy":13,"die":8,"dmg":1388,"fx":261,"heal":427,"leak":2,"skill":50,"spawn":18,"status":408},
"events": {"atk":407,"deploy":13,"die":8,"dmg":1388,"fx":261,"heal":427,"leak":2,"skill":54,"spawn":18,"status":408},
"fx": {"bandage":3,"block":4,"burst":2,"charge":7,"dodge":2,"featherArrow":1,"flame":5,"garrisonGrant":1,"healField":1,"jungleSoul":3,"mote":102,"redistribute":45,"reweave":3,"riposte":2,"rockslide":2,"sandChains":3,"sandChainsCharged":2,"shieldBreak":9,"slash":50,"spGift":5,"thorns":6,"truesilver":3},
"snaps": "0f880931 8c81facc 290a0f9b 2bb4b1a9 8e556ed9 f2194a8a d52e0a62 9b379622 a978b6ed e31e004a 73d5361b 01a346ea ff508697 13a33137 5e17b2f9 f47e3e5d 9d93e6fc ebf591d1 49f516d3 62bd96ec"
},
@@ -349,54 +349,54 @@
"killed": 11,
"total": 14,
"unspawned": 0,
"bossHpLeft": 2930486,
"rngDraws": 419,
"bossHpLeft": 2963186,
"rngDraws": 379,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":11,"total":14,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1322967,"bossDmg":470678,"heal":61418,"deaths":14,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":976175,"bossDmg":798836,"heal":4049,"deaths":15,"layerGains":{}}
"p1": {"killed":11,"total":14,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1301416,"bossDmg":462784,"heal":55328,"deaths":14,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":958751,"bossDmg":774030,"heal":4401,"deaths":17,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,50,2,181771,8203,389,24,2],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3059.14,1,56,0,19478,8542,996,13,2],
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,53,1,32068,8097,1016,8,6],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,52,0,28064,18299,2238,32,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",850.6,1,58,3,452956,3394,18917,106,18],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",967.4,1,60,0,110091,10507,495,39,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1250.45,1,19,2,154583,4283,257,193,5],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1202.87,1,61,1,186693,2473,454,43,18],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,7,0,0,4854,36310,64,4],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1316.45,1,15,0,157261,4283,348,71,3],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,50,2,181433,8233,419,23,1],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",1480.42,1,56,0,18660,9555,996,14,2],
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,53,1,32068,7498,1041,8,6],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,52,0,27339,18659,2152,32,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",760.49,1,58,1,440543,3484,13958,106,18],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",748.73,1,60,1,109385,11025,551,39,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1147.47,1,19,1,152890,4386,257,193,5],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1093.46,1,61,1,184949,2582,454,38,16],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1608.42,1,7,0,0,4983,35154,63,4],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1213.47,1,15,1,154147,4386,348,71,3],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,48,0,0,6794,52,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,47,0,26743,5448,984,10,1],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",1553.11,1,26,0,402652,9073,457,295,8],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,26,0,390348,6532,239,289,7],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,59,0,17337,7149,0,18,6],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,57,0,136318,18427,385,126,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,54,1,131361,6930,369,49,4],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,57,1,146039,18319,1075,120,6],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,54,1,139224,6925,369,53,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,55,0,0,3470,348,0,0],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1212.24,1,12,0,86149,4283,467,68,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1849.61,1,20,1,129378,4141,554,186,49],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1247.45,1,62,0,46238,2437,433,12,6],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1109.26,1,12,0,85141,4386,467,68,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,20,1,108475,4244,435,153,39],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1144.47,1,62,0,45445,2540,433,15,6],
["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]
],
"enemies": {"enemy_1010_demon":[4,4,0,9763,200612],"enemy_1438_dspred":[10,7,3,30779,829016],"enemy_1521_dslily":[1,0,0,92746,1269514]},
"hooks": [1,60,6000,1267,1368,3071,3207,6785,40,46,143,145,13,37523,1861,23,15,3,1,41,0,41,3],
"events": {"atk":1489,"deploy":45,"die":43,"dmg":3198,"fx":851,"heal":317,"leak":3,"skill":283,"spawn":37,"status":944},
"enemies": {"enemy_1010_demon":[4,4,0,9763,200612],"enemy_1438_dspred":[10,7,3,30762,822741],"enemy_1521_dslily":[1,0,0,92273,1236814]},
"hooks": [1,60,6000,1224,1327,3023,3152,6681,43,48,136,134,14,36235,1594,23,15,3,1,44,0,37,5],
"events": {"atk":1450,"deploy":45,"die":45,"dmg":3143,"fx":842,"heal":271,"leak":3,"skill":270,"spawn":37,"status":941},
"fx": {
"aoe": 28,
"aoe": 25,
"appear": 10,
"blink": 3,
"burst": 41,
"burst": 37,
"charge": 6,
"counter": 13,
"crit": 3,
"dilemma": 4,
"counter": 14,
"crit": 1,
"dilemma": 5,
"disappear": 10,
"displace": 29,
"dodge": 3,
"dodge": 5,
"explosion": 6,
"frostNova": 49,
"frostNova": 39,
"grow": 1,
"iceSpike": 148,
"levitate": 2,
@@ -404,17 +404,17 @@
"pull": 1,
"ripple": 27,
"rockfall": 18,
"sentry": 1,
"sentry": 2,
"sermon": 2,
"shadowWeave": 1,
"shield": 5,
"slippery": 4,
"soul": 7,
"sp": 13,
"sp": 14,
"splash": 2,
"stealth": 8,
"swordRain": 61,
"swordStorm": 5,
"stealth": 7,
"swordRain": 65,
"swordStorm": 6,
"tentacle": 6,
"tide": 5,
"tornado": 1,
@@ -422,7 +422,7 @@
"weightless": 4,
"zone": 318
},
"snaps": "8a10eb3c 242ebdb2 97a7d1ae db3553f9 c3c1a514 ca2de0f0 581702a0 9fa6219a 65e2ac32 897d30d4 f891594b 175ab973 2c511fb5 19cc4b3a 36b027b0 f040e8a6 7cf2da6e ef11804d 9c97e186 c1016a03"
"snaps": "9ebfb59f ebaa506f f9e0adbc 78861a0d fc253243 261e35f6 da034a99 20c77428 1a1e5a85 8e3269d0 cf47638c e41592e8 bb36e8db 5cac1f53 42ce30eb ba5c97a1 c2fb0718 ce0af9ba f02c4273 3f5c1461"
},
"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",
@@ -459,7 +459,7 @@
],
"enemies": {"enemy_1010_demon_2":[2,2,0,7219,107495],"enemy_1438_dspred":[5,2,3,28087,137265],"enemy_1521_dslily":[1,0,0,68045,411797]},
"hooks": [1,23,6000,567,567,959,1067,335,8,11,25,24,0,13355,129,10,8,3,1,8,0,30,0],
"events": {"atk":762,"deploy":13,"die":8,"dmg":1049,"fx":335,"heal":329,"leak":3,"skill":49,"spawn":20,"status":513},
"events": {"atk":762,"deploy":13,"die":8,"dmg":1049,"fx":335,"heal":329,"leak":3,"skill":51,"spawn":20,"status":513},
"fx": {"bandage":1,"block":18,"burst":30,"camouflage":1,"featherArrow":5,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":68,"redistribute":45,"reweave":3,"rockfall":18,"rockslide":4,"sandChains":6,"shieldBreak":18,"slash":46,"spGift":3,"tentacle":6,"thorns":50,"tide":5,"truesilver":3},
"snaps": "bdb41e6b d33270c9 b699515f 4c778ec3 7b987465 1dc86ced aa1b5647 d974b80c 1d71a5f3 0398ba1c cb1b4c96 23b4948c 4e179090 70afc8aa 855ad8e7 80f3d475 405e2c17 3b9e93ac 2b80137e cf30df9d"
},
@@ -622,15 +622,15 @@
"killed": 4,
"total": 6,
"unspawned": 0,
"bossHpLeft": 2865339,
"bossHpLeft": 2830586,
"rngDraws": 115,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":852009,"bossDmg":418475,"heal":4847,"deaths":6,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":773698,"bossDmg":696186,"heal":429,"deaths":13,"layerGains":{}}
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":851952,"bossDmg":418475,"heal":4847,"deaths":6,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":809842,"bossDmg":730939,"heal":429,"deaths":13,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,1,124910,0,0,36,2],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,1,124853,0,0,36,2],
["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,0,33331,0,0,0,11],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,0,28361,3948,2626,18,1],
@@ -642,12 +642,12 @@
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,46318,0,0,37,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,41,0,0,12099,91,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,2,0,0,0,0,0,0],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,26,0,134205,5043,208,208,6],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,26,0,135017,5043,208,208,6],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,10,0,0,0,0,0,0],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,52,0,48871,8558,131,148,0],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,52,0,54215,8558,131,149,5],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,48,0,161587,5555,0,65,4],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,0,0,0,0,0],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,250219,0,0,92,4],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,280207,0,0,107,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,49,0,115884,3978,0,105,27],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,50,0,62932,4878,0,22,4],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null],
@@ -657,14 +657,14 @@
"enemy_10028_vtswd": [2,2,0,0,0],
"enemy_10029_vtshld": [6,6,0,0,0],
"enemy_10030_vtwand": [1,1,0,0,0],
"enemy_10085_hllevi_2": [2,0,2,0,310434],
"enemy_10085_hllevi_2": [2,0,2,0,311768],
"enemy_1010_demon": [4,4,0,16047,200612],
"enemy_9032_aclionk": [1,0,0,37713,1114661]
"enemy_9032_aclionk": [1,0,0,37713,1149414]
},
"hooks": [1,51,6000,908,916,1487,1562,667,32,36,92,95,0,30548,1153,4,16,2,1,26,0,7,1],
"events": {"atk":1019,"deploy":35,"die":34,"dmg":1512,"fx":447,"heal":11,"leak":2,"skill":182,"spawn":38,"status":688},
"fx": {"aoe":33,"appear":4,"blink":2,"burst":7,"crit":1,"dilemma":4,"disappear":4,"displace":8,"dodge":1,"equip":10,"explosion":4,"frostNova":27,"garrisonGrant":1,"iceSpike":28,"overclock":1,"revive":2,"ripple":18,"sentry":2,"shadowWeave":1,"shield":1,"slippery":4,"soul":2,"sp":10,"stealth":6,"sword":1,"swordRain":27,"swordStorm":5,"telegraph":15,"vest":6,"weightless":3,"zone":209},
"snaps": "4d7c2a7f 686758e9 df0ed6b8 7a4e1f91 125c191e 017e01bd 6857aa52 b24dacfd d63b2f43 e0c1d922 32697925 90296fa2 d122aaa7 110fa0da ab96f729 b71d17dc 22cf704c e686354f 0ffb1a0f e60f3754"
"hooks": [1,51,6000,924,932,1505,1589,667,32,36,97,95,0,30558,1153,4,16,2,1,26,0,8,1],
"events": {"atk":1036,"deploy":35,"die":34,"dmg":1545,"fx":448,"heal":11,"leak":2,"skill":192,"spawn":38,"status":691},
"fx": {"aoe":33,"appear":4,"blink":2,"burst":8,"crit":1,"dilemma":4,"disappear":4,"displace":8,"dodge":1,"equip":10,"explosion":4,"frostNova":27,"garrisonGrant":1,"iceSpike":28,"overclock":1,"revive":2,"ripple":18,"sentry":2,"shadowWeave":1,"shield":1,"slippery":4,"soul":2,"sp":10,"stealth":6,"sword":1,"swordRain":27,"swordStorm":5,"telegraph":15,"vest":6,"weightless":3,"zone":209},
"snaps": "ae4c2d42 930756f4 dfc027de 067097c8 063c8181 4f0dee8a db069676 6457f437 3f8f2a7b b398399a 5c778a50 56497384 c05a22f0 58b7c8c9 4ed8f59d 8c6d8923 e673e1f7 dd8b1c6e c6fdbae0 00aaa057"
},
"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",
@@ -713,7 +713,7 @@
"enemy_9032_aclionk": [1,0,0,40419,102806]
},
"hooks": [1,47,6000,190,190,281,479,239,32,34,21,21,0,7024,68,2,20,2,1,23,0,0,2],
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"fx":90,"heal":34,"leak":2,"skill":42,"spawn":36,"status":187},
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"fx":90,"heal":34,"leak":2,"skill":44,"spawn":36,"status":187},
"fx": {"dodge":2,"equip":12,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":9,"riposte":2,"rockslide":1,"sandChains":4,"slash":24,"summon":2,"sword":1,"telegraph":11,"truesilver":4,"vest":5},
"snaps": "8056ac8d bae7d7dc 775827b4 0d832bb0 b68eb0ff 07afed85 063dbd3e 1ffe3d7a b60db00b cc64833f 2776a8b9 a8e2de77 5a30391e 46573a52 04e74ed7 41e6b272 5ea737fb bb532ee9 6cc70b97 f363c643"
},
@@ -834,94 +834,95 @@
"time": 200,
"ticks": 6000,
"reason": "forced",
"killed": 14,
"killed": 12,
"total": 18,
"unspawned": 0,
"bossHpLeft": 6062859,
"rngDraws": 557,
"bossHpLeft": 6077350,
"rngDraws": 712,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":14,"total":18,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1790908,"bossDmg":807770,"heal":51268,"deaths":20,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":576529,"bossDmg":329372,"heal":7587,"deaths":27,"layerGains":{}}
"p1": {"killed":12,"total":18,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1745573,"bossDmg":937264,"heal":66120,"deaths":15,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":389964,"bossDmg":185386,"heal":7841,"deaths":25,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,79,3,262071,30808,624,31,2],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,80,0,1319,16539,571,8,1],
["p1",3,"op","chess_char_5_14_b",1,"none",3702.21,1,85,1,26292,12901,3039,6,7],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,66,0,13133,24424,2166,9,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1214.62,1,88,3,813951,3465,18796,93,12],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",0,0,76,3,399625,12328,635,28,3],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,71,0,17983,7337,821,7,1],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,72,2,199412,6767,323,18,7],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,64,0,0,11634,24266,20,2],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",0,0,82,2,57122,10198,28,18,1],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,87,0,0,7386,644,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,77,1,12912,4604,140,9,1],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",1773.92,1,92,2,23376,15406,71,45,3],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,65,0,5587,12946,4426,9,4],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,89,0,81942,17280,425,23,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,69,1,146718,12638,460,31,3],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,81,1,156574,4640,344,115,2],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,18,0,100154,7626,531,60,2],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,84,0,44145,4466,488,43,14],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,60,0,5121,3311,59,3,1],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",5244.92,1,85,2,93962,25464,853,43,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",6488.78,1,76,0,4978,12151,903,7,2],
["p1",3,"op","chess_char_5_14_b",1,"none",3988,1,87,0,40053,15591,4140,41,7],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",5476,1,88,1,45108,22564,3261,24,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1602.62,1,74,1,744558,4047,17936,100,11],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2764,1,82,3,449489,17324,228,43,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,1,1,144401,4077,819,12,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,64,1,19039,6619,175,12,3],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",3220,1,71,0,0,14759,37696,35,4],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",0,0,77,4,203984,10279,109,9,1],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,4,0,0,4260,161,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,70,0,25247,5349,885,14,2],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",2149,1,90,0,9322,15881,103,48,3],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,63,0,8814,10282,1941,3,2],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1185.56,1,83,1,96977,19717,1326,32,10],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,62,1,73045,9516,232,18,2],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,86,0,18579,3122,0,163,3],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,18,1,119533,6337,363,64,2],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,73,0,33296,6531,2553,37,12],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,61,0,5151,3529,277,3,1],
["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]
],
"enemies": {
"enemy_10027_vtsk": [6,2,4,146657,875461],
"enemy_10027_vtsk": [6,0,4,138479,677740],
"enemy_1005_yokai": [4,4,0,0,1600],
"enemy_1229_darmy_2": [8,8,0,2986,294231],
"enemy_9013_acstmk_2": [1,0,0,55467,1137141],
"enemy_9014_acstma": [6,5,0,6657,7982],
"enemy_9015_acstmb": [3,2,0,14937,73414],
"enemy_9016_acstmr": [8,7,0,0,47]
"enemy_1229_darmy_2": [8,8,0,3451,294231],
"enemy_9013_acstmk_2": [1,0,0,51007,1122650],
"enemy_9014_acstma": [5,4,0,5742,7466],
"enemy_9015_acstmb": [3,2,0,18719,56499],
"enemy_9016_acstmr": [8,7,0,0,52]
},
"hooks": [1,90,6000,693,695,2663,2759,5887,75,81,69,78,3,17822,841,16,36,4,1,77,0,4,8],
"events": {"atk":886,"deploy":54,"die":77,"dmg":2758,"fx":577,"heal":168,"leak":4,"skill":138,"spawn":58,"status":660},
"hooks": [1,88,6000,813,814,2708,2782,7397,66,71,77,74,7,21230,726,15,35,4,1,69,0,0,6],
"events": {"atk":977,"deploy":53,"die":67,"dmg":2779,"fx":604,"heal":181,"leak":4,"skill":151,"spawn":57,"status":726},
"fx": {
"aoe": 20,
"appear": 7,
"beam": 48,
"blink": 16,
"burst": 4,
"charge": 11,
"counter": 3,
"crit": 62,
"dilemma": 3,
"disappear": 7,
"dodge": 8,
"explode": 8,
"aoe": 19,
"appear": 10,
"beam": 45,
"blink": 15,
"charge": 9,
"counter": 7,
"crit": 79,
"dilemma": 2,
"disappear": 10,
"displace": 1,
"dodge": 6,
"explode": 7,
"explosion": 1,
"frostNova": 14,
"iceSpike": 4,
"frostNova": 12,
"iceSpike": 12,
"levitate": 7,
"overclock": 1,
"palsy": 1,
"palsy": 7,
"phase": 1,
"pull": 1,
"quadShot": 2,
"revive": 2,
"ripple": 7,
"ripple": 2,
"sentry": 5,
"sermon": 1,
"sermon": 2,
"shadowWeave": 5,
"shell": 8,
"shield": 3,
"shield": 7,
"slippery": 2,
"soul": 4,
"sp": 11,
"spGain": 3,
"sp": 3,
"spGain": 4,
"splash": 1,
"stealth": 3,
"summon": 4,
"swordRain": 67,
"swordStorm": 9,
"swordRain": 78,
"swordStorm": 10,
"telegraph": 6,
"tornado": 1,
"tornadoPulse": 5,
"weightless": 3,
"zone": 206
"tornado": 2,
"tornadoPulse": 8,
"weightless": 4,
"zone": 194
},
"snaps": "22e07f1e 19c1844a 05819a41 cfd700f0 eb877a23 de50ef00 f4939038 18b1425d d0829b20 931209b5 162058fa bccd16ab b6114221 59e7ea05 6e957f17 ad7d3f17 e4524db2 40bb9d67 7eedcf08 389b4af1"
"snaps": "264b53d3 6fb1395f acf19f90 c8b5fd49 ffacda1f acd792c1 1cb10cd2 fe369e7d b7b9d050 c11e4e8e 5bb9e66b a1356611 34955c15 942d0207 eff26c91 0641e8c1 ffdfb125 bf8b5166 4a71053e 70aee475"
},
"hidden-boss_8-solo": {
"about": "假想敌:胄 solo pool 1800000: 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",
@@ -969,7 +970,7 @@
"enemy_9016_acstmr": [7,7,0,0,31]
},
"hooks": [1,53,6000,301,301,538,573,110,37,45,17,17,0,5624,46,4,27,8,1,33,0,0,0],
"events": {"atk":354,"deploy":22,"die":37,"dmg":567,"fx":174,"heal":104,"leak":8,"skill":33,"spawn":41,"status":138},
"events": {"atk":354,"deploy":22,"die":37,"dmg":567,"fx":174,"heal":104,"leak":8,"skill":35,"spawn":41,"status":138},
"fx": {"bandage":1,"beam":32,"blink":10,"block":3,"charge":6,"explode":7,"featherArrow":1,"garrisonGrant":1,"healField":4,"mote":5,"phase":1,"redistribute":7,"reweave":1,"rockslide":6,"sandChains":1,"shell":7,"shieldBreak":6,"slash":56,"spGift":3,"summon":5,"telegraph":3,"thorns":5,"truesilver":3},
"snaps": "32c4a923 512591e1 e9c907a6 e3107633 ba7c0969 3a59d469 b8cecc85 46968da9 2129d863 19b4ada6 9afd8bfd ede8ed1f 6d35ee92 820ce8ef 4981d928 d404a14b 089b661f 39f4bf14 8620255e 7016887c"
},
@@ -1149,12 +1150,12 @@
"killed": 12,
"total": 12,
"unspawned": 0,
"bossHpLeft": 6538312,
"rngDraws": 443,
"bossHpLeft": 6533029,
"rngDraws": 447,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":423452,"bossDmg":251067,"heal":8710,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":972739,"bossDmg":810621,"heal":4586,"deaths":6,"layerGains":{}}
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":424664,"bossDmg":253185,"heal":8710,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":976811,"bossDmg":813786,"heal":4705,"deaths":6,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,5,0,0,0,0,0,0],
@@ -1163,7 +1164,7 @@
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,42,0,5478,4818,1781,19,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,5,216376,0,3535,25,6],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,0,63613,3102,23,55,5],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,21,2,116902,2929,347,221,6],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,21,2,118113,2929,347,221,6],
["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,3025,4,2],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,13,0,17221,0,0,10,1],
@@ -1171,11 +1172,11 @@
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,47,0,7846,2420,188,13,1],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,30,0,320008,0,0,251,7],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,48,0,2076,6222,1217,4,2],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,38,0,28816,2204,585,341,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2366.4,1,20,7,346029,2520,1510,122,6],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,38,0,28887,2204,585,341,2],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2060.34,1,20,7,349105,2945,1629,123,6],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,10,1,10469,40,40,13,2],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,18,0,98654,416,416,111,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,22,0,111281,630,630,200,49],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,22,0,112204,630,630,200,49],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,4,0,47559,0,0,17,5],
["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],
@@ -1185,31 +1186,31 @@
"enemies": {
"enemy_1402_tgshd_2": [2,2,0,9259,173864],
"enemy_9010_acpupp": [2,2,0,3029,160520],
"enemy_9021_acduml_2": [1,0,0,0,1061688],
"enemy_9022_acdumm": [1,0,0,193,0],
"enemy_9023_acdums": [12,12,0,10715,120]
"enemy_9021_acduml_2": [1,0,0,0,1066971],
"enemy_9022_acdumm": [1,0,0,373,0],
"enemy_9023_acdums": [12,12,0,10960,120]
},
"hooks": [1,49,6000,1245,1372,2347,2453,4134,23,24,102,100,0,33025,1845,8,18,0,1,26,0,3,0],
"events": {"atk":1349,"deploy":29,"die":24,"dmg":2435,"fx":874,"heal":64,"skill":201,"spawn":42,"status":672},
"hooks": [1,49,6000,1246,1377,2368,2483,4223,23,24,104,101,0,32963,1844,8,18,0,1,26,0,3,0],
"events": {"atk":1352,"deploy":29,"die":24,"dmg":2465,"fx":877,"heal":64,"skill":205,"spawn":42,"status":671},
"fx": {
"aoe": 5,
"appear": 2,
"beam": 12,
"beam": 13,
"blink": 1,
"burst": 3,
"charge": 2,
"crit": 9,
"crit": 7,
"dilemma": 6,
"disappear": 2,
"displace": 4,
"ember": 2,
"explode": 175,
"explode": 178,
"explosion": 5,
"expose": 209,
"frostNova": 49,
"iceSpike": 175,
"overclock": 1,
"phase": 32,
"phase": 33,
"pull": 1,
"ripple": 10,
"sentry": 1,
@@ -1228,7 +1229,7 @@
"weightless": 5,
"zone": 114
},
"snaps": "9477cf77 c4a7c1f1 6b8aff0a cc5cc18e 2e4ea3b5 39789a8c d54686f3 8b23f51b 39fde3da 2c783868 f3b430ea 88324a3d d6f55835 89f98c03 2fc000ed 0b0ae8cf 6d8b4321 b7edc120 45db4ae2 6343d81b"
"snaps": "2c69fe6c abc6b86b f72f3e77 8614e1a0 400871dc 3b1793ec d3f50041 7a737a83 03e5d8a4 19d891ad 49ae8f1a 81011e7a eba67866 a900037d 0c5a9938 086b50ab a6d68dfb e3154077 37c85fa4 d28a4203"
},
"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",
@@ -1269,7 +1270,7 @@
"enemy_9023_acdums": [10,10,0,38449,100]
},
"hooks": [1,26,6000,606,606,1242,1839,452,13,14,27,24,0,13867,59,10,15,1,1,17,0,11,0],
"events": {"atk":723,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":441,"leak":1,"skill":51,"spawn":25,"status":257},
"events": {"atk":723,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":441,"leak":1,"skill":53,"spawn":25,"status":257},
"fx": {"bandage":4,"beam":15,"burst":11,"ember":2,"explode":189,"featherArrow":7,"garrisonGrant":1,"healField":2,"jungleSoul":5,"mote":61,"phase":30,"redistribute":43,"reweave":3,"rockslide":2,"sandChainsCharged":1,"shieldBreak":15,"slash":52,"spGift":7,"summon":8,"telegraph":2,"truesilver":3},
"snaps": "39af4287 c1c868de fadf8f46 c55be858 66f9b2a4 3ff37c1b 45adfa96 4967058e cea41925 489c48a7 bccea42b 898a4b3c 6df25753 873fc61f 1619fcb8 7afad5a1 763db01d 46db0a43 f918b4af 75a431db"
},
+32 -32
View File
@@ -592,7 +592,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":250,"bossDamage":283411,"dmgDealt":2891237,"gold":139,"itemsEquipped":12,"kills":329,"leaks":10,"lpLost":8,"merges":5,"perfectRounds":10,"refreshes":23},
"stats": {"activatedLayers":250,"bossDamage":283411,"dmgDealt":2897768,"gold":139,"itemsEquipped":12,"kills":329,"leaks":10,"lpLost":8,"merges":5,"perfectRounds":10,"refreshes":23},
"lineup": ["3_19_b@11,7[2_04_e_b]","5_12_a@11,8","4_10_a@10,5[1_02_e_b+2_01_e_a]","6_06_a@9,4","1_08_b@9,5","6_16_a@9,7","5_20_a@9,8[3_05_e_a+3_02_e_b]","3_13_b@9,9"],
"bonds": ["arcaneShip 2/86","miraShip 1/12","preciShip 0/10","sargonShip 0/10","siracusaShip 1/110","skillfulShip 1/36","soloShip 1/6","suntShip 1/0","swiftShip 0/32"]
},
@@ -622,7 +622,7 @@
"combat": "client",
"boost": null,
"setup": {"stageId":"act1autochess_m04","bossId":"boss_4","hiddenBossId":"boss_10","factions":["FLY","TIMES","REFLECTION"]},
"end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":114800},
"end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":116300},
"rng": {"setup":25,"shop":391,"waves":0,"draft":73,"bots":13595,"meta":11},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
@@ -659,7 +659,7 @@
{
"round": 7,
"ai_0": [1,25,10,0,0,4,8,"7dbd16ef",20,"kazimierzShip 1/2, kjeragShip 0/2, raidShip 1/4, skillfulShip 1/3, soloShip 1/3, steadShip 1/6, swiftShip 1/2",28,28,0,51],
"ai_1": [1,24,10,0,2,4,8,"b6785496",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",28,28,0,53]
"ai_1": [1,24,10,0,2,4,8,"6a9b4c34",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",22,28,6,53]
},
{
"round": 8,
@@ -679,17 +679,17 @@
{
"round": 11,
"ai_0": [1,13,13,0,1,5,8,"cf13d02e",107,"kazimierzShip 1/33, kjeragShip 0/2, raidShip 1/33, skillfulShip 0/9, soloShip 1/3, steadShip 2/12, suntShip 1/0, swiftShip 1/26",10,27,17,100],
"ai_1": [1,4,12,0,0,5,8,"60f9ee96",81,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/46, sargonShip 0/4, siracusaShip 1/10, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",6,27,21,102]
"ai_1": [1,4,12,0,1,5,8,"60f9ee96",83,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/46, sargonShip 0/4, siracusaShip 1/12, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",7,27,20,102]
},
{
"round": 12,
"ai_0": [1,3,13,0,0,6,8,"17862185",123,"kazimierzShip 1/39, kjeragShip 0/2, raidShip 1/34, skillfulShip 0/9, soloShip 1/3, steadShip 2/17, suntShip 1/0, swiftShip 1/30, visiShip 1/0",17,50,33,114],
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/55, sargonShip 0/4, siracusaShip 0/10, skillfulShip 0/6, swiftShip 0/23",22,50,28,114]
"ai_1": [0,0,13,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/55, sargonShip 0/4, siracusaShip 0/12, skillfulShip 0/6, swiftShip 0/23",22,50,28,114]
},
{
"round": 13,
"ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"kazimierzShip 0/51, kjeragShip 0/2, miraShip 0/7, preciShip 0/8, raidShip 0/34, skillfulShip 0/9, soloShip 0/3, steadShip 0/17, swiftShip 0/38, yanShip 0/6",14,49,35,126],
"ai_1": [0,0,0,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/55, sargonShip 0/4, siracusaShip 0/10, skillfulShip 0/6, swiftShip 0/23",null,null,null,114]
"ai_1": [0,0,0,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/55, sargonShip 0/4, siracusaShip 0/12, skillfulShip 0/6, swiftShip 0/23",null,null,null,114]
}
],
"players": {
@@ -703,7 +703,7 @@
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2518112,"gold":126,"itemsEquipped":8,"kills":203,"leaks":87,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2556830,"gold":126,"itemsEquipped":8,"kills":209,"leaks":87,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
"lineup": [],
"bonds": ["kazimierzShip 0/51","kjeragShip 0/2","miraShip 0/7","preciShip 0/8","raidShip 0/34","skillfulShip 0/9","soloShip 0/3","steadShip 0/17","swiftShip 0/38","yanShip 0/6"]
},
@@ -717,9 +717,9 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1382343,"gold":114,"itemsEquipped":11,"kills":164,"leaks":69,"lpLost":32,"merges":3,"perfectRounds":5,"refreshes":19},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1361757,"gold":114,"itemsEquipped":11,"kills":159,"leaks":74,"lpLost":32,"merges":3,"perfectRounds":4,"refreshes":19},
"lineup": [],
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/55","sargonShip 0/4","siracusaShip 0/10","skillfulShip 0/6","swiftShip 0/23"]
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/55","sargonShip 0/4","siracusaShip 0/12","skillfulShip 0/6","swiftShip 0/23"]
}
}
},
@@ -733,8 +733,8 @@
"combat": "client",
"boost": null,
"setup": {"stageId":"act2autochess_m03","bossId":"boss_4","hiddenBossId":"boss_9","factions":["FLY","TIMES","REFLECTION"]},
"end": {"victory":false,"roundsPassed":11,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":116600},
"rng": {"setup":25,"shop":561,"waves":0,"draft":76,"bots":24785,"meta":19},
"end": {"victory":false,"roundsPassed":11,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":116950},
"rng": {"setup":25,"shop":565,"waves":0,"draft":76,"bots":25625,"meta":20},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{
@@ -763,7 +763,7 @@
},
{
"round": 5,
"ai_0": [1,23,11,0,0,3,8,"59856422",20,"deputShip 1/12, kazimierzShip 0/2, skillfulShip 1/2, swiftShip 1/6",47,51,4,33],
"ai_0": [1,23,11,0,0,3,8,"59856422",20,"deputShip 1/12, kazimierzShip 0/2, skillfulShip 1/2, swiftShip 1/6",49,51,2,33],
"ai_1": [1,45,10,0,0,3,8,"a37755ee",27,"deputShip 2/6, kazimierzShip 0/7, swiftShip 1/21",53,53,0,32],
"ai_2": [1,27,9,0,2,3,8,"c25b67c7",22,"deputShip 1/4, siracusaShip 1/4, soloShip 1/14, yanShip 0/1",53,53,0,31]
},
@@ -787,26 +787,26 @@
},
{
"round": 9,
"ai_0": [1,3,12,0,0,4,8,"6d558be7",154,"deputShip 2/32, indomShip 1/4, kazimierzShip 0/2, kjeragShip 1/25, skillfulShip 1/51, soloShip 1/16, steadShip 1/22, suntShip 1/4, swiftShip 0/20",57,65,8,81],
"ai_0": [1,5,12,0,0,4,8,"6d558be7",156,"deputShip 2/32, indomShip 1/4, kazimierzShip 0/2, kjeragShip 1/27, skillfulShip 1/51, soloShip 1/16, steadShip 1/22, suntShip 1/4, swiftShip 0/20",61,67,6,81],
"ai_1": [1,24,12,0,0,4,8,"ad1959de",111,"deputShip 2/35, kazimierzShip 0/7, skillfulShip 1/22, soloShip 1/16, suntShip 1/16, swiftShip 0/21, victoriaShip 1/22",44,56,12,73],
"ai_2": [1,10,12,0,0,4,8,"e0957cd2",127,"deputShip 0/10, miraShip 1/3, siracusaShip 2/78, soloShip 1/22, suntShip 1/6, swiftShip 1/18, yanShip 0/1",57,63,6,75]
},
{
"round": 10,
"ai_0": [1,3,12,0,0,5,8,"f8b9abf3",197,"deputShip 2/36, indomShip 1/8, kazimierzShip 0/2, kjeragShip 1/35, skillfulShip 1/57, soloShip 1/20, steadShip 1/33, suntShip 1/8, swiftShip 0/20",18,18,0,92],
"ai_1": [1,17,12,0,0,5,8,"6f5bb81b",124,"deputShip 2/40, indomShip 1/5, kazimierzShip 0/7, skillfulShip 1/26, soloShip 1/20, suntShip 0/16, swiftShip 0/21, victoriaShip 1/33",3,18,15,85],
"ai_0": [1,5,12,0,0,5,8,"f8b9abf3",199,"deputShip 2/36, indomShip 1/8, kazimierzShip 0/2, kjeragShip 1/37, skillfulShip 1/57, soloShip 1/20, steadShip 1/33, suntShip 1/8, swiftShip 0/20",18,18,0,92],
"ai_1": [1,18,12,0,0,5,8,"6f5bb81b",124,"deputShip 2/40, indomShip 1/5, kazimierzShip 0/7, skillfulShip 1/26, soloShip 1/20, suntShip 0/16, swiftShip 0/21, victoriaShip 1/33",3,18,15,85],
"ai_2": [1,3,12,0,2,5,8,"b6b17abc",137,"deputShip 0/10, miraShip 1/4, siracusaShip 2/82, soloShip 1/22, suntShip 1/6, swiftShip 1/23, yanShip 0/1",8,18,10,86]
},
{
"round": 11,
"ai_0": [0,0,12,0,0,5,0,"811c9dc5",0,"deputShip 0/38, indomShip 0/10, kazimierzShip 0/2, kjeragShip 0/41, skillfulShip 0/65, soloShip 0/22, steadShip 0/42, suntShip 0/10, swiftShip 0/26",22,25,3,103],
"ai_1": [1,7,12,0,0,5,8,"a9a4015c",177,"deputShip 2/45, indomShip 1/28, kazimierzShip 0/7, skillfulShip 1/30, soloShip 1/24, steadShip 1/8, suntShip 0/16, swiftShip 0/21, victoriaShip 1/42",7,25,18,98],
"ai_0": [1,2,12,0,1,5,8,"fc0a3b21",256,"deputShip 1/38, indomShip 1/10, kazimierzShip 0/2, kjeragShip 1/43, skillfulShip 1/65, soloShip 1/22, steadShip 1/42, suntShip 1/10, swiftShip 1/26",22,25,3,103],
"ai_1": [1,8,12,0,0,5,8,"a9a4015c",177,"deputShip 2/45, indomShip 1/28, kazimierzShip 0/7, skillfulShip 1/30, soloShip 1/24, steadShip 1/8, suntShip 0/16, swiftShip 0/21, victoriaShip 1/42",7,25,18,98],
"ai_2": [0,0,14,0,0,5,0,"811c9dc5",0,"deputShip 0/10, miraShip 0/7, preciShip 0/5, siracusaShip 0/104, soloShip 0/22, suntShip 0/6, swiftShip 0/28, yanShip 0/1",9,25,16,100]
},
{
"round": 12,
"ai_0": [0,0,0,0,0,5,0,"811c9dc5",0,"deputShip 0/38, indomShip 0/10, kazimierzShip 0/2, kjeragShip 0/41, skillfulShip 0/65, soloShip 0/22, steadShip 0/42, suntShip 0/10, swiftShip 0/26",null,null,null,103],
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/48, indomShip 0/54, kazimierzShip 0/11, skillfulShip 0/32, soloShip 0/26, steadShip 0/8, suntShip 0/16, swiftShip 0/27, victoriaShip 0/49",4,57,53,111],
"ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"deputShip 0/42, indomShip 0/14, kazimierzShip 0/2, kjeragShip 0/47, skillfulShip 0/71, soloShip 0/26, steadShip 0/54, suntShip 0/14, swiftShip 0/26",30,57,27,116],
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/50, indomShip 0/56, kazimierzShip 0/7, skillfulShip 0/34, soloShip 0/28, steadShip 0/17, suntShip 0/16, swiftShip 0/21, victoriaShip 0/51",11,57,46,110],
"ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"deputShip 0/10, miraShip 0/7, preciShip 0/5, siracusaShip 0/104, soloShip 0/22, suntShip 0/6, swiftShip 0/28, yanShip 0/1",null,null,null,100]
}
],
@@ -814,16 +814,16 @@
"ai_0": {
"alive": false,
"victory": false,
"roundsPassed": 10,
"eliminatedRound": 11,
"roundsPassed": 11,
"eliminatedRound": 12,
"lp": 0,
"bandId": "band_chiave",
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 2,
"reward": 213,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4040284,"gold":103,"itemsEquipped":14,"kills":258,"leaks":32,"lpLost":23,"merges":3,"perfectRounds":4,"refreshes":10},
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":5277649,"gold":116,"itemsEquipped":15,"kills":295,"leaks":55,"lpLost":31,"merges":3,"perfectRounds":4,"refreshes":10},
"lineup": [],
"bonds": ["deputShip 0/38","indomShip 0/10","kazimierzShip 0/2","kjeragShip 0/41","skillfulShip 0/65","soloShip 0/22","steadShip 0/42","suntShip 0/10","swiftShip 0/26"]
"bonds": ["deputShip 0/42","indomShip 0/14","kazimierzShip 0/2","kjeragShip 0/47","skillfulShip 0/71","soloShip 0/26","steadShip 0/54","suntShip 0/14","swiftShip 0/26"]
},
"ai_1": {
"alive": false,
@@ -835,9 +835,9 @@
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1816653,"gold":111,"itemsEquipped":10,"kills":244,"leaks":113,"lpLost":48,"merges":3,"perfectRounds":5,"refreshes":17},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1943175,"gold":110,"itemsEquipped":11,"kills":249,"leaks":106,"lpLost":47,"merges":2,"perfectRounds":5,"refreshes":17},
"lineup": [],
"bonds": ["deputShip 0/48","indomShip 0/54","kazimierzShip 0/11","skillfulShip 0/32","soloShip 0/26","steadShip 0/8","suntShip 0/16","swiftShip 0/27","victoriaShip 0/49"]
"bonds": ["deputShip 0/50","indomShip 0/56","kazimierzShip 0/7","skillfulShip 0/34","soloShip 0/28","steadShip 0/17","suntShip 0/16","swiftShip 0/21","victoriaShip 0/51"]
},
"ai_2": {
"alive": false,
@@ -986,7 +986,7 @@
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":207,"bossDamage":12338,"dmgDealt":1294439,"gold":129,"itemsEquipped":20,"kills":240,"leaks":43,"lpLost":0,"merges":6,"perfectRounds":9,"refreshes":20},
"stats": {"activatedLayers":207,"bossDamage":12384,"dmgDealt":1294492,"gold":129,"itemsEquipped":20,"kills":240,"leaks":43,"lpLost":0,"merges":6,"perfectRounds":9,"refreshes":20},
"lineup": ["2_14_b@12,3","5_10_a@11,6[4_03_e_b+1_02_e_a]","6_01_a@11,7[3_05_e_a]","5_10_a@11,8[5_09_e_a+4_08_e_b]","6_14_a@10,3","4_16_a@10,8[4_11_e_a+4_04_e_b]","6_05_a@9,8[1_05_e_a+5_03_e_a]","2_16_a@9,9[5_02_e_a+4_05_e_b]"],
"bonds": ["deputShip 1/96","emptyShip 1/23","preciShip 1/14","sargonShip 0/2","siracusaShip 1/74","steadShip 0/4"]
},
@@ -1000,7 +1000,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":282,"bossDamage":72960,"dmgDealt":1633867,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
"stats": {"activatedLayers":282,"bossDamage":72915,"dmgDealt":1633817,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
"lineup": ["4_25_a@12,3","1_07_b@11,7[1_01_e_a+1_05_e_a]","5_20_a@11,8[3_02_e_a+2_01_e_b]","6_18_a@10,3[2_01_e_b+4_03_e_b]","4_10_a@10,7[4_03_e_b]","3_18_b@9,4[3_07_e_b+3_11_e_a]","6_05_a@9,8","2_16_b@9,9[5_01_e_a+4_02_e_a]"],
"bonds": ["sargonShip 0/8","siracusaShip 2/258","swiftShip 1/24","yanShip 0/1"]
},
@@ -1158,7 +1158,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4175013,"gold":113,"itemsEquipped":11,"kills":170,"leaks":75,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":9},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4195040,"gold":113,"itemsEquipped":11,"kills":170,"leaks":75,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":9},
"lineup": [],
"bonds": ["deputShip 0/26","egirShip 0/6","kazimierzShip 0/4","kjeragShip 0/18","lateranoShip 0/4","preciShip 0/6","raidShip 0/6","skillfulShip 0/14","soloShip 0/14","steadShip 0/62","swiftShip 0/89","victoriaShip 0/45","yanShip 0/35"]
},
@@ -1353,7 +1353,7 @@
},
{
"round": 13,
"ai_0": [1,399,12,0,2,6,8,"eb5e835a",1944,"arcaneShip 2/360, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/308, investShip 0/300, kazimierzShip 0/308, kjeragShip 0/300, lateranoShip 1/310, maniShip 0/300, miraShip 0/300, preciShip 0/302, raidShip 0/300, sargonShip 0/300, siracusaShip 1/332, skillfulShip 1/335, soloShip 1/307, steadShip 0/300, suntShip 1/300, swiftShip 0/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",35,35,0,151]
"ai_0": [1,399,12,0,2,6,8,"eb5e835a",1938,"arcaneShip 2/360, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/308, investShip 0/300, kazimierzShip 0/308, kjeragShip 0/300, lateranoShip 1/304, maniShip 0/300, miraShip 0/300, preciShip 0/302, raidShip 0/300, sargonShip 0/300, siracusaShip 1/332, skillfulShip 1/335, soloShip 1/307, steadShip 0/300, suntShip 1/300, swiftShip 0/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",35,35,0,151]
}
],
"players": {
@@ -1367,9 +1367,9 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 0,
"reward": 425,
"stats": {"activatedLayers":2269,"bossDamage":1145000,"dmgDealt":3025009,"gold":176,"itemsEquipped":26,"kills":365,"leaks":1,"lpLost":1,"merges":5,"perfectRounds":12,"refreshes":34},
"stats": {"activatedLayers":2263,"bossDamage":1145000,"dmgDealt":3025009,"gold":176,"itemsEquipped":26,"kills":365,"leaks":1,"lpLost":1,"merges":5,"perfectRounds":12,"refreshes":34},
"lineup": ["4_02_b@12,7[3_08_e_a+6_03_e_a]","6_01_a@12,8[3_03_e_a+3_02_e_a]","6_16_a@11,5[1_05_e_b+1_02_e_b]","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_a@10,4[4_07_e_a+3_03_e_b]","6_05_a@10,5[1_02_e_a]","4_01_b@10,6[4_06_e_a+4_05_e_a]","1_08_b@9,9[4_10_e_a]"],
"bonds": ["arcaneShip 2/389","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/310","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/345","skillfulShip 0/335","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
"bonds": ["arcaneShip 2/389","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/304","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/345","skillfulShip 0/335","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
}
}
},
+207 -207
View File
@@ -62,7 +62,7 @@
"enemy_9010_acpupp": [1,0,0,0,959]
},
"hooks": [1,50,2700,404,404,632,636,325,34,34,25,21,19,14421,65,36,31,0,1,35,0,0,0],
"events": {"atk":426,"bounty":4,"deploy":16,"die":34,"dmg":636,"fx":65,"heal":319,"skill":45,"spawn":47,"status":126},
"events": {"atk":426,"bounty":4,"deploy":16,"die":34,"dmg":636,"fx":65,"heal":319,"skill":49,"spawn":47,"status":126},
"fx": {"aoe":1,"bombard":9,"bombardShell":10,"counter":6,"crit":18,"dp":1,"ember":1,"explode":3,"heal":11,"liberate":1,"summon":1,"taunt":1,"telegraph":1,"zone":1},
"snaps": "15544384 8f8ab114 83735bc4 1672a1a1 e9e7ffdb 9b9d0193 15df767d 792390ec 43783e3b"
},
@@ -90,7 +90,7 @@
["p1",2,"op","chess_char_1_20_a",1,null,2857.4,1,1,0,0,0,0,0,0],
["p1",3,"op","chess_char_1_04_b",1,"uniequip_002_udflow",3541.94,1,4,2,11017,233,0,12,1],
["p1",4,"op","chess_char_1_08_b",1,"uniequip_002_texas",0,0,7,1,6914,3473,0,2,2],
["p1",5,"op","chess_char_1_18_a",1,null,0,0,60,2,4710,6978,400,8,0],
["p1",5,"op","chess_char_1_18_a",1,null,0,0,60,2,4710,6978,400,8,2],
["p1",6,"op","chess_char_1_19_a",1,null,0,0,57,0,5402,3853,0,8,1],
["p1",7,"op","chess_char_1_17_a",1,null,0,0,5,0,0,1481,0,0,0],
["p1",8,"op","chess_char_1_01_b",1,"uniequip_002_inside",0,0,62,2,43961,3695,0,46,2],
@@ -118,10 +118,10 @@
"enemy_2005_axetro": [1,0,0,5090,1565],
"enemy_9011_acrefr": [1,0,0,0,2338]
},
"hooks": [1,62,3300,343,343,389,401,3347,45,56,11,16,31,7389,39,11,44,11,1,46,0,0,0],
"events": {"atk":361,"bounty":4,"deploy":18,"die":45,"dmg":401,"fx":46,"heal":15,"leak":11,"skill":22,"spawn":56,"status":73},
"hooks": [1,62,3300,343,343,389,401,3347,45,56,13,16,31,7389,39,11,44,11,1,46,0,0,0],
"events": {"atk":361,"bounty":4,"deploy":18,"die":45,"dmg":401,"fx":46,"heal":15,"leak":11,"skill":32,"spawn":56,"status":69},
"fx": {"aoe":4,"crit":20,"displace":8,"dp":2,"ember":1,"pulse":7,"revive":1,"summon":1,"taunt":2},
"snaps": "8776586b 475e50d7 071c9999 6c9d726c 9eb6d7fa 7d64c42f 9c037d87 005e2c01 a8568b0b cbe5c8a8 f8c59fb5"
"snaps": "7504f6da cf86e400 f495d65e e1e866ef 88f9cabd d57a8164 b1e49710 28ad6226 f40f3c34 62a776b0 2bec217d"
},
"roster-003": {
"about": "pass 0 band 2: chess_char_1_10_b chess_char_1_12_b chess_char_1_20_b chess_char_1_18_b chess_char_1_19_b chess_char_1_13_b chess_char_1_14_b chess_char_1_17_b chess_char_2_01_a chess_char_2_02_a chess_char_2_04_a chess_char_2_05_a token_10000_silent_healrb",
@@ -148,7 +148,7 @@
["p1",1,"op","chess_char_1_10_b",0,"uniequip_002_sunbr",2493,1,5,1,1,0,818,7,6],
["p1",2,"op","chess_char_1_12_b",1,"uniequip_002_estell",2215.6,1,7,2,2737,991,0,38,2],
["p1",3,"op","chess_char_1_20_b",1,"uniequip_002_liskam",1682.2,1,10,16,22525,1763,0,73,1],
["p1",4,"op","chess_char_1_18_b",1,"uniequip_002_utage",0,0,11,6,23407,10446,7092,102,0],
["p1",4,"op","chess_char_1_18_b",1,"uniequip_002_utage",0,0,11,6,23407,10446,7092,102,1],
["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,14,7,42035,8350,0,76,2],
["p1",6,"op","chess_char_1_13_b",1,"uniequip_002_podego",2446.5,1,8,3,3,0,0,2,2],
["p1",7,"op","chess_char_1_14_b",1,"uniequip_002_greyy",1017,1,12,9,874,2616,0,18,1],
@@ -181,10 +181,10 @@
"enemy_2008_flking": [1,0,0,12348,0],
"enemy_9016_acstmr": [1,1,0,0,8]
},
"hooks": [1,104,3300,602,602,708,826,235,87,91,23,22,0,15806,206,28,85,0,1,94,1,0,0],
"events": {"atk":685,"bounty":7,"deploy":16,"die":91,"dmg":805,"fx":108,"heal":207,"layer":1,"skill":44,"spawn":101,"status":221},
"hooks": [1,104,3300,602,602,708,826,235,87,91,24,22,0,15820,206,28,85,0,1,94,1,0,0],
"events": {"atk":685,"bounty":7,"deploy":16,"die":91,"dmg":805,"fx":108,"heal":207,"layer":1,"skill":46,"spawn":101,"status":219},
"fx": {"aoe":1,"blink":1,"crit":1,"displace":6,"ember":1,"explode":10,"garrison":1,"heal":6,"pulse":45,"reveal":1,"sleep":8,"summon":24,"undying":1,"zone":2},
"snaps": "71b7c4d9 509c11bd 88a6db76 28da7031 52a90f4b db8bcb09 03eabfbe d95b5c5d a0750867 1514f7fd cf878e2d"
"snaps": "554dbc21 dca8028a bfe874b1 c3ea8fde b234e0fc e2e35296 0e5e8cfd 66160212 74b71698 3535e79e 56ba7f72"
},
"roster-004": {
"about": "pass 0 band 2: chess_char_2_08_a chess_char_2_17_a chess_char_2_18_a chess_char_2_07_a chess_char_2_09_a chess_char_2_11_a chess_char_2_13_a chess_char_2_16_a chess_char_2_12_a chess_char_2_06_a chess_char_2_10_a chess_char_2_14_a",
@@ -214,7 +214,7 @@
["p1",6,"op","chess_char_2_11_a",1,null,0,0,12,1,13899,7114,0,27,1],
["p1",7,"op","chess_char_2_13_a",1,null,0,0,13,1,10519,3427,0,20,1],
["p1",8,"op","chess_char_2_16_a",1,null,3098.43,1,54,1,5847,4789,0,15,0],
["p1",9,"op","chess_char_2_12_a",1,null,0,0,52,4,11913,4469,0,41,0],
["p1",9,"op","chess_char_2_12_a",1,null,0,0,52,4,11913,4469,0,41,2],
["p1",10,"op","chess_char_2_06_a",0,null,0,0,4,0,0,3624,11718,22,7],
["p1",11,"op","chess_char_2_10_a",1,null,0,0,5,0,5860,2118,0,44,1],
["p1",12,"op","chess_char_2_14_a",1,null,2131.36,1,55,0,0,4030,5401,10,0],
@@ -240,10 +240,10 @@
"enemy_1504_cqbw": [1,0,1,0,490],
"enemy_2009_csaudc": [1,0,1,0,490]
},
"hooks": [1,56,3296,431,431,1432,1451,182,23,50,15,17,0,10435,29,27,36,27,1,31,0,2,0],
"events": {"atk":493,"bounty":1,"deploy":17,"die":23,"dmg":1384,"fx":20,"heal":182,"leak":27,"skill":30,"spawn":52,"status":161},
"hooks": [1,56,3296,431,431,1432,1451,182,23,50,17,17,0,10435,29,27,36,27,1,31,0,2,0],
"events": {"atk":493,"bounty":1,"deploy":17,"die":23,"dmg":1384,"fx":20,"heal":182,"leak":27,"skill":34,"spawn":52,"status":161},
"fx": {"aoe":4,"burst":2,"camouflage":2,"dodge":3,"dollEnd":1,"liberate":1,"overload":1,"shield":2,"shieldBreak":1,"substitute":1,"summon":1,"takeoff":1},
"snaps": "5e5ad012 2959f5b3 6f8f0a25 7a19ca8d 735c2d88 ba5818a8 aa9d4c4c e9f19437 8ae79dca 59339c7a 8d8c5022"
"snaps": "f188aea7 a6afe0e2 586473b0 64a602b4 a28a4c01 c2e6f731 0fc56c2d 9e21f4c3 795ab8db 9dbb7b5b 6f022b9b"
},
"roster-005": {
"about": "pass 0 band 3: chess_char_2_07_b chess_char_2_08_b chess_char_2_09_b chess_char_2_11_b chess_char_2_12_b chess_char_2_19_a chess_char_2_01_b chess_char_2_02_b chess_char_2_04_b chess_char_2_05_b chess_char_2_06_b chess_char_2_10_b token_10000_silent_healrb",
@@ -269,7 +269,7 @@
["p1",2,"op","chess_char_2_08_b",1,"uniequip_002_bubble",3373,1,4,0,336,1472,0,0,1],
["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",2133,1,6,29,83330,0,0,58,2],
["p1",4,"op","chess_char_2_11_b",1,"uniequip_002_kazema",2214,1,8,16,80064,11387,0,85,3],
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1452,1,10,2,7397,9338,0,130,0],
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1452,1,10,2,7397,9338,0,130,1],
["p1",6,"op","chess_char_2_19_a",1,null,1021.2,1,11,1,14957,2535,356,26,3],
["p1",7,"op","chess_char_2_01_b",1,"uniequip_002_excu",2146,1,12,20,36079,6856,0,22,1],
["p1",8,"op","chess_char_2_02_b",1,"uniequip_002_silent",1614,1,1,0,0,0,8056,29,5],
@@ -295,10 +295,10 @@
"enemy_1509_mousek": [1,1,0,9774,17600],
"enemy_2010_csdcr": [1,1,0,1414,9600]
},
"hooks": [1,102,4200,712,712,1693,1684,767,82,89,36,35,0,28924,152,52,84,1,1,82,0,0,0],
"events": {"atk":893,"bounty":5,"deploy":18,"die":88,"dmg":1628,"fx":69,"heal":143,"leak":1,"skill":71,"spawn":97,"status":437},
"hooks": [1,102,4200,712,712,1693,1684,767,82,89,37,36,0,28924,152,52,84,1,1,82,0,0,0],
"events": {"atk":893,"bounty":5,"deploy":18,"die":88,"dmg":1628,"fx":69,"heal":143,"leak":1,"skill":73,"spawn":97,"status":437},
"fx": {"counter":1,"explode":3,"expose":14,"overload":2,"phase":1,"shield":8,"shieldBreak":16,"sleep":7,"summon":5,"telegraph":5,"zone":7},
"snaps": "ee572523 8710edac 620f58f9 829ee1fe abc6ffba af9de629 c1be6cb9 e28b3914 44204dcd 6da54116 7bf02b9f ffa24a7c ea6b1cec 81beb5ee"
"snaps": "de0d6fce 6d6804e9 d7011be0 b72154cf 325314c7 efa5c8d0 ae45ceac e177f8b9 c1acb458 e7de2cbb de6e5396 c820655d de465eb1 281894a7"
},
"roster-006": {
"about": "pass 0 band 3: chess_char_2_17_b chess_char_2_18_b chess_char_3_06_a chess_char_2_13_b chess_char_2_16_b chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_3_05_a chess_char_2_14_b chess_char_2_19_b chess_char_3_01_a",
@@ -353,7 +353,7 @@
"enemy_2017_csphts": [1,1,0,1838,9600]
},
"hooks": [1,90,4200,372,372,658,640,177,77,77,22,22,0,20005,55,36,78,0,1,77,1,0,4],
"events": {"atk":383,"bounty":5,"deploy":12,"die":77,"dmg":640,"fx":33,"heal":115,"layer":1,"skill":44,"spawn":90,"status":35},
"events": {"atk":383,"bounty":5,"deploy":12,"die":77,"dmg":640,"fx":33,"heal":115,"layer":1,"skill":46,"spawn":90,"status":35},
"fx": {"aoe":2,"buff":1,"coin":3,"dodge":9,"explode":2,"garrison":1,"phase":2,"shieldBreak":1,"sp":2,"takeoff":3,"zone":7},
"snaps": "7e2ad48a 6bd6d5ab b9d597a4 5d6f961e 1e4c04e2 525f85c6 9bf96317 42a8fa26 f1af39d5 66b9f6f3 bca33a7f 56cc754e 896ae641 1111820a"
},
@@ -445,7 +445,7 @@
"leaks": 51,
"leaksUncounted": 1,
"coins": 8,
"dmg": 374500,
"dmg": 380250,
"bossDmg": 0,
"heal": 5204,
"deaths": 18,
@@ -457,7 +457,7 @@
["p1",2,"op","chess_char_3_12_b",2,"uniequip_002_blemsh",0,0,19,6,32008,6013,5204,18,1],
["p1",3,"op","chess_char_3_02_b",1,"uniequip_002_ayer",0,0,10,1,26273,2911,0,37,1],
["p1",4,"op","chess_char_3_03_b",1,"uniequip_002_swire",0,0,105,2,22220,5092,0,25,1],
["p1",5,"op","chess_char_3_04_b",1,"uniequip_002_swire2",0,0,109,3,33237,15510,0,43,0],
["p1",5,"op","chess_char_3_04_b",1,"uniequip_002_swire2",0,0,109,3,38988,15626,0,49,0],
["p1",6,"op","chess_char_3_05_b",2,"uniequip_002_skadi",0,0,14,9,47733,13898,0,28,1],
["p1",7,"op","chess_char_3_21_a",2,null,0,0,2,0,14448,1464,0,11,1],
["p1",8,"op","chess_char_3_01_b",2,"uniequip_002_angel",0,0,3,3,34648,2037,0,41,1],
@@ -479,13 +479,13 @@
"enemy_1305_mhslim_2": [1,1,0,720,3110],
"enemy_1415_mmkabi_2": [1,0,1,0,11499],
"enemy_1513_dekght": [1,0,1,0,0],
"enemy_2021_syfish": [60,29,30,2215,222507],
"enemy_2025_syufo": [13,1,9,22002,61781]
"enemy_2021_syfish": [60,29,30,2265,228258],
"enemy_2025_syufo": [13,1,9,22069,61781]
},
"hooks": [1,109,5100,467,467,816,888,338,54,104,18,22,0,6770,54,39,88,47,1,58,11,8,0],
"events": {"atk":517,"bounty":4,"deploy":21,"die":57,"dmg":887,"fx":98,"heal":337,"layer":11,"leak":47,"skill":36,"spawn":100,"status":84},
"hooks": [1,109,5100,476,476,825,898,341,54,104,18,22,0,6770,54,40,88,47,1,58,11,8,0],
"events": {"atk":526,"bounty":4,"deploy":21,"die":57,"dmg":897,"fx":98,"heal":340,"layer":11,"leak":47,"skill":44,"spawn":100,"status":84},
"fx": {"blink":2,"buff":1,"burst":8,"coin":15,"displace":11,"explode":16,"garrison":6,"layer":5,"pull":11,"revive":4,"shield":1,"shieldBreak":1,"strike":8,"summon":1,"volley":8},
"snaps": "a90ffeac d57612e4 e8d10948 88e75de1 e4c4862e 99ffec22 42d6df71 787805d3 8509084c 62cebe35 3ecdb395 419f753d 70a4b54a 555aa325 72b287c5 1bd11f0b a6987cb5"
"snaps": "a90ffeac d57612e4 e8d10948 88e75de1 e4c4862e 99ffec22 42d6df71 787805d3 6d700ba6 662099ea 641c7acd 8c28fc25 17c9a531 45c3a979 675372ca 8171c584 4bb0fa0e"
},
"roster-009": {
"about": "pass 0 band 4: chess_char_3_16_b chess_char_4_01_a chess_char_3_18_b chess_char_4_04_a chess_char_3_13_b chess_char_3_14_b chess_char_3_17_b chess_char_3_19_b chess_char_3_20_b chess_char_3_21_b chess_char_4_02_a chess_char_4_06_a token_10028_vigil_wolf",
@@ -560,28 +560,28 @@
"time": 170,
"ticks": 5100,
"reason": "timeout",
"killed": 28,
"killed": 40,
"total": 102,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 119,
"rngDraws": 114,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":28,"total":102,"perfect":false,"leaks":74,"leaksUncounted":0,"coins":4,"dmg":401888,"bossDmg":0,"heal":2729,"deaths":21,"layerGains":{"preciShip":8}}
"p1": {"killed":40,"total":102,"perfect":false,"leaks":62,"leaksUncounted":0,"coins":4,"dmg":469309,"bossDmg":0,"heal":4633,"deaths":21,"layerGains":{"preciShip":8}}
},
"units": [
["p1",1,"op","chess_char_4_17_a",1,null,0,0,4,0,1213,2849,0,9,0],
["p1",1,"op","chess_char_4_17_a",1,null,0,0,4,0,947,2849,0,7,0],
["p1",2,"op","chess_char_4_18_a",1,null,0,0,5,1,8608,3319,452,4,4],
["p1",3,"op","chess_char_4_11_a",1,null,2144,1,125,0,0,3002,0,0,1],
["p1",4,"op","chess_char_4_12_a",2,null,0,0,102,2,46522,4540,1032,24,1],
["p1",4,"op","chess_char_4_12_a",2,null,0,0,102,5,70347,5044,1536,37,2],
["p1",5,"op","chess_char_4_19_a",2,null,0,0,82,0,6224,3120,0,9,1],
["p1",6,"op","chess_char_4_07_a",1,null,0,0,112,0,399,3476,0,1,1],
["p1",7,"op","chess_char_4_16_a",2,null,1,1,129,1,75566,7474,1245,23,0],
["p1",8,"op","chess_char_4_09_a",1,null,0,0,15,0,17615,1336,0,15,2],
["p1",9,"op","chess_char_4_10_a",1,null,0,0,6,0,35230,2190,0,14,1],
["p1",10,"op","chess_char_4_13_a",1,null,0,0,111,1,27879,3174,0,38,5],
["p1",11,"op","chess_char_4_14_a",1,null,0,0,104,10,162863,2552,0,29,2],
["p1",12,"op","chess_char_4_20_a",2,null,0,0,93,3,19767,2434,0,9,1],
["p1",6,"op","chess_char_4_07_a",1,null,0,0,112,0,423,3476,0,2,1],
["p1",7,"op","chess_char_4_16_a",2,null,1,1,129,5,100862,8873,2644,33,9],
["p1",8,"op","chess_char_4_09_a",1,null,0,0,15,0,16650,1336,0,14,2],
["p1",9,"op","chess_char_4_10_a",1,null,0,0,6,0,40704,2190,0,17,1],
["p1",10,"op","chess_char_4_13_a",1,null,0,0,111,1,30141,3174,0,43,5],
["p1",11,"op","chess_char_4_14_a",1,null,0,0,104,14,173742,2552,0,33,2],
["p1",12,"op","chess_char_4_20_a",2,null,0,0,93,4,20658,2434,0,10,1],
["p1",13,"token","token_10041_cathy_catsld",null,null,100,1,16,0,0,0,0,0,null],
["p1",14,"token","token_10041_cathy_catsld",null,null,100,1,17,0,0,0,0,0,null],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
@@ -590,26 +590,26 @@
],
"enemies": {
"enemy_10034_cnvsax": [1,1,0,4495,42998],
"enemy_1005_yokai": [24,10,14,0,51835],
"enemy_1005_yokai_2": [27,5,17,6823,83175],
"enemy_1005_yokai_3": [6,0,5,10406,110783],
"enemy_1005_yokai": [24,16,8,0,60158],
"enemy_1005_yokai_2": [27,11,14,5256,113643],
"enemy_1005_yokai_3": [6,0,5,11587,126060],
"enemy_10083_hlbird": [1,1,0,0,6450],
"enemy_10144_xdelk_2": [1,0,1,2001,32913],
"enemy_1050_lslime": [1,0,1,11371,13748],
"enemy_10144_xdelk_2": [1,0,1,2904,32957],
"enemy_1050_lslime": [1,0,1,11734,13748],
"enemy_1158_divman": [1,1,0,130,14189],
"enemy_1174_duholy_2": [1,0,1,1056,15752],
"enemy_1174_duholy_2": [1,0,1,801,15691],
"enemy_1204_msfhu_2": [1,0,1,0,3],
"enemy_1267_nhpbr_2": [1,0,1,0,2756],
"enemy_1269_nhfly": [33,10,23,2535,14436],
"enemy_1267_nhpbr_2": [1,0,1,61,3079],
"enemy_1269_nhfly": [33,10,23,3841,20722],
"enemy_1321_wdarft": [1,0,1,0,0],
"enemy_1422_lrsldr_2": [1,0,1,0,12663],
"enemy_1516_jakill": [1,0,1,649,186],
"enemy_1422_lrsldr_2": [1,0,1,0,19051],
"enemy_1516_jakill": [1,0,1,559,559],
"enemy_2034_sythef_2": [1,0,1,0,0]
},
"hooks": [1,130,5100,418,418,1082,1056,715,50,117,19,24,0,8423,461,10,102,68,1,198,2,0,37],
"events": {"atk":488,"bounty":2,"deploy":25,"die":49,"dmg":1043,"fx":203,"heal":6,"layer":2,"leak":68,"skill":38,"spawn":119,"status":268},
"fx": {"beam":22,"block":9,"catShield":2,"charge":1,"dilemma":2,"dodge":37,"explode":10,"explosion":2,"featherArrow":1,"flame":1,"frostNova":5,"garrison":2,"garrisonGrant":1,"overclock":1,"phase":2,"pull":1,"riposte":6,"rockslide":4,"shieldBreak":4,"slippery":1,"summon":11,"swordRain":52,"swordStorm":6,"telegraph":2,"thorns":5,"tornado":1,"tornadoPulse":5,"undying":7},
"snaps": "ea6f1d60 38c60373 51717f18 12c199a0 1a28c551 0c19cb20 8e3891bd bc3999c1 a5e65d07 ffd96d28 b34086a2 1b522168 50a7b105 251e9a58 890c4ee9 ffae92c8 38d069d5"
"hooks": [1,130,5100,428,428,1138,1119,1063,65,120,29,30,0,8699,498,10,102,59,1,200,2,0,36],
"events": {"atk":499,"bounty":2,"deploy":25,"die":61,"dmg":1106,"fx":217,"heal":8,"layer":2,"leak":59,"skill":60,"spawn":119,"status":299},
"fx": {"beam":22,"block":3,"catShield":2,"charge":1,"dilemma":2,"dodge":36,"explode":10,"explosion":2,"featherArrow":1,"flame":1,"frostNova":5,"garrison":2,"garrisonGrant":1,"overclock":1,"phase":2,"pull":1,"riposte":6,"rockslide":4,"shieldBreak":4,"slippery":1,"summon":11,"swordRain":65,"swordStorm":9,"telegraph":2,"thorns":5,"tornado":2,"tornadoPulse":9,"undying":7},
"snaps": "a11f2665 9baaf19a e920dc19 198d3a45 baa40e7a 3b94af70 106e002a fdd90efe e21ec604 fe43a631 8239580b d00386be b6d9a443 250fb743 0e44df17 4b9bacbc ac746ded"
},
"roster-011": {
"about": "pass 0 band 5: chess_char_4_23_a chess_char_4_01_b chess_char_4_22_a chess_char_4_04_b chess_char_4_07_b chess_char_4_09_b chess_char_4_21_a chess_char_4_24_a chess_char_4_25_a chess_char_4_26_a chess_char_4_02_b chess_char_4_06_b",
@@ -680,31 +680,31 @@
"round": 10,
"seed": 1517795683,
"waveId": "act1autochess_h03",
"time": 177.23,
"ticks": 5317,
"time": 174.73,
"ticks": 5242,
"reason": "cleared",
"killed": 97,
"total": 98,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 312,
"rngDraws": 278,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":97,"total":98,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":10,"dmg":998316,"bossDmg":0,"heal":28828,"deaths":13,"layerGains":{"preciShip":16}}
"p1": {"killed":97,"total":98,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":10,"dmg":997508,"bossDmg":0,"heal":27788,"deaths":12,"layerGains":{"preciShip":16}}
},
"units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,7,0,350,3513,0,14,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",5964,1,10,27,237809,14539,14399,53,34],
["p1",3,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,104,0,2798,7376,0,3,2],
["p1",4,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,97,9,61388,6301,1837,25,3],
["p1",5,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2031.22,1,123,5,53285,4323,0,40,3],
["p1",6,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,8,8,87473,11495,0,49,2],
["p1",7,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,9,0,5244,1740,0,47,0],
["p1",8,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,4,29,295323,1664,0,38,3],
["p1",9,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,5,0,25573,1926,0,51,20],
["p1",10,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,101,17,169905,3252,0,26,11],
["p1",11,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,91,2,59168,2838,0,22,2],
["p1",12,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,6,0,0,0,12592,30,1],
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,7,0,276,3513,0,11,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6069,1,10,21,192055,12064,12029,56,29],
["p1",3,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,104,0,2798,7356,0,3,2],
["p1",4,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,97,10,66884,6424,1960,27,3],
["p1",5,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",1681.22,1,123,5,49023,4673,0,36,3],
["p1",6,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,8,8,93767,11156,0,47,2],
["p1",7,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,9,0,4627,2645,0,43,1],
["p1",8,"op","chess_char_4_10_b",1,"uniequip_002_aroma",319.84,1,4,33,359780,1344,0,54,4],
["p1",9,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,5,1,21641,1926,0,46,18],
["p1",10,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,101,17,148925,3252,0,25,11],
["p1",11,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,91,2,57733,2838,0,22,2],
["p1",12,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,6,0,0,641,13799,35,1],
["p1",13,"token","token_10041_cathy_catsld",null,null,100,1,16,0,0,0,0,0,null],
["p1",14,"token","token_10041_cathy_catsld",null,null,100,1,17,0,0,0,0,0,null],
["-","~1","device","trap_1105_accrate",null,null,100,0,1,0,0,0,0,0,null],
@@ -720,53 +720,53 @@
"enemy_1183_mlasrt": [1,1,0,1417,7488],
"enemy_1207_sfji_2": [1,1,0,1923,16128],
"enemy_1208_msfji_2": [2,2,0,0,60],
"enemy_1267_nhpbr": [45,45,0,9874,233280],
"enemy_1267_nhpbr_2": [24,24,0,5274,152064],
"enemy_1267_nhpbr": [45,45,0,8681,233280],
"enemy_1267_nhpbr_2": [24,24,0,6514,152064],
"enemy_1270_nhstlk": [1,1,0,21,13824],
"enemy_1270_nhstlk_2": [15,15,0,12130,276480],
"enemy_1325_cbgpro_2": [1,1,0,0,5299],
"enemy_1425_lrcmra_2": [1,1,0,1498,28800],
"enemy_1525_blkswb": [1,0,1,16295,48796],
"enemy_2050_smsha": [1,1,0,7885,46080]
"enemy_1270_nhstlk_2": [15,15,0,11284,276480],
"enemy_1325_cbgpro_2": [1,1,0,15,5299],
"enemy_1425_lrcmra_2": [1,1,0,1199,28800],
"enemy_1525_blkswb": [1,0,1,17888,47988],
"enemy_2050_smsha": [1,1,0,6241,46080]
},
"hooks": [1,121,5317,542,542,1454,1651,1076,110,114,81,81,0,13555,1681,57,98,1,1,119,2,1,13],
"events": {"atk":823,"bounty":5,"deploy":20,"die":113,"dmg":1634,"fx":372,"heal":76,"layer":2,"leak":1,"skill":161,"spawn":115,"status":531},
"hooks": [1,121,5242,526,526,1444,1693,1144,109,113,76,75,0,13597,1632,56,98,1,1,119,2,1,11],
"events": {"atk":832,"bounty":5,"deploy":20,"die":112,"dmg":1676,"fx":378,"heal":78,"layer":2,"leak":1,"skill":152,"spawn":115,"status":479},
"fx": {
"beam": 5,
"blink": 2,
"block": 5,
"block": 2,
"burst": 1,
"displace": 27,
"dodge": 13,
"explode": 6,
"displace": 25,
"dodge": 11,
"explode": 5,
"explosion": 11,
"featherArrow": 2,
"flame": 3,
"frostNova": 20,
"frostNova": 18,
"garrison": 2,
"garrisonGrant": 1,
"healField": 1,
"levitate": 68,
"levitate": 83,
"overclock": 2,
"palsy": 1,
"palsy": 4,
"pull": 2,
"revive": 1,
"riposte": 9,
"rockslide": 34,
"rockslide": 29,
"shieldBreak": 17,
"slash": 20,
"slippery": 3,
"splash": 27,
"slash": 19,
"slippery": 4,
"splash": 29,
"summon": 1,
"swordStorm": 1,
"telegraph": 2,
"thorns": 1,
"thorns": 2,
"tornado": 3,
"tornadoPulse": 10,
"truesilver": 2,
"zone": 69
},
"snaps": "f45385f9 557bea05 721dc86f 9aeea534 50755b10 2b98e7dd 7a6d1c71 a27c6ab1 cbbf2fd8 c7422513 af6cd4c6 3bfd8fc5 764d243a b529af35 b0f4df5a 6b04d745 f09c8f4d 92383b5c"
"snaps": "c0395dfa 0bc3a88e 16ea4fc8 44910193 6b2d71a2 4c4f69c8 95b16219 8cea36a9 c1cf499b 2e4e30b2 5b44582c 697803ae 5af424df 15bc9baa 36e7dd7e 29ea8855 8bce8feb e3404557"
},
"roster-013": {
"about": "pass 0 band 5: chess_char_4_23_b chess_char_5_08_a chess_char_5_01_a chess_char_5_05_a chess_char_5_06_a chess_char_5_07_a chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b chess_char_5_02_a chess_char_5_03_a chess_char_5_10_a",
@@ -1329,50 +1329,50 @@
"round": 4,
"seed": 3256727035,
"waveId": "act1autochess_04",
"time": 102.4,
"ticks": 3072,
"time": 98.07,
"ticks": 2942,
"reason": "cleared",
"killed": 26,
"killed": 29,
"total": 29,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 142,
"rngDraws": 157,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":26,"total":29,"perfect":false,"leaks":3,"leaksUncounted":0,"coins":8,"dmg":232039,"bossDmg":0,"heal":3434,"deaths":4,"layerGains":{}}},
"players": {"p1":{"killed":29,"total":29,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":8,"dmg":238140,"bossDmg":0,"heal":3572,"deaths":3,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_1_02_b",0,"none",3297.3,1,5,1,3187,1475,641,17,1],
["p1",2,"op","chess_char_1_20_a",0,null,0,0,6,0,2746,2198,0,24,1],
["p1",1,"op","chess_char_1_02_b",0,"none",3229.86,1,5,1,1795,1562,779,14,1],
["p1",2,"op","chess_char_1_20_a",0,null,893.77,1,6,2,6656,1304,0,37,2],
["p1",3,"op","chess_char_1_04_b",0,"none",0,0,11,2,7053,2904,0,33,1],
["p1",4,"op","chess_char_1_08_b",0,"none",2414.98,1,44,1,11193,3115,0,38,4],
["p1",5,"op","chess_char_1_18_a",0,null,2530,1,15,0,20028,2233,2233,44,4],
["p1",6,"op","chess_char_1_19_a",0,null,0,0,9,3,7931,1482,0,31,0],
["p1",7,"op","chess_char_1_17_a",0,null,1139,1,7,0,9297,0,0,12,2],
["p1",8,"op","chess_char_1_01_b",0,"none",1341,1,10,4,19515,0,0,82,6],
["p1",9,"op","chess_char_1_03_b",0,"none",1311,1,12,4,68209,0,0,38,2],
["p1",10,"op","chess_char_1_06_b",0,"none",1297,1,14,3,33323,559,559,72,2],
["p1",11,"op","chess_char_1_07_b",1,"none",1527,1,4,5,23798,0,0,32,2],
["p1",12,"op","chess_char_1_09_b",0,"none",2085,1,8,3,25761,0,0,75,25],
["p1",4,"op","chess_char_1_08_b",0,"none",2414.98,1,44,1,11193,3115,0,38,3],
["p1",5,"op","chess_char_1_18_a",0,null,2530,1,15,0,20592,2233,2233,45,4],
["p1",6,"op","chess_char_1_19_a",0,null,0,0,9,3,7931,1482,0,31,1],
["p1",7,"op","chess_char_1_17_a",0,null,1139,1,7,0,11585,0,0,15,2],
["p1",8,"op","chess_char_1_01_b",0,"none",1341,1,10,4,16855,0,0,83,5],
["p1",9,"op","chess_char_1_03_b",0,"none",1311,1,12,5,69334,0,0,39,2],
["p1",10,"op","chess_char_1_06_b",0,"none",1297,1,14,3,34003,559,559,73,2],
["p1",11,"op","chess_char_1_07_b",1,"none",1527,1,4,4,29924,0,0,27,1],
["p1",12,"op","chess_char_1_09_b",0,"none",2085,1,8,4,21218,0,0,75,25],
["-","~1","device","trap_1105_accrate",null,null,100,0,1,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]
],
"enemies": {
"enemy_10043_sailor": [1,1,0,0,4950],
"enemy_10124_uashld_2": [1,0,1,1427,21583],
"enemy_10124_uashld_2": [1,1,0,1427,22500],
"enemy_1081_sotisd": [1,1,0,1110,13500],
"enemy_1116_liprr": [12,11,1,163,56744],
"enemy_1116_liprr_2": [6,6,0,417,40500],
"enemy_1121_lifbos": [3,3,0,5256,54000],
"enemy_1174_duholy": [1,1,0,1912,13500],
"enemy_1116_liprr": [12,12,0,384,59400],
"enemy_1116_liprr_2": [6,6,0,307,40500],
"enemy_1121_lifbos": [3,3,0,4543,54000],
"enemy_1174_duholy": [1,1,0,1708,13500],
"enemy_1229_darmy_2": [1,1,0,2835,4950],
"enemy_1325_cbgpro_2": [1,1,0,55,4140],
"enemy_1504_cqbw": [1,1,0,791,9000],
"enemy_2085_skzjxd_2": [1,0,1,0,9172]
"enemy_2085_skzjxd_2": [1,1,0,0,11700]
},
"hooks": [1,45,3072,596,596,709,717,790,30,34,50,48,37,10199,109,27,29,3,1,30,0,0,0],
"events": {"atk":669,"bounty":4,"deploy":13,"die":31,"dmg":709,"fx":41,"heal":32,"leak":3,"skill":97,"spawn":44,"status":252},
"fx": {"crit":27,"dp":4,"explode":1,"liberate":5,"shield":2,"sp":1,"telegraph":1},
"snaps": "b6d13f59 cd9b53d1 d9124384 d817a5a5 4e96daff 80f99699 215cf8ec 696ab312 99ec6290 7e58dd9a 639f8594"
"hooks": [1,45,2942,615,615,727,734,928,32,33,49,47,32,9649,110,27,29,0,1,32,0,0,0],
"events": {"atk":688,"bounty":4,"deploy":13,"die":33,"dmg":726,"fx":41,"heal":32,"skill":96,"spawn":44,"status":256},
"fx": {"crit":26,"dp":3,"explode":1,"liberate":6,"shield":4,"telegraph":1},
"snaps": "08475251 060e1404 a77d3fa6 74ab94fb 39ec40a8 4a899aaf 8a49b1ba b7ea4461 d110804c df9fa85e"
},
"roster-022": {
"about": "pass 1 band 2: chess_char_1_10_b chess_char_1_12_b chess_char_1_20_b chess_char_1_18_b chess_char_1_19_b chess_char_1_13_b chess_char_1_14_b chess_char_1_17_b chess_char_2_01_a chess_char_2_02_a chess_char_2_04_a chess_char_2_05_a",
@@ -1392,15 +1392,15 @@
"bossHpLeft": null,
"rngDraws": 158,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":16,"total":28,"perfect":false,"leaks":12,"leaksUncounted":0,"coins":4,"dmg":202546,"bossDmg":0,"heal":19896,"deaths":6,"layerGains":{}}},
"players": {"p1":{"killed":16,"total":28,"perfect":false,"leaks":12,"leaksUncounted":0,"coins":4,"dmg":203302,"bossDmg":0,"heal":19896,"deaths":6,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_1_10_b",1,"none",0,0,7,1,6370,8454,1895,21,2],
["p1",2,"op","chess_char_1_12_b",0,"none",0,0,8,2,26053,5955,714,17,1],
["p1",2,"op","chess_char_1_12_b",0,"none",0,0,8,3,27258,5955,714,18,1],
["p1",3,"op","chess_char_1_20_b",0,"none",2872,1,6,1,11594,10327,0,24,8],
["p1",4,"op","chess_char_1_18_b",0,"none",0,0,9,0,4940,7421,2877,6,2],
["p1",5,"op","chess_char_1_19_b",0,"none",0,0,10,2,19917,8644,0,41,0],
["p1",5,"op","chess_char_1_19_b",0,"none",0,0,10,2,20108,8644,0,42,1],
["p1",6,"op","chess_char_1_13_b",0,"none",1095,1,3,0,17174,0,3175,49,2],
["p1",7,"op","chess_char_1_14_b",0,"none",1430,1,4,7,77822,500,0,34,2],
["p1",7,"op","chess_char_1_14_b",0,"none",1430,1,4,6,77183,500,0,34,2],
["p1",8,"op","chess_char_1_17_b",0,"none",1356,1,12,1,18041,1000,0,7,3],
["p1",9,"op","chess_char_2_01_a",0,null,677.6,1,35,0,5908,678,0,9,0],
["p1",10,"op","chess_char_2_02_a",0,null,1400,1,5,0,0,1000,8195,45,2],
@@ -1414,17 +1414,17 @@
"enemy_1025_reveng": [1,1,0,556,8640],
"enemy_1116_liprr": [12,9,3,727,95134],
"enemy_1116_liprr_2": [6,4,2,1413,56359],
"enemy_1121_lifbos": [3,0,2,1910,16634],
"enemy_1121_lifbos": [3,0,2,1910,16825],
"enemy_1160_hvyslr": [1,1,0,2131,17280],
"enemy_1200_msfjin": [1,0,1,0,21],
"enemy_1272_nhtank": [1,0,1,16493,13676],
"enemy_1272_nhtank": [1,0,1,16493,14242],
"enemy_1404_msnip": [1,0,1,6948,5875],
"enemy_2002_bearmi": [1,0,1,9048,2918]
},
"hooks": [1,43,3300,361,361,886,878,767,22,33,30,31,0,12925,97,30,28,11,1,22,0,0,0],
"events": {"atk":379,"bounty":2,"deploy":13,"die":22,"dmg":878,"fx":26,"heal":463,"leak":11,"skill":60,"spawn":42,"status":202},
"hooks": [1,43,3300,363,363,888,880,767,22,33,31,31,0,12925,98,30,28,11,1,22,0,0,0],
"events": {"atk":382,"bounty":2,"deploy":13,"die":22,"dmg":880,"fx":26,"heal":463,"leak":11,"skill":62,"spawn":42,"status":202},
"fx": {"buff":1,"crit":1,"liberate":4,"shield":16,"zone":4},
"snaps": "bb47df39 8263a762 58d26e3b 0148770c 25917da5 9db56ade 1c2ec6b6 b898c8a7 895e43ef 453d5c2d a8806843"
"snaps": "9001a773 d682bb23 8fbcd651 5df53986 07e3ac0f 0f7aa6b9 5acdf417 61e10070 400a77ca 652541a4 8bc87622"
},
"roster-023": {
"about": "pass 1 band 2: chess_char_2_08_a chess_char_2_17_a chess_char_2_18_a chess_char_2_07_a chess_char_2_09_a chess_char_2_11_a chess_char_2_13_a chess_char_2_16_a chess_char_2_12_a chess_char_2_06_a chess_char_2_10_a chess_char_2_14_a",
@@ -1454,7 +1454,7 @@
["p1",6,"op","chess_char_2_11_a",0,null,2152.73,1,8,0,1692,704,0,6,1],
["p1",7,"op","chess_char_2_13_a",0,null,2472.85,1,9,0,253,0,0,12,1],
["p1",8,"op","chess_char_2_16_a",0,null,2453.67,1,6,0,4,0,0,4,0],
["p1",9,"op","chess_char_2_12_a",0,null,1516.59,1,10,0,2,0,0,2,0],
["p1",9,"op","chess_char_2_12_a",0,null,1516.59,1,10,0,2,0,0,2,1],
["p1",10,"op","chess_char_2_06_a",1,null,1972.8,1,4,0,0,0,17292,35,2],
["p1",11,"op","chess_char_2_10_a",0,null,1476.86,1,5,0,4354,0,0,39,1],
["p1",12,"op","chess_char_2_14_a",0,null,1550.43,1,13,0,0,1085,12137,19,2],
@@ -1476,10 +1476,10 @@
"enemy_1439_dslntf_2": [1,0,0,2668,5256],
"enemy_2048_smgrd": [1,0,0,8461,2244]
},
"hooks": [1,52,3300,419,419,917,1086,360,32,32,19,16,0,11570,67,46,36,0,1,32,0,4,0],
"events": {"atk":590,"bounty":3,"deploy":13,"die":32,"dmg":969,"fx":58,"heal":342,"skill":35,"spawn":51,"status":290},
"hooks": [1,52,3300,419,419,917,1086,360,32,32,20,17,0,11570,67,46,36,0,1,32,0,4,0],
"events": {"atk":590,"bounty":3,"deploy":13,"die":32,"dmg":969,"fx":58,"heal":342,"skill":37,"spawn":51,"status":290},
"fx": {"beam":44,"buff":3,"burst":4,"explode":1,"summon":1,"takeoff":1,"telegraph":4},
"snaps": "79233706 fbbde954 585efdc3 c8692d0d f9f87605 85686e81 abc154b8 8d43dd1f 41c73952 55c49dad 32067d73"
"snaps": "dd24c30b 998fef35 660771ba c69dbe74 3ffd6844 5f694b3c aac3f1c1 1533a52a ac7cca8b ccd35e50 5b498d3e"
},
"roster-024": {
"about": "pass 1 band 3: chess_char_2_07_b chess_char_2_08_b chess_char_2_09_b chess_char_2_11_b chess_char_2_12_b chess_char_2_19_a chess_char_2_01_b chess_char_2_02_b chess_char_2_04_b chess_char_2_05_b chess_char_2_06_b chess_char_2_10_b",
@@ -1505,7 +1505,7 @@
["p1",2,"op","chess_char_2_08_b",0,"none",3193,1,1,0,0,0,0,0,0],
["p1",3,"op","chess_char_2_09_b",0,"none",0,0,4,1,1,2079,46,1,0],
["p1",4,"op","chess_char_2_11_b",0,"none",2214,1,7,13,4490,2214,0,31,10],
["p1",5,"op","chess_char_2_12_b",0,"none",1352,1,10,19,23053,2259,0,61,0],
["p1",5,"op","chess_char_2_12_b",0,"none",1352,1,10,19,23053,2259,0,61,1],
["p1",6,"op","chess_char_2_19_a",0,null,1021.2,1,5,46,33391,0,0,72,10],
["p1",7,"op","chess_char_2_01_b",0,"none",2146,1,8,15,3240,0,0,19,2],
["p1",8,"op","chess_char_2_02_b",0,"none",2624.8,1,6,0,0,264,1103,9,1],
@@ -1530,10 +1530,10 @@
"enemy_1381_winman_2": [1,1,0,30,7488],
"enemy_1535_wlfmster": [1,0,1,8843,18291]
},
"hooks": [1,200,4200,794,794,1045,1112,2413,152,187,67,65,0,13905,1117,53,188,35,1,161,0,0,0],
"events": {"atk":928,"bounty":3,"deploy":12,"die":152,"dmg":1066,"fx":77,"heal":172,"leak":35,"skill":132,"spawn":200,"status":544},
"hooks": [1,200,4200,794,794,1045,1112,2413,152,187,68,66,0,13905,1117,53,188,35,1,161,0,0,0],
"events": {"atk":928,"bounty":3,"deploy":12,"die":152,"dmg":1066,"fx":77,"heal":172,"leak":35,"skill":134,"spawn":200,"status":544},
"fx": {"beam":3,"buff":5,"liberate":1,"summon":56,"undying":1,"zone":11},
"snaps": "eb7b8332 9d8684f1 b0364b3a 29231b85 09c98374 73eb2fd3 0a28ed5a 161be0bb a65e07dd 07fb1d04 767a2957 60b181b9 26307eb0 2624bf52"
"snaps": "07ba544c 84f4ccbf 38166938 b925fca7 945f4b86 2aa6642d 96e01f04 a3185a51 b77953b3 fa7afb72 6d5be7ad a030c96f 78dbd4b6 8d3af5e8"
},
"roster-025": {
"about": "pass 1 band 3: chess_char_2_17_b chess_char_2_18_b chess_char_3_06_a chess_char_2_13_b chess_char_2_16_b chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_3_05_a chess_char_2_14_b chess_char_2_19_b chess_char_3_01_a",
@@ -1587,7 +1587,7 @@
"enemy_2021_syfish": [1,1,0,0,4608]
},
"hooks": [1,81,4200,610,610,981,984,390,67,67,98,95,0,5824,750,71,66,0,1,124,1,0,0],
"events": {"atk":626,"bounty":3,"deploy":12,"die":67,"dmg":984,"fx":172,"heal":331,"layer":1,"skill":193,"spawn":81,"status":233},
"events": {"atk":626,"bounty":3,"deploy":12,"die":67,"dmg":984,"fx":172,"heal":331,"layer":1,"skill":195,"spawn":81,"status":233},
"fx": {"block":1,"buff":1,"coin":2,"ember":57,"garrison":1,"sp":71,"summon":1,"takeoff":3,"zone":35},
"snaps": "a16b87b8 7f2721ce a438ac49 e52df11e 882d7026 bc1e5811 1dfd72d4 90a6dd65 80ab29f4 1b3f315f c5e5d061 a8e8097d ffc02f6e 481fc792"
},
@@ -1660,14 +1660,14 @@
"time": 170,
"ticks": 5100,
"reason": "timeout",
"killed": 27,
"killed": 28,
"total": 53,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 136,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":27,"total":53,"perfect":false,"leaks":26,"leaksUncounted":0,"coins":4,"dmg":321187,"bossDmg":0,"heal":17637,"deaths":10,"layerGains":{"raidShip":18}}
"p1": {"killed":28,"total":53,"perfect":false,"leaks":25,"leaksUncounted":0,"coins":4,"dmg":368915,"bossDmg":0,"heal":17637,"deaths":10,"layerGains":{"raidShip":18}}
},
"units": [
["p1",1,"op","chess_char_3_06_b",0,"none",0,0,2,0,1546,6162,0,9,1],
@@ -1678,9 +1678,9 @@
["p1",6,"op","chess_char_3_05_b",0,"uniequip_003_skadi",0,0,12,2,26200,12108,0,25,1],
["p1",7,"op","chess_char_3_21_a",0,null,759.08,1,10,0,8795,605,0,35,14],
["p1",8,"op","chess_char_3_01_b",0,"uniequip_003_angel",211.23,1,11,1,38052,1505,0,75,32],
["p1",9,"op","chess_char_3_08_b",0,"none",0,0,1,4,33840,3248,0,10,2],
["p1",9,"op","chess_char_3_08_b",0,"none",0,0,1,5,81344,3248,0,19,3],
["p1",10,"op","chess_char_3_09_b",0,"none",0,0,3,2,11671,1534,0,14,4],
["p1",11,"op","chess_char_3_10_b",0,"none",0,0,5,2,49060,2120,0,23,7],
["p1",11,"op","chess_char_3_10_b",0,"none",0,0,5,2,49283,2120,0,23,7],
["p1",12,"op","chess_char_3_11_b",0,"none",0,0,7,6,52214,1610,0,19,10]
],
"enemies": {
@@ -1689,17 +1689,17 @@
"enemy_1042_frostd": [1,1,0,0,7020],
"enemy_1165_duhond": [1,1,0,76,3024],
"enemy_1203_sfhu_2": [1,0,1,7943,2437],
"enemy_1267_nhpbr_2": [24,14,8,11740,97532],
"enemy_1270_nhstlk_2": [9,4,5,10826,94005],
"enemy_1267_nhpbr_2": [24,15,7,11719,115399],
"enemy_1270_nhstlk_2": [9,4,5,10826,103834],
"enemy_1283_sgkill_2": [1,0,1,10291,7305],
"enemy_1425_lrcmra": [1,0,1,1414,13093],
"enemy_2008_flking": [1,0,1,8107,3573],
"enemy_9006_actoxi": [12,5,5,5143,115747]
"enemy_2008_flking": [1,0,1,8128,4073],
"enemy_9006_actoxi": [12,5,5,5143,135278]
},
"hooks": [1,67,5100,531,531,1890,1890,30,37,61,107,109,0,6763,40,30,53,22,1,39,3,0,0],
"events": {"atk":703,"bounty":2,"deploy":14,"die":39,"dmg":1818,"fx":107,"heal":30,"layer":3,"leak":22,"skill":214,"spawn":65,"status":187},
"fx": {"blink":1,"buff":1,"coin":18,"explode":26,"garrison":3,"revive":2,"shield":19,"shieldBreak":1,"strike":10,"volley":7,"zone":19},
"snaps": "757da7ba 23c3e301 44393780 f617c160 eeedb2b3 6f6317df 3ad26b55 5bd556aa 9c8c490d 4f3f479d 5a8c9a94 a5d5a70b d2bb0c6d 6cd3e449 f56e1feb c875393e 9def5afe"
"hooks": [1,67,5100,540,540,1934,1934,30,38,61,108,110,0,7239,40,30,53,21,1,40,3,0,0],
"events": {"atk":747,"bounty":2,"deploy":14,"die":40,"dmg":1863,"fx":108,"heal":30,"layer":3,"leak":21,"skill":220,"spawn":65,"status":187},
"fx": {"blink":1,"buff":1,"coin":18,"explode":26,"garrison":3,"revive":2,"shield":19,"shieldBreak":1,"strike":10,"volley":7,"zone":20},
"snaps": "757da7ba 23c3e301 44393780 f617c160 eeedb2b3 6f6317df 3ad26b55 5bd556aa a29f3948 771a5093 f9fd3ae6 9b2e6d4b 873809bc 8d2b5f1a 506cb112 f0f0a521 c25d712d"
},
"roster-028": {
"about": "pass 1 band 4: chess_char_3_16_b chess_char_4_01_a chess_char_3_18_b chess_char_4_04_a chess_char_3_13_b chess_char_3_14_b chess_char_3_17_b chess_char_3_19_b chess_char_3_20_b chess_char_3_21_b chess_char_4_02_a chess_char_4_06_a token_10028_vigil_wolf",
@@ -1719,39 +1719,39 @@
"bossHpLeft": null,
"rngDraws": 84,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":3,"total":62,"perfect":false,"leaks":59,"leaksUncounted":0,"coins":0,"dmg":153737,"bossDmg":0,"heal":105,"deaths":26,"layerGains":{}}},
"players": {"p1":{"killed":3,"total":62,"perfect":false,"leaks":59,"leaksUncounted":0,"coins":0,"dmg":155338,"bossDmg":0,"heal":105,"deaths":26,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_3_16_b",0,"none",0,0,4,0,70,4069,0,7,1],
["p1",2,"op","chess_char_4_01_a",0,null,0,0,7,0,0,3614,0,0,0],
["p1",3,"op","chess_char_3_18_b",0,"uniequip_003_vulpis",0,0,10,0,0,2743,105,0,0],
["p1",4,"op","chess_char_4_04_a",0,null,0,0,90,0,13678,21701,0,34,4],
["p1",5,"op","chess_char_3_13_b",0,"none",0,0,1,0,15809,1802,0,35,1],
["p1",6,"op","chess_char_3_14_b",0,"none",0,0,87,1,23224,4151,0,27,1],
["p1",6,"op","chess_char_3_14_b",0,"none",0,0,87,1,24314,4151,0,28,1],
["p1",7,"op","chess_char_3_17_b",0,"none",0,0,81,1,31808,7884,0,21,7],
["p1",8,"op","chess_char_3_19_b",0,"uniequip_003_vigil",0,0,2,1,24320,2429,0,34,4],
["p1",9,"op","chess_char_3_20_b",0,"none",0,0,9,0,4159,1897,0,11,1],
["p1",9,"op","chess_char_3_20_b",0,"none",0,0,9,0,4671,1897,0,12,1],
["p1",10,"op","chess_char_3_21_b",0,"uniequip_003_archet",0,0,60,0,17805,4603,0,22,8],
["p1",11,"op","chess_char_4_02_a",0,null,0,0,65,0,9310,5869,0,7,0],
["p1",12,"op","chess_char_4_06_a",0,null,0,0,73,0,8278,3414,0,17,1],
["p1",13,"token","token_10028_vigil_wolf",null,null,0,0,3,0,5276,3473,0,12,null]
],
"enemies": {
"enemy_10044_wintun": [1,0,1,6689,14921],
"enemy_10044_wintun": [1,0,1,6689,15432],
"enemy_10127_rkmbst_2": [1,0,1,1878,0],
"enemy_1019_jshoot_2": [18,0,18,7782,16859],
"enemy_1092_mdgint": [1,0,1,0,374],
"enemy_1019_jshoot_2": [18,0,18,7782,17842],
"enemy_1092_mdgint": [1,0,1,0,748],
"enemy_1174_duholy_2": [1,0,1,1445,892],
"enemy_1234_dsubrl": [1,0,1,4496,214],
"enemy_1328_cbjedi": [1,0,1,0,267],
"enemy_1328_cbjedi": [1,0,1,0,0],
"enemy_1381_winman": [21,3,18,7653,104478],
"enemy_1389_winbab_2": [15,0,12,35466,15020],
"enemy_1509_mousek": [1,0,1,1686,0],
"enemy_9006_actoxi": [1,0,1,554,712]
},
"hooks": [1,89,5100,399,399,503,519,30,30,86,28,28,0,4426,16,26,62,56,1,33,0,0,18],
"events": {"atk":424,"deploy":27,"die":30,"dmg":495,"fx":84,"leak":56,"skill":56,"spawn":75,"status":77},
"hooks": [1,89,5100,401,401,506,522,30,30,86,28,28,0,4426,16,26,62,56,1,33,0,0,18],
"events": {"atk":427,"deploy":27,"die":30,"dmg":498,"fx":84,"leak":56,"skill":56,"spawn":75,"status":77},
"fx": {"camouflage":1,"crit":6,"dodge":18,"dp":4,"phase":1,"pulse":17,"sentry":9,"shadowWeave":9,"shield":4,"shieldBreak":2,"summon":2,"telegraph":1,"volley":6,"wolfShadowLost":3,"zone":1},
"snaps": "7c092213 c094fdd0 61afa7ba 7ab796f5 0793fdee 51faaed8 4a46eeb8 4da2e9b0 57cbf8ad d370e410 155d413f 6fdd87fd 956855f7 70823bab 31442bc2 8416fed2 6ac1b28d"
"snaps": "7c092213 7eeed0b1 ac8f2ed3 3858c149 e8ea91a7 8453351a 4fc7b210 32f63df8 4e74f555 5bfbe308 7746a517 c9e84d55 480b39df 046d3213 b82f6b7a f22d156a 686086b5"
},
"roster-029": {
"about": "pass 1 band 4: chess_char_4_17_a chess_char_4_18_a chess_char_4_11_a chess_char_4_12_a chess_char_4_19_a chess_char_4_07_a chess_char_4_16_a chess_char_4_09_a chess_char_4_10_a chess_char_4_13_a chess_char_4_14_a chess_char_4_20_a token_10041_cathy_catsld token_10041_cathy_catsld",
@@ -1781,7 +1781,7 @@
["p1",4,"op","chess_char_4_12_a",0,null,1754,1,104,3,8643,2902,398,27,9],
["p1",5,"op","chess_char_4_19_a",0,null,1560,1,105,4,8607,3442,0,39,4],
["p1",6,"op","chess_char_4_07_a",0,null,1738,1,89,2,13301,2526,0,35,2],
["p1",7,"op","chess_char_4_16_a",0,null,257.6,1,63,20,55897,11230,0,97,0],
["p1",7,"op","chess_char_4_16_a",0,null,257.6,1,63,20,55897,11230,0,97,5],
["p1",8,"op","chess_char_4_09_a",0,null,386,1,3,7,25390,1000,0,12,9],
["p1",9,"op","chess_char_4_10_a",0,null,0,0,5,0,0,1338,0,0,0],
["p1",10,"op","chess_char_4_13_a",0,null,0,0,7,8,30420,2137,0,64,19],
@@ -1804,10 +1804,10 @@
"enemy_1439_dslntf": [6,3,3,7340,112566],
"enemy_2002_bearmi_2": [1,0,1,2252,5593]
},
"hooks": [1,109,4764,670,670,1086,1879,759,88,95,50,51,0,22092,730,68,85,7,1,88,1,10,8],
"events": {"atk":685,"bounty":2,"deploy":22,"die":88,"dmg":1865,"fx":73,"heal":477,"layer":1,"leak":7,"skill":98,"spawn":101,"status":242},
"hooks": [1,109,4764,670,670,1086,1879,759,88,95,55,54,0,22092,730,68,85,7,1,88,1,10,8],
"events": {"atk":685,"bounty":2,"deploy":22,"die":88,"dmg":1865,"fx":73,"heal":477,"layer":1,"leak":7,"skill":112,"spawn":101,"status":236},
"fx": {"block":10,"burst":10,"displace":3,"dodge":8,"dp":4,"explode":1,"expose":2,"garrison":1,"garrisonGrant":1,"phase":1,"riposte":8,"shieldBreak":14,"swordRain":5,"zone":5},
"snaps": "1a08290d 2035038b da4cec2a 484320a2 63b0f0f1 d6892173 e8fd3d3f c4a1af3f 48af4c4a d9965b74 64975bb5 9962fa24 d35e7765 32c10030 0c39ce51 dfa8b0ed"
"snaps": "5ee1d302 1c02fbcd cbb518f8 fc90ae00 98195e0a 21da2468 b9aa38e0 db0862b7 a4015752 c3d0537c 5e0bdc1d b3dd31dc 595b167d 137aa308 f30e8d89 932e6025"
},
"roster-030": {
"about": "pass 1 band 5: chess_char_4_23_a chess_char_4_01_b chess_char_4_22_a chess_char_4_04_b chess_char_4_07_b chess_char_4_09_b chess_char_4_21_a chess_char_4_24_a chess_char_4_25_a chess_char_4_26_a chess_char_4_02_b chess_char_4_06_b",
@@ -1880,20 +1880,20 @@
"rngDraws": 138,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":63,"total":76,"perfect":false,"leaks":13,"leaksUncounted":0,"coins":6,"dmg":819119,"bossDmg":0,"heal":52143,"deaths":5,"layerGains":{"preciShip":8}}
"p1": {"killed":63,"total":76,"perfect":false,"leaks":13,"leaksUncounted":0,"coins":6,"dmg":819119,"bossDmg":0,"heal":51618,"deaths":5,"layerGains":{"preciShip":8}}
},
"units": [
["p1",1,"op","chess_char_4_17_b",0,"uniequip_003_hsguma",2421.63,1,98,2,6160,4854,0,15,3],
["p1",2,"op","chess_char_4_18_b",0,"uniequip_003_mudrok",5248.66,1,5,4,124530,7693,7230,85,3],
["p1",3,"op","chess_char_4_11_b",0,"none",2544,1,94,3,11375,6927,0,47,0],
["p1",2,"op","chess_char_4_18_b",0,"uniequip_003_mudrok",5248.66,1,5,4,126783,7169,6705,87,3],
["p1",3,"op","chess_char_4_11_b",0,"none",2544,1,94,2,11067,6927,0,45,0],
["p1",4,"op","chess_char_4_19_b",0,"uniequip_003_flamtl",1943,1,89,5,82926,7434,0,81,11],
["p1",5,"op","chess_char_4_22_b",0,"none",2379,1,12,9,121507,11711,0,123,41],
["p1",6,"op","chess_char_4_16_b",0,"uniequip_003_texas2",1609,1,14,10,109108,23327,0,168,0],
["p1",6,"op","chess_char_4_16_b",0,"uniequip_003_texas2",1609,1,14,10,109108,23327,0,168,2],
["p1",7,"op","chess_char_4_10_b",0,"none",1514,1,8,0,0,0,0,0,0],
["p1",8,"op","chess_char_4_12_b",0,"uniequip_003_glady",4456.2,1,9,1,57432,0,0,30,26],
["p1",9,"op","chess_char_4_13_b",0,"uniequip_003_gnosis",1974,1,13,14,128749,0,0,101,101],
["p1",10,"op","chess_char_4_14_b",0,"none",0,0,76,0,11040,3092,0,6,1],
["p1",11,"op","chess_char_4_20_b",0,"uniequip_003_fartth",1499,1,6,14,166293,0,0,84,4],
["p1",10,"op","chess_char_4_14_b",0,"none",0,0,76,0,9689,3092,0,6,1],
["p1",11,"op","chess_char_4_20_b",0,"uniequip_003_fartth",1499,1,6,15,165700,0,0,84,4],
["p1",12,"op","chess_char_4_21_b",0,"none",1524,1,10,0,0,0,44913,62,4],
["p1",13,"token","token_10041_cathy_catsld",null,null,100,1,16,0,0,0,0,0,null],
["p1",14,"token","token_10041_cathy_catsld",null,null,100,1,15,0,0,0,0,0,null],
@@ -1907,15 +1907,15 @@
"enemy_1148_dssbr": [1,1,0,16,4645],
"enemy_1198_msfshu": [1,1,0,0,3],
"enemy_1269_nhfly": [1,1,0,533,0],
"enemy_1320_wdrrl_2": [6,4,2,19074,173001],
"enemy_1320_wdrrl_2": [6,4,2,18563,173001],
"enemy_1387_winshd": [1,1,0,3282,19907],
"enemy_1422_lrsldr_2": [42,36,6,2177,360958],
"enemy_1422_lrsldr_2": [42,36,6,2163,360958],
"enemy_1539_reid": [1,0,1,7794,22227]
},
"hooks": [1,96,5384,1059,1059,1146,1132,145,68,81,194,191,0,15988,2319,61,76,13,1,74,1,0,10],
"events": {"atk":1131,"bounty":3,"deploy":18,"die":68,"dmg":1114,"fx":87,"heal":138,"layer":1,"leak":13,"skill":384,"spawn":92,"status":425},
"hooks": [1,96,5384,1057,1057,1143,1129,145,68,81,196,193,0,16010,2319,60,76,13,1,74,1,0,10],
"events": {"atk":1129,"bounty":3,"deploy":18,"die":68,"dmg":1111,"fx":87,"heal":137,"layer":1,"leak":13,"skill":392,"spawn":92,"status":423},
"fx": {"block":5,"charge":4,"displace":19,"dodge":10,"dp":11,"explode":5,"garrison":1,"garrisonGrant":1,"riposte":10,"shieldBreak":18,"swordRain":2,"undying":1},
"snaps": "13b54bca ae775c4a d3614271 0d03455c 9ca29c10 83783b14 6d6b4407 c2174cd9 6f926bed d6b6e996 efdb27b6 0314a0c9 36dc41d1 7533c479 f5571118 e7c68fef ba7a0e82 5e962c21"
"snaps": "70957ea7 12980bbe eb40080d c6a9d8b0 9fbf288c ce758920 f04d0223 eeb062ad 767c47e0 9f0398d9 f2c0ec15 dda8a114 61112f0b 78f7d894 0bcdbfa3 2ea72ee7 7c313f29 5ee2d05a"
},
"roster-032": {
"about": "pass 1 band 5: chess_char_4_23_b chess_char_5_08_a chess_char_5_01_a chess_char_5_05_a chess_char_5_06_a chess_char_5_07_a chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b chess_char_5_02_a chess_char_5_03_a chess_char_5_10_a",
@@ -2383,9 +2383,9 @@
["p1",3,"op","chess_char_3_18_a",1,null,1481,1,10,0,5796,0,0,0,7],
["p1",4,"op","chess_char_3_18_b",1,"none",1845,1,60,3,61231,4973,0,130,9],
["p1",5,"op","chess_char_3_04_a",2,null,1864,1,9,5,30544,308,0,102,3],
["p1",6,"op","chess_char_3_05_a",1,null,5876,1,11,0,20218,5530,0,84,0],
["p1",6,"op","chess_char_3_05_a",1,null,5876,1,11,0,20218,5530,0,84,1],
["p1",7,"op","chess_char_3_04_b",2,"uniequip_004_swire2",2588.3,1,74,8,61422,352,301,62,4],
["p1",8,"op","chess_char_3_05_b",1,"none",6786.23,1,34,3,38798,723,0,33,0],
["p1",8,"op","chess_char_3_05_b",1,"none",6786.23,1,34,3,38798,723,0,33,2],
["p1",9,"op","chess_char_3_01_a",1,null,1471.8,1,1,1,3517,0,0,36,1],
["p1",10,"op","chess_char_3_19_a",1,null,1439,1,4,3,34773,0,0,114,12],
["p1",11,"op","chess_char_3_21_a",1,null,1364,1,7,11,62130,0,0,197,19],
@@ -2405,10 +2405,10 @@
"enemy_1438_dspred": [1,1,0,1030,14256],
"enemy_2034_sythef": [1,1,0,139,8640]
},
"hooks": [1,73,5100,1445,1445,1980,1980,2325,48,54,73,71,0,16134,93,54,53,0,1,50,8,0,0],
"events": {"atk":1445,"bounty":3,"deploy":20,"die":54,"dmg":1980,"fx":176,"heal":188,"layer":8,"skill":143,"spawn":66,"status":153},
"hooks": [1,73,5100,1445,1445,1980,1980,2325,48,54,76,73,0,16134,93,54,53,0,1,50,8,0,0],
"events": {"atk":1445,"bounty":3,"deploy":20,"die":54,"dmg":1980,"fx":176,"heal":188,"layer":8,"skill":149,"spawn":66,"status":147},
"fx": {"aoe":32,"blink":3,"bondMilestone":1,"buff":14,"coin":63,"displace":6,"dp":28,"garrison":8,"shield":1,"volley":19,"wolfShadow":1},
"snaps": "6d9db527 8766db85 954b6eb0 2cef17b3 647c06e2 1d9e5e43 8f86fa75 7716b2ca d33a9b65 47ffc983 db69a403 7973fbaf 6ff6f5f8 32a51549 72571943 2948a3dd 3bbdee6f"
"snaps": "7039516f 1f992cb5 0f5e59af c4b1bbc5 07a0826f 56c348a1 323da105 91eb067a 068e4335 66187a93 1732df53 b1fbe31f f69a53e8 250909f9 25729cd3 5c78fdcd 2b29dd1f"
},
"roster-040": {
"about": "pass 2 band 4: chess_char_4_01_a chess_char_4_17_a chess_char_4_18_a chess_char_4_12_a chess_char_4_04_a chess_char_4_07_a chess_char_4_16_a chess_char_4_09_a chess_char_3_19_b chess_char_3_21_b chess_char_4_02_a chess_char_4_13_a token_10028_vigil_wolf",
@@ -2422,23 +2422,23 @@
"time": 170,
"ticks": 5100,
"reason": "timeout",
"killed": 21,
"killed": 23,
"total": 65,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 231,
"rngDraws": 243,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":21,"total":65,"perfect":false,"leaks":44,"leaksUncounted":0,"coins":2,"dmg":393784,"bossDmg":0,"heal":502,"deaths":25,"layerGains":{}}},
"players": {"p1":{"killed":23,"total":65,"perfect":false,"leaks":42,"leaksUncounted":0,"coins":2,"dmg":401720,"bossDmg":0,"heal":502,"deaths":25,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_4_01_a",1,null,0,0,4,0,3208,2638,0,28,1],
["p1",1,"op","chess_char_4_01_a",1,null,0,0,4,0,3410,2638,0,29,1],
["p1",2,"op","chess_char_4_17_a",2,null,0,0,7,0,13400,2849,0,10,1],
["p1",3,"op","chess_char_4_18_a",2,null,0,0,9,0,12723,2867,0,11,1],
["p1",4,"op","chess_char_4_12_a",1,null,0,0,87,1,22797,3856,348,27,1],
["p1",5,"op","chess_char_4_04_a",2,null,1260.13,1,96,2,10615,7540,0,12,0],
["p1",5,"op","chess_char_4_04_a",2,null,1260.13,1,96,2,10615,7540,0,12,5],
["p1",6,"op","chess_char_4_07_a",2,null,0,0,77,0,9369,7998,0,23,1],
["p1",7,"op","chess_char_4_16_a",1,null,0,0,94,2,53656,7476,0,11,0],
["p1",7,"op","chess_char_4_16_a",1,null,0,0,94,2,53656,7476,0,11,7],
["p1",8,"op","chess_char_4_09_a",2,null,0,0,3,0,0,1336,0,0,0],
["p1",9,"op","chess_char_3_19_b",1,"none",0,0,5,3,43991,1648,0,54,4],
["p1",9,"op","chess_char_3_19_b",1,"none",0,0,5,5,51724,1648,0,66,4],
["p1",10,"op","chess_char_3_21_b",1,"uniequip_004_archet",0,0,95,8,151296,3180,0,81,9],
["p1",11,"op","chess_char_4_02_a",1,null,0,0,84,1,20900,2856,0,8,1],
["p1",12,"op","chess_char_4_13_a",2,null,0,0,79,4,51831,3174,0,54,1],
@@ -2447,22 +2447,22 @@
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
],
"enemies": {
"enemy_1005_yokai": [24,10,14,0,40379],
"enemy_1005_yokai_2": [27,7,16,8895,76423],
"enemy_1005_yokai_3": [6,1,5,17836,168377],
"enemy_1005_yokai": [24,11,13,0,43819],
"enemy_1005_yokai_2": [27,8,15,8571,80022],
"enemy_1005_yokai_3": [6,1,5,18161,169071],
"enemy_10065_ftzlc": [1,1,0,461,16339],
"enemy_10162_mnctpt": [1,0,1,8289,20600],
"enemy_1119_vofsd": [1,0,1,187,45048],
"enemy_1119_vofsd": [1,0,1,187,45251],
"enemy_1196_msfyin_2": [1,1,0,0,3],
"enemy_1254_lypa_2": [1,0,1,7915,22178],
"enemy_1367_dseed": [1,0,1,0,1619],
"enemy_1517_xi": [1,0,1,6402,2809],
"enemy_9016_acstmr": [1,1,0,0,8]
},
"hooks": [1,95,5100,537,537,814,847,493,48,88,20,31,15,8321,313,21,65,40,1,58,0,1,3],
"events": {"atk":609,"bounty":1,"deploy":28,"die":48,"dmg":841,"fx":101,"heal":1,"leak":40,"skill":40,"spawn":80,"status":165},
"fx": {"beam":5,"block":3,"buff":5,"burst":1,"coldWind":1,"displace":17,"dodge":3,"dp":4,"iceSpike":1,"liberate":1,"overclock":1,"overload":1,"palsy":3,"revive":3,"rockfall":1,"sentry":6,"shadowWeave":8,"shield":7,"shieldBreak":6,"stone":1,"swordStorm":7,"telegraph":2,"volley":9,"wake":1,"wolfShadow":1,"wolfShadowLost":2,"zone":1},
"snaps": "4fee72dc 044d4d71 2d296bd2 8f190397 25e9481f a2393e55 f63611fd a84907cb 781ab904 197e3258 21a13c21 50ff3d32 48914f41 a18d45b9 b3b3f708 95f49808 87c68c2c"
"hooks": [1,95,5100,558,558,836,872,514,50,88,32,31,16,8324,314,21,65,38,1,60,0,1,10],
"events": {"atk":630,"bounty":1,"deploy":28,"die":50,"dmg":866,"fx":109,"heal":1,"leak":38,"skill":63,"spawn":80,"status":155},
"fx": {"beam":5,"block":3,"buff":5,"burst":1,"coldWind":1,"displace":17,"dodge":10,"dp":4,"iceSpike":1,"liberate":1,"overclock":1,"overload":1,"palsy":4,"revive":3,"rockfall":1,"sentry":6,"shadowWeave":8,"shield":7,"shieldBreak":6,"stone":1,"swordStorm":7,"telegraph":2,"volley":9,"wake":1,"wolfShadow":1,"wolfShadowLost":2,"zone":1},
"snaps": "ed481fc3 8e0dc466 ef4ce4ad d085ef7a c9763ca6 af64f527 2d3af1d5 67a9d60b 2e59cb78 c998981e 2978105f aef54882 19722e20 04daeecd b596b323 c3b27682 6a8a0ef3"
},
"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_12_b 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",
@@ -2491,7 +2491,7 @@
["p1",3,"op","chess_char_4_19_a",1,null,0,0,56,0,1987,3120,0,57,2],
["p1",4,"op","chess_char_4_22_a",1,null,2022,1,7,0,1927,5565,1006,57,1],
["p1",5,"op","chess_char_4_12_b",1,"none",4562.3,1,11,0,35227,17118,13682,86,5],
["p1",6,"op","chess_char_4_04_b",2,"uniequip_002_ines",2149,1,14,11,181631,3872,0,149,0],
["p1",6,"op","chess_char_4_04_b",2,"uniequip_002_ines",2149,1,14,11,181631,3872,0,149,1],
["p1",7,"op","chess_char_4_07_b",2,"none",0,0,52,1,33633,4466,0,51,2],
["p1",8,"op","chess_char_4_09_b",2,"uniequip_004_mizuki",1756,1,1,1,6302,263,0,1,1],
["p1",9,"op","chess_char_4_20_a",1,null,1217,1,2,1,29735,0,0,78,3],
@@ -2512,10 +2512,10 @@
"enemy_1427_lrnazg": [1,1,0,1405,12000],
"enemy_2010_csdcr": [1,1,0,2913,9600]
},
"hooks": [1,64,5700,1157,1157,1435,1439,27488,47,48,29,24,73,26679,96,56,47,0,1,48,1,0,14],
"events": {"atk":1342,"bounty":4,"deploy":17,"die":48,"dmg":1409,"fx":192,"heal":72,"layer":1,"skill":52,"spawn":60,"status":325},
"hooks": [1,64,5700,1157,1157,1435,1439,27488,47,48,30,24,73,26690,96,56,47,0,1,48,1,0,14],
"events": {"atk":1342,"bounty":4,"deploy":17,"die":48,"dmg":1409,"fx":192,"heal":72,"layer":1,"skill":54,"spawn":60,"status":325},
"fx": {"aoe":2,"beam":1,"buff":2,"devour":4,"displace":4,"dodge":14,"ember":1,"garrison":1,"garrisonGrant":1,"link":3,"mote":98,"overload":4,"reweave":3,"riposte":4,"ripple":1,"sentry":2,"shadowWeave":37,"shield":1,"snow":1,"summon":1,"wake":5,"zone":2},
"snaps": "80eff0bd 95d47c09 dd2e0008 eb3c9562 27587df1 fe49d9de e46cc855 ed9545ed 00d83ca7 ef239119 9dc72c4f 571cf256 94414c9e 312bd27c aa01479f 39e0622b 72cdcf90 ef9cd273 c5f3ecbe"
"snaps": "39e7ce6d 6bf38ad7 b97d538a cd636ea4 171d3c97 cdfd8c44 d612e433 5f097ac7 053027bd a16e3f13 288a1e29 9b60247c 73a0d04c 4935688a fe7f1739 1881d361 9c94ffe2 24053735 3403994c"
},
"roster-042": {
"about": "pass 2 band 5: 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_5_01_a chess_char_4_16_b chess_char_4_13_b chess_char_4_20_b chess_char_4_24_b chess_char_4_25_b chess_char_5_02_a",
@@ -2557,7 +2557,7 @@
["p1",4,"op","chess_char_4_19_b",1,"none",2525.9,1,10,0,19818,2156,0,18,5],
["p1",5,"op","chess_char_4_22_b",1,"uniequip_002_svrash",3339.7,1,11,6,48019,10366,6083,60,1],
["p1",6,"op","chess_char_5_01_a",2,null,0,0,76,4,34609,14118,3890,65,0],
["p1",7,"op","chess_char_4_16_b",1,"none",1922.7,1,79,7,104704,7396,1628,92,0],
["p1",7,"op","chess_char_4_16_b",1,"none",1922.7,1,79,7,104704,7396,1628,92,7],
["p1",8,"op","chess_char_4_13_b",2,"uniequip_004_gnosis",2553.2,1,8,6,36309,0,0,49,3],
["p1",9,"op","chess_char_4_20_b",1,"none",1844.7,1,9,21,139236,1023,0,70,3],
["p1",10,"op","chess_char_4_24_b",2,"none",2571.4,1,12,7,173659,7101,646,37,4],
@@ -2579,10 +2579,10 @@
"enemy_1512_mcmstr": [1,1,0,5981,92160],
"enemy_9008_acbunn": [1,1,0,1338,13824]
},
"hooks": [1,79,5461,554,554,1344,1361,1606,69,66,25,25,0,17722,2148,41,59,0,1,76,50,1,6],
"events": {"atk":674,"bounty":4,"deploy":18,"die":66,"dmg":1181,"fx":205,"heal":174,"layer":50,"skill":47,"spawn":73,"status":325},
"hooks": [1,79,5461,554,554,1344,1361,1606,69,66,32,28,0,17722,2148,41,59,0,1,76,50,1,6],
"events": {"atk":674,"bounty":4,"deploy":18,"die":66,"dmg":1181,"fx":205,"heal":174,"layer":50,"skill":60,"spawn":73,"status":314},
"fx": {"aoe":5,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":4,"dodge":6,"explode":26,"extraAttack":12,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":40,"overload":1,"revive":1,"reweave":3,"riposte":2,"rockfall":1,"shieldBreak":2,"sleepGuard":4,"snow":1,"stone":1,"swordStorm":7,"telegraph":2,"zone":40},
"snaps": "df8e82b1 398be261 233cef48 a757d9ed d27885c6 99345711 c9bc17f6 9c10aeaf 97cbb8a3 5e2314f5 784bbe0d b1f2487c 8a057f12 72acb6ed baf33fd6 0d272c6b 21ae120e f91f3666 505de780"
"snaps": "875d082b 85a4bc0b 9625d9f6 7e68be43 ddfb3f30 824ba052 277e00a7 05c18f12 c69c93d5 804b666f d57f06c7 1c059a92 2e2f05e5 8a408670 bcd9bab3 325c0f56 9eeb4763 8b918a67 86550ca0"
},
"roster-043": {
"about": "pass 2 band 5: chess_char_5_08_a chess_char_5_11_a chess_char_5_05_a chess_char_5_06_a chess_char_5_13_a chess_char_5_14_a chess_char_5_07_a chess_char_5_17_a chess_char_5_03_a chess_char_5_10_a chess_char_5_12_a chess_char_5_15_a",
@@ -2799,7 +2799,7 @@
["p1",1,"op","chess_char_6_03_b",1,"uniequip_002_yu",0,0,3,1,19316,8739,850,4,1],
["p1",2,"op","chess_char_6_15_a",1,null,0,0,101,0,7408,4408,0,10,0],
["p1",3,"op","chess_char_6_19_a",1,null,0,0,4,0,10494,2168,0,10,0],
["p1",4,"op","chess_char_6_17_a",1,null,0,0,148,0,29960,9909,0,10,0],
["p1",4,"op","chess_char_6_17_a",1,null,0,0,148,0,29960,9909,0,10,4],
["p1",5,"op","chess_char_6_14_a",1,null,0,0,67,0,0,4002,1296,3,0],
["p1",6,"op","chess_char_6_16_a",1,null,2272,1,164,0,4509,7849,0,8,1],
["p1",7,"op","chess_char_6_18_a",1,null,0,0,6,0,14933,1142,0,10,1],
@@ -2824,10 +2824,10 @@
"enemy_1439_dslntf": [1,0,1,0,0],
"enemy_2034_sythef_2": [1,0,1,0,0]
},
"hooks": [1,165,6669,205,205,496,500,100,26,162,5,9,4,3662,276,15,134,132,1,27,1,1,0],
"events": {"atk":291,"bounty":1,"deploy":31,"die":30,"dmg":488,"fx":170,"heal":58,"layer":1,"leak":132,"skill":10,"spawn":149,"status":395},
"hooks": [1,165,6669,205,205,496,500,100,26,162,9,9,4,3662,276,15,134,132,1,27,1,1,0],
"events": {"atk":291,"bounty":1,"deploy":31,"die":30,"dmg":488,"fx":170,"heal":58,"layer":1,"leak":132,"skill":18,"spawn":149,"status":387},
"fx": {"blessing":1,"blink":2,"burst":1,"camouflage":2,"displace":1,"explode":72,"garrison":1,"garrisonGrant":2,"link":1,"snow":7,"sunBurst":3,"talent":2,"telegraph":2,"zone":73},
"snaps": "35175e6a 5254960e e325e1fb 55b3c1fe b88e8a9b 8d94183d a24456d0 600bb5cc 3e1b9122 1cf41cf2 33056009 92c6302d 615494f3 a16c8209 6ddf8772 e5436879 2fbf3bf6 a19f6111 6e40516c 6facc459 af773fa1 73afcd9e f58d0df3"
"snaps": "6605881c 99d27c6c d2d45669 7e4fbc70 bcf82881 4959c1cf 07251748 47e1ea9c ef3fabd2 d150c862 3083e599 7e3dcbbd 7c7253a3 191cba19 fb761362 7766cc81 56e7cf0e 85fb9079 929affb4 6525eb41 e68dc459 0c416386 3305aebb"
},
"roster-047": {
"about": "pass 2 band 7: chess_char_6_06_b chess_char_6_15_b chess_char_6_07_b chess_char_6_17_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b token_10030_mlyss_wtrman",
@@ -2852,7 +2852,7 @@
["p1",1,"op","chess_char_6_06_b",1,"none",0,0,147,2,25390,5278,0,11,1],
["p1",2,"op","chess_char_6_15_b",1,"none",0,0,77,12,61772,4608,0,38,3],
["p1",3,"op","chess_char_6_07_b",1,"none",0,0,114,0,12509,6774,0,10,0],
["p1",4,"op","chess_char_6_17_b",1,"none",0,0,117,1,25547,4702,0,7,0],
["p1",4,"op","chess_char_6_17_b",1,"none",0,0,116,1,25547,4702,0,7,2],
["p1",5,"op","chess_char_6_08_b",1,"none",1508,1,178,4,19587,5100,2267,16,1],
["p1",6,"op","chess_char_6_09_b",2,"none",268,1,177,0,3589,3068,0,8,0],
["p1",7,"op","chess_char_6_11_b",1,"none",0,0,163,0,2881,6379,0,8,0],
@@ -2883,10 +2883,10 @@
"enemy_1389_winbab_2": [48,14,34,57056,117080],
"enemy_1525_blkswb": [1,0,1,4227,26880]
},
"hooks": [1,177,6417,702,694,819,922,115,77,172,13,15,80,9761,1199,38,142,91,1,81,0,0,0],
"events": {"atk":704,"bounty":3,"deploy":32,"die":81,"dmg":837,"fx":59,"heal":26,"leak":91,"skill":26,"spawn":163,"status":184},
"hooks": [1,177,6417,702,694,819,922,115,77,172,15,15,80,9761,1199,38,142,91,1,81,0,0,0],
"events": {"atk":704,"bounty":3,"deploy":32,"die":81,"dmg":837,"fx":59,"heal":26,"leak":91,"skill":30,"spawn":163,"status":180},
"fx": {"airstrike":26,"anchor":1,"appear":1,"blink":2,"copy":1,"disappear":1,"ember":1,"explode":1,"garrisonGrant":2,"heal":2,"link":1,"revive":4,"strike":6,"summon":2,"summonStun":2,"sunBurst":1,"sword":3,"telegraph":1,"zone":1},
"snaps": "96a9ade8 70342b7c 1cd3438c 5f14d177 4a9ae9a3 2a5e7ceb 96699c48 2c0389f8 b0aa382f e4517b5f ee9f9aa8 d0ade83b 407412c8 471a3be8 08511f89 ca703df9 8934e235 0a76565b 69916b70 0ca721df 0e9b9182 e3809b4d"
"snaps": "019a26cc 168013a9 838d9fe2 d6e37c3b 03ecc5c8 af794d9a bab801d7 d430ce64 545aa402 627f3cca 90dea475 f6fbcb03 eac8545b d32ae905 60789830 dc25a684 972cf188 1da714fe 79b82ec1 826d0e8e 866ff2a7 c4064e28"
},
"roster-048": {
"about": "pass 2 band 7: chess_char_6_19_b chess_char_5_13_b chess_char_6_20_b chess_char_5_12_b chess_char_5_20_b chess_char_6_05_b",
@@ -2989,7 +2989,7 @@
"enemy_9009_acfort": [1,0,1,7851,19348]
},
"hooks": [1,71,5700,366,366,1824,1824,0,31,65,30,31,0,3900,792,3,64,34,1,33,0,0,0],
"events": {"atk":422,"bounty":3,"deploy":7,"die":31,"dmg":1823,"fx":45,"leak":34,"skill":60,"spawn":69,"status":304},
"events": {"atk":422,"bounty":3,"deploy":7,"die":31,"dmg":1823,"fx":45,"leak":34,"skill":64,"spawn":69,"status":304},
"fx": {"coin":8,"dilemma":2,"explode":2,"frostNova":25,"revive":2,"shield":1,"shieldBreak":1,"summon":1,"telegraph":3},
"snaps": "3dfc169e 33981103 b9e7dc10 e5c76cae 6c52ad96 4e2ae444 84eaae26 656ad352 8930f4ed cb10ab9f 8d62f572 2928e41b 928d6080 4b392947 cad850f5 88878813 87bd227a 00e4cae4 a630d784"
}
+95
View File
@@ -0,0 +1,95 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { DATA, makeMatch } from './harness.js';
import { GameData } from '../../server/match/gamedata.js';
import { bondSnapshot } from '../../server/match/bondsMeta.js';
import { buildBattleSpec, createBattleFromSpec, compactResult } from '../../server/sim/spec.js';
import { validateClientResult } from '../../server/match/fields.js';
import { helperStats, helperOrder } from '../../server/match/unite.js';
const IDS = ['maniShip', 'emptyShip', 'soloShip', 'suntShip'];
const gd = new GameData(DATA, 'mode_multi_normal');
const bonds = () => Object.fromEntries(IDS.map((id) => [id, { count: 2, active: true, tier: 1, layers: 20 }]));
test('hidden-layer bonds retain saved layers, prep gains, pending gains and battle input layers', () => {
const h = makeMatch({ mode: 'solo', humans: 1, fake: true }).start();
try {
h.toPrep(1);
const ps = h.ps('p_0');
for (const id of IDS) {
assert.equal(gd.bond(id).noStack, true, id);
ps.bondCountBonus[id] = gd.bond(id).thresholds[0];
ps.layers[id] = 50;
}
ps.bondCountBonus.deputShip = 2;
ps.recompute();
for (const id of IDS) {
assert.equal(ps.bonds[id].active, true);
assert.equal(ps.layers[id], 50, 'recompute must not erase saved layers');
assert.equal(ps.addLayers(id, 8, { requireActive: true }), 8);
assert.equal(ps.addLayers(id, 8, { requireActive: false }), 8);
}
h.m.dispatch(ps, 'onPrepEnd', { round: 1 });
for (const id of IDS) assert.equal(ps.layers[id], 68, '助力 prep-end traits can add hidden layers');
ps.pendingLayerGains = Object.fromEntries(IDS.map((id) => [id, 9]));
for (const id of IDS) {
assert.equal(ps.bondsView()[id].layers, 77);
assert.equal(bondSnapshot(ps.bonds)[id].layers, 68, 'battle input keeps settled layers');
}
} finally { h.m.dispose(); }
});
test('hidden-layer gains run through the simulator hooks, event stream and result', () => {
const spec = buildBattleSpec({ stageId: 'act2autochess_m01', timeLimit: 1, content: 'none',
players: [{ playerId: 'p_0', units: [], bonds: bonds() }], flags: { layerGainsEnabled: true } });
const b = createBattleFromSpec(spec, DATA, { quiet: true });
const calls = [];
b.on('layerGain', (c) => calls.push(c.bondId));
for (const id of IDS) {
assert.equal(b.getPlayer('p_0').bonds[id].layers, 20);
assert.equal(b.addLayers('p_0', id, 4), 4);
assert.equal(b.getPlayer('p_0').bonds[id].layers, 24);
}
assert.deepEqual(calls, IDS);
assert.deepEqual(b.drainEvents().filter((e) => e[0] === 'layer').map((e) => e[2]), IDS);
b.forceEnd();
assert.deepEqual(b.result().perPlayer.p_0.layerGains, Object.fromEntries(IDS.map((id) => [id, 4])));
});
test('client validation accepts legal hidden-layer gains and still rejects malformed JSON values', () => {
const spec = buildBattleSpec({ stageId: 'act2autochess_m01', timeLimit: 1, content: 'none',
players: [{ playerId: 'p_0', units: [], bonds: bonds() }], flags: { layerGainsEnabled: true } });
const b = createBattleFromSpec(spec, DATA, { quiet: true });
for (const id of IDS) b.addLayers('p_0', id, 4);
b.forceEnd();
const raw = compactResult(b.result());
assert.equal(validateClientResult(spec, raw, { gd }).ok, true);
for (const value of [-1, '4', null, {}, 1000]) {
const forged = structuredClone(b.result());
forged.perPlayer.p_0.layerGains.soloShip = value;
assert.equal(validateClientResult(spec, forged, { gd }).ok, false, JSON.stringify(value));
}
});
test('settlement retains hidden-layer gains from the server result', () => {
const h = makeMatch({ mode: 'solo', humans: 1, fake: true,
script: () => ({ layerGains: { p_0: { soloShip: 12, suntShip: 9, yanShip: 3 } } }) }).start();
try {
h.toPrep(1);
h.m.handle('p_0', { t: 'g.ready', ready: true });
h.toPrep(2);
assert.equal(h.ps('p_0').layers.soloShip, 12);
assert.equal(h.ps('p_0').layers.suntShip, 9);
assert.equal(h.ps('p_0').layers.yanShip, 3);
} finally { h.m.dispose(); }
});
test('hidden layers contribute to unite pair ordering while inactive layers do not', () => {
const player = (id, seat, layers) => ({ playerId: id, seat, deployCount: 1, board: new Map(),
layers: { soloShip: layers, suntShip: 999 }, bonds: {
soloShip: { active: true, layers }, suntShip: { active: false, layers: 999 },
} });
const a = player('p_0', 0, 5), b = player('p_1', 1, 8), m = { gd }, results = new Map();
assert.equal(helperStats(m, b, results).layers, 8);
assert.deepEqual(helperOrder(m, [a, b], results).map((p) => p.playerId), ['p_1', 'p_0']);
});
+26 -16
View File
@@ -6,6 +6,7 @@
import { test, after } from 'node:test';
import assert from 'node:assert/strict';
import { startServer } from '../../server/index.js';
import { getData } from '../../server/data.js';
import { Match } from '../../server/match/Match.js';
import { TestClient } from '../helpers/wsClient.js';
import { FakeBattle } from './fakeBattle.js';
@@ -23,7 +24,8 @@ let srv = null;
const clients = [];
async function server() {
if (!srv) srv = await startServer({ port: 0, host: '127.0.0.1', log, MatchClass: FastMatch });
// Issue #144: in the one-human + AI draft, seed 69 makes the AI take band_bldsk first.
if (!srv) srv = await startServer({ port: 0, host: '127.0.0.1', log, MatchClass: FastMatch, seedFn: () => 69 });
return srv;
}
async function player(name, token) {
@@ -38,6 +40,23 @@ async function player(name, token) {
}
const ok = async (c, msg) => { const r = await c.request(msg); assert.equal(r.t, 'ok', `${msg.t}: ${JSON.stringify(r)}`); return r; };
/** Pick from the current public draft, including the AI's choices; never rely on a timeout after a refused pick. */
async function pickCoopBands(players) {
const pending = new Map(players.map((c) => [c.id, c]));
let frame;
while (pending.size) {
frame = await players[0].waitFor('m.public', (p) => p.phase === 'BAND_DRAFT' && pending.has(p.draft?.turn), 10000);
assert.equal(frame.draft.order.length, frame.players.length);
const taken = new Set(Object.values(frame.draft.picks));
const band = Object.values(getData().bands).find((b) =>
(!Array.isArray(b.modeTypeList) || b.modeTypeList.includes('MULTI')) && !taken.has(b.bandId));
assert.ok(band, 'a co-op strategy is still available');
await ok(pending.get(frame.draft.turn), { t: 'g.band', bandId: band.bandId });
pending.delete(frame.draft.turn);
}
return frame;
}
after(async () => {
for (const c of clients) await c.terminate().catch(() => {});
if (srv) await srv.close();
@@ -101,15 +120,7 @@ test('co-op over websockets: two humans + AI, reconnect with the token mid-match
for (const c of [a, b]) await c.waitFor('m.public', (p) => p.phase === 'INFO_CHECK');
await ok(a, { t: 'g.infoReady' });
await ok(b, { t: 'g.infoReady' });
const draft = await a.waitFor('m.public', (p) => p.phase === 'BAND_DRAFT' && p.draft);
assert.equal(draft.draft.order.length, 3);
// humans pick when it is their turn (bots pick themselves)
for (let i = 0; i < 20; i++) {
const p = await a.waitFor('m.public', (x) => x.phase !== 'BAND_DRAFT' || [a.id, b.id].includes(x.draft && x.draft.turn), 10000);
if (p.phase !== 'BAND_DRAFT') break;
const who = p.draft.turn === a.id ? a : b;
await who.request({ t: 'g.band', bandId: 'band_bldsk' });
}
await pickCoopBands([a, b]);
await a.waitFor('m.public', (p) => p.phase === 'PREP' && p.round === 1, 10000);
// B drops and comes back with its token
await b.terminate();
@@ -120,7 +131,9 @@ test('co-op over websockets: two humans + AI, reconnect with the token mid-match
assert.equal(priv.playerId, b2.id);
await ok(a, { t: 'g.ready', ready: true });
await ok(b2, { t: 'g.ready', ready: true });
await a.waitFor('m.public', (p) => p.phase === 'COMBAT', 10000);
// Instant simulated battles can finish before the throttled public phase is broadcast, as in the solo test.
const start = await a.waitFor('b.start', (x) => x.authoritative, 10000);
assert.equal(start.fieldId, `n:${a.id}`);
await a.waitFor('m.public', (p) => p.phase === 'PREP' && p.round === 2, 15000);
await ok(a, { t: 'g.leave' });
await ok(b2, { t: 'g.leave' });
@@ -154,11 +167,8 @@ test('co-op spectator over websockets: watches the real match like an eliminated
assert.equal(r.code, 'SPECTATOR', msg.t);
}
await ok(a, { t: 'g.infoReady' });
for (let i = 0; i < 40; i++) { // every draft frame in order: pick on the own turn (the AI picks at once)
const p = await a.waitFor('m.public', (x) => x.phase !== 'INFO_CHECK', 10000);
if (p.phase !== 'BAND_DRAFT') break;
if (p.draft && p.draft.turn === a.id && !p.draft.picks[a.id]) await ok(a, { t: 'g.band', bandId: 'band_bldsk' });
}
const draft = await pickCoopBands([a]);
assert.ok(Object.values(draft.draft.picks).includes('band_bldsk'), 'seed 69: the AI already took the default');
await a.waitFor('m.public', (p) => p.phase === 'PREP' && p.round === 1, 10000);
for (const msg of [{ t: 'g.buy', slot: 0 }, { t: 'g.ready', ready: true }, { t: 'g.refresh' }]) {
const r = await s.request(msg);
@@ -0,0 +1,121 @@
// test/render/feedback4-texas-skill-clip.test.js — player report (after 0.1.3): 德克萨斯 S2「剑雨」释放时不播放技能动画.
//
// Her Front model's skill roles are { begin: null, loop: 'Skill', end: null } — the 2.17 s 剑雨 clip — and both her
// skills are instant (server/sim/skills.js fires 'skill' 1 and 0 inside one tick and holds only the anim code SKILL for
// 0.5 s, snapshot.js animOf). spine.js setSkill(true) had a branch for a Begin clip and otherwise just re-played the
// base clip, so the skill clip never played: the actor showed its idle through the whole cast. The same clip shape
// (no Begin, no own Idle) carries 58 instant skills across the manifest.
//
// Real manifest entries, headless fake PIXI (test/render/fakepixi.js), like feedback3-skill-idle.test.js.
import { test, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { installFakePixi } from './fakepixi.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const assets = JSON.parse(readFileSync(path.join(ROOT, 'data/assets.json'), 'utf8'));
let fake, SpineActor;
before(async () => {
fake = installFakePixi();
({ SpineActor } = await import('../../public/js/render/spine.js'));
});
after(() => fake.restore());
/** An actor of a real operator's Front model with its equipped skill, recording every clip set / queued. */
function actor(charId, skillIndex) {
const entry = assets.chars[charId].spine.front;
const a = new SpineActor({ animations: Object.keys(entry.animations).map((name) => ({ name })) }, entry);
a.setSkillIndex(skillIndex);
a.log = [];
const st = a.spine.state;
const set = st.setAnimation, add = st.addAnimation;
st.setAnimation = (i, name, loop) => { a.log.push(['set', name, !!loop]); return set(i, name, loop); };
st.addAnimation = (i, name, loop) => { a.log.push(['queue', name, !!loop]); return add(i, name, loop); };
return a;
}
const run = (a, s, dt = 1 / 60) => { for (let t = 0; t < s - 1e-9; t += dt) a.update(dt); };
const names = (a) => a.log.map(([, n]) => n);
test('德克萨斯 S2 剑雨: an instant skill with no Begin clip plays its Skill clip and keeps it for the clip length', () => {
const a = actor('char_102_texas', 1);
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.end, a.roles.skill.idle], [null, 'Skill', null, null], 'the manifest roles');
assert.equal(a.dur('Skill'), 2.167, 'the 剑雨 clip');
a.setSkill(true);
assert.deepEqual(a.log, [['set', 'Skill', false]], 'the skill clip itself, played once');
a.setSkill(false); // the sim fires 'skill' 1 and 0 in the same tick: the flag is already off
run(a, 1);
assert.equal(a.mode, 'skillBegin');
assert.equal(a.current, 'Skill', 'still on screen halfway through the cast');
run(a, 1.3);
assert.equal(a.mode, 'base');
assert.equal(a.current, 'Idle', 'back to the idle once the clip has run');
assert.deepEqual(names(a), ['Skill', 'Idle'], 'no end clip, no base flash before it');
});
test('the other instant skills with the same clip shape play their clip too (char_140_whitew S1 日晷, char_206_gnosis S2 零度爆发, 德克萨斯 S1)', () => {
for (const [charId, idx, clip] of [['char_140_whitew', 0, 'Skill'], ['char_206_gnosis', 1, 'Skill_2'], ['char_102_texas', 0, 'Skill']]) {
const a = actor(charId, idx);
assert.equal(a.roles.skill.begin, null, `${charId} S${idx + 1}: no Begin clip`);
assert.equal(a.roles.skill.idle, null, `${charId} S${idx + 1}: no own Idle`);
a.setSkill(true);
assert.equal(a.current, clip, `${charId} S${idx + 1} plays ${clip}`);
}
});
test('a skill that stays on (银灰 S3 真银斩: same clip shape, 1.33 s clip) plays it once and then rests — no freeze on its last frame', () => {
const a = actor('char_172_svrash', 2);
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.idle], [null, 'Skill', null]);
a.setSkill(true);
assert.equal(a.current, 'Skill');
run(a, 2);
assert.equal(a.mode, 'base', 'the one-shot window is over');
assert.equal(a.current, 'Idle', 'rests on the idle while the skill is still running');
a.log.length = 0;
a.attack(1.2);
assert.equal(a.current, 'Skill', 'attacks still use the skill loop, as before');
a.setSkill(false);
assert.equal(a.current, 'Idle', 'no End clip: straight back to the idle');
});
test('unchanged: a Begin clip still runs into its queued next clip, a skill with its own Idle still idles, and End still closes the skill', () => {
const b = actor('char_4207_branch', 1); // 折桠 S2: Skill_2_Begin → Skill_2_Idle (community report #23)
b.setSkill(true);
assert.deepEqual(b.log, [['set', 'Skill_2_Begin', false], ['queue', 'Skill_2_Idle', true]]);
const n = actor('char_1014_nearl2', 2); // 耀骑士临光 S3: no Begin, own Idle — plays that idle at once
n.setSkill(true);
assert.deepEqual(n.log, [['set', 'Skill_3_Idle', true]]);
const u = actor('char_337_utage', 1); // 宴: Begin → loop, unchanged
u.setSkill(true);
assert.deepEqual(u.log, [['set', 'Skill_Start', false], ['queue', 'Skill_Loop', true]]);
});
test('the deploy-time passives the sim now opens a window for play their clip (琳琅诗怀雅 S1/S2: kind passive, no duration)', () => {
// server/sim/skills.js `_startPassive` fires ['skill', id, 1] at the deployment and 0 after SKILL_ANIM_WINDOW (0.5 s),
// which is shorter than her 1 s clips: the actor holds the clip to its own end and then idles.
for (const [idx, clip] of [[0, 'Skill_1'], [1, 'Skill_2']]) {
const a = actor('char_1033_swire2', idx);
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.idle], [null, clip, null]);
assert.equal(a.dur(clip), 1);
a.setSkill(true);
assert.equal(a.current, clip, `S${idx + 1} plays ${clip} at the deployment`);
run(a, 0.5);
a.setSkill(false); // the sim closes the window at 0.5 s …
run(a, 0.4);
assert.equal(a.current, clip, '… the clip is still running (it is 1 s long)');
run(a, 0.2);
assert.equal(a.mode, 'base');
assert.equal(a.current, 'Idle', 'and then back to the idle');
}
});
test('a skill clip that IS the normal attack clip is not played as a skill animation (no attack twitch at the deployment)', () => {
const a = actor('char_1028_texas2', 0); // 缄默德克萨斯 S1: anims.skill.loop === anims.attack.loop ('Attack')
assert.equal(a.roles.skill.loop, a.roles.attack.loop);
a.setSkill(true);
assert.deepEqual(a.log, [['set', 'Idle', true]], 'falls through to the base clip, as before');
assert.equal(a.current, 'Idle');
});
+1 -1
View File
@@ -78,7 +78,7 @@ test('耀骑士临光 S2: withdrawn at the skill end, she lies down on her tile
const h = field([{ chessId: NEARL2, row: 10, col: 4, skillIndex: idx }]);
const u = h.unit(NEARL2);
h.step();
assert.ok(u.alive && u.findBuff('nearl2:night'), 'S2 on deploy');
assert.ok(u.alive && u.skill.active, 'S2 on deploy');
assert.ok(h.runUntil(() => !u.alive, 40), 'withdraws when the skill ends');
assert.equal(u.removeReason, 'retreat');
const d = downOf(h, u);
+65
View File
@@ -0,0 +1,65 @@
// Regression checks for 突袭 landing terrain, using the shared server/browser simulation.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, flatStage, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { inRange } from '../../server/sim/content/support/index.js';
const HOME = [12, 3];
const ENEMY = [10, 8];
const BONDS = { raidShip: { count: 2, active: true, tier: 1, layers: 0 } };
function scenario(ready, noLanding = false) {
const stage = flatStage();
const put = (r, c, glyph) => { stage.rows[r] = stage.rows[r].slice(0, c) + glyph + stage.rows[r].slice(c + 1); };
// Every tile in the landing search area is high ground or water except the one dry shore tile.
for (let r = 9; r <= 12; r++) for (let c = 6; c <= 10; c++) put(r, c, 'h');
put(...ENEMY, 'd');
put(10, 9, 'r'); // A shooting platform: low ground in the legend, but unavailable to melee.
if (!noLanding) put(9, 8, 'r');
const h = makeBattle({
stage, bonds: BONDS,
defs: {
chess: { raider: chessRec({
id: 'raider', profession: 'WARRIOR', position: 'MELEE', bonds: ['raidShip'],
rangeGrid: [[0, 0], [0, 1], [1, 0], [0, -1], [-1, 0]],
skill: ready ? { spCost: 10, initSp: 10 } : null,
}) },
enemies: { enemy_wader: enemyRec({ key: 'enemy_wader', hp: 1e7, speed: 0 }) },
},
units: [{ chessId: 'raider', row: HOME[0], col: HOME[1] }],
enemies: [{ key: 'enemy_wader', pos: ENEMY }],
setup(b) { b.setObstacle(10, 9, true); },
});
h.step();
assert.equal(h.unit('raider').alive, true);
assert.equal(h.b.grid.canStand(10, 7), false, 'high ground is not a melee landing tile');
assert.equal(h.b.grid.canStand(...ENEMY), false, 'water is not a landing tile');
assert.equal(h.b.grid.canStand(10, 9), false, 'shooting platform is not a melee landing tile');
return h;
}
for (const ready of [false, true]) {
const trigger = ready ? 'skill ready' : 'idle for 10 seconds';
test(`突袭 (${trigger}): skips high ground, water and shooting platforms, landing on dry ground`, () => {
const h = scenario(ready);
const u = h.unit('raider');
h.run(ready ? 0.6 : 11);
assert.deepEqual([u.tileR, u.tileC], [9, 8], 'the only legal shore tile');
assert.ok(u.alive && u.deployed && u.ground);
assert.ok(inRange(u, h.enemy('enemy_wader')), 'the enemy is in range after the jump');
const landings = h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial);
assert.equal(landings.length, 1, 'one successful raid redeployment');
checkInvariants(h.b);
});
test(`突袭 (${trigger}): stays deployed when only high ground, water and platforms surround the enemy`, () => {
const h = scenario(ready, true);
const u = h.unit('raider');
h.run(12);
assert.deepEqual([u.tileR, u.tileC], HOME);
assert.ok(u.alive && u.deployed, 'no withdrawal without a valid landing');
assert.equal(h.hooksOf('death').filter((c) => c.unit === u).length, 0);
assert.equal(h.hooksOf('deploy').filter((c) => c.unit === u && !c.initial).length, 0);
checkInvariants(h.b);
});
}
+170
View File
@@ -4,15 +4,160 @@ import assert from 'node:assert/strict';
import { makeBattle, chessRec, enemyRec } from '../helpers/battleHarness.js';
import { genericSkillSpec, genericKind } from '../../server/sim/content/generic.js';
import { getDefaultSource } from '../../server/sim/simdata.js';
import { UF } from '../../shared/constants.js';
const approx = (a, b, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps, `${a} ≈ ${b}`);
const RANGE3 = [[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3], [-1, 0], [-1, 1], [-1, 2], [-1, 3]];
const dummy = (o = {}) => enemyRec({ key: 'enemy_dummy', hp: 1e7, speed: 0, ...o });
test('deployment duration: synchronous start, one charge, countdown, carry and fresh redeployment', () => {
for (const carryState of [undefined, { hpPct: 0.5, sp: 0, skillActive: true }]) {
const order = [];
const h = makeBattle({
defs: { chess: { t_deploy: sniperWith({ spCost: 0 }, { id: 't_deploy' }) } },
units: [{ chessId: 't_deploy', row: 10, col: 4, carryState }], timeLimit: 120,
kits: { t_deploy: () => ({ skill: {
kind: 'duration', activateOnDeploy: true, duration: 4, spCost: 0, spType: 'none', trigger: 'NEVER', mods: { atkPct: 1 },
} }) },
setup(b) {
for (const name of ['skillStart', 'deploy']) b.on(name, (c) => order.push([name, c.reason, c.unit.skill.timeLeft]));
},
});
h.b.start();
const u = h.unit('t_deploy'), sk = u.skill;
assert.deepEqual(order, [['skillStart', 'deploy', 4], ['deploy', undefined, 4]]);
assert.equal(sk.active, true);
assert.equal(sk.activations, 1);
assert.equal(sk.charges, 0);
assert.equal(sk.ready, false);
approx(u.s.atk, 200);
const tuple = () => h.snapshot().units.find((t) => t[0] === u.id);
assert.ok(tuple()[7] & UF.SKILL);
assert.deepEqual(tuple().slice(5, 7), [4, 4]);
h.run(2);
approx(sk.timeLeft, 2);
assert.deepEqual(tuple().slice(5, 7), [2, 4]);
h.run(2.1);
assert.equal(sk.active, false);
assert.equal(sk.ready, false);
assert.equal(sk.charges, 0);
assert.equal(sk.sp, 0);
approx(u.s.atk, 100);
assert.ok(!(tuple()[7] & UF.SKILL));
assert.deepEqual(tuple().slice(5, 7), [0, 0]);
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['duration']);
assert.equal(sk.gainSp(100, 'talent'), 0);
h.run(5);
assert.equal(sk.activations, 1);
h.b.retreat(u);
assert.equal(h.hooksOf('skillEnd').length, 1, 'expired skill does not end again on retreat');
assert.ok(h.b.redeploy(u, { free: true }));
assert.equal(sk.activations, 2);
approx(sk.timeLeft, 4);
assert.equal(sk.charges, 0);
h.invariants();
}
});
test('deployment duration: early retreat/death cleans effects and leaves no stale end on redeployment', () => {
for (const remove of ['retreat', 'kill']) {
const h = makeBattle({
defs: { chess: { t_deploy: sniperWith({ spCost: 0 }, { id: 't_deploy' }) } },
units: [{ chessId: 't_deploy', row: 10, col: 4 }],
kits: { t_deploy: () => ({ skill: {
kind: 'duration', activateOnDeploy: true, duration: 4, spCost: 0, spType: 'none', trigger: 'NEVER', mods: { aspd: 50 },
} }) },
});
const u = h.unit('t_deploy');
h.run(1);
h.b[remove](u);
assert.equal(u.skill.active, false);
approx(u.s.aspd, u.base.aspd);
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['death']);
assert.ok(h.b.redeploy(u, { free: true }));
h.run(3.1);
assert.equal(u.skill.active, true, 'old deployment deadline does not end the new window');
assert.equal(h.hooksOf('skillEnd').length, 1);
h.run(1);
assert.deepEqual(h.hooksOf('skillEnd').map((c) => c.reason), ['death', 'duration']);
assert.equal(u.skill.ready, false);
h.invariants();
}
});
function sniperWith(skill, o = {}) {
return chessRec({ id: 't_sn', profession: 'SNIPER', subProfessionId: 'closerange', stats: { atk: 100, bat: 1, ...(o.stats || {}) }, rangeGrid: RANGE3, skill, ...o });
}
test('generic timed passive: deployment window owns stats and arts hits without reinterpreting passive proc parameters; permanent passive stays on', () => {
const h = makeBattle({
defs: { chess: {
t_sn: sniperWith({ skillType: 'PASSIVE', duration: 0, spCost: 0,
description: '部署后在4秒内攻击力+100%,攻击造成法术伤害',
bb: { atk: 1, duration: 4, max_target: 2, 'attack@cold': 1, ability_range_forward_extend: 1 } }),
t_p: sniperWith({ skillType: 'PASSIVE', duration: 0, spCost: 0, description: '攻击力+100%', bb: { atk: 1, duration: 4 } }, { id: 't_p' }),
}, enemies: { enemy_dummy: dummy() } },
units: [{ chessId: 't_sn', row: 10, col: 4 }, { chessId: 't_p', row: 12, col: 4 }],
content: 'generic', captureNoisy: true,
});
h.b.start();
const u = h.unit('t_sn'), p = h.unit('t_p');
assert.equal(u.skill.kind, 'duration');
assert.equal(u.skill.activations, 1);
assert.equal(u.skill.ready, false);
assert.deepEqual(u.rangeKeys, u.baseRangeKeys, 'passive parameters do not extend the attack range');
h.spawn('enemy_dummy', { pos: [10, 6] });
h.spawn('enemy_dummy', { pos: [10, 6] });
h.run(2);
approx(u.s.atk, 200);
const hits = h.hooksOf('damaged').filter((c) => c.source === u);
assert.ok(hits.length && hits.every((c) => c.type === 'arts'));
assert.equal(new Set(hits.map((c) => c.target)).size, 1, 'passive max_target does not turn normal attacks into multi-target attacks');
assert.deepEqual(h.snapshot().units.find((t) => t[0] === u.id).slice(5, 7), [2, 4]);
h.run(2.1);
assert.equal(u.skill.active, false);
assert.equal(u.skill.charges, 0);
approx(u.s.atk, 100);
assert.deepEqual(u.rangeKeys, u.baseRangeKeys);
h.run(2);
const late = h.hooksOf('damaged').filter((c) => c.source === u && c.t > 4.5);
assert.ok(late.length && late.every((c) => c.type === 'phys'));
assert.equal(h.hooksOf('statusApplied').filter((c) => c.source === u && c.status === 'cold').length, 0,
'passive proc parameters do not become attack statuses');
assert.equal(p.skill.kind, 'passive');
assert.equal(p.skill.active, true);
approx(p.s.atk, 200);
h.invariants();
});
test('generic 宴 S2: real normal/elite descriptions convert damage to arts only during the deployment window', () => {
const ds = getDefaultSource();
for (const id of ['chess_char_1_18_a', 'chess_char_1_18_b']) {
const index = ds.rawChess(id).skills.find((s) => s.skillId === 'skchr_utage_2').index;
const h = makeBattle({
content: 'generic', timeLimit: 40, captureNoisy: true,
units: [{ chessId: id, skillIndex: index, row: 10, col: 4 }],
defs: { enemies: { enemy_dummy: dummy({ atk: 0 }) } },
});
h.b.start();
const u = h.unit(id);
h.spawn('enemy_dummy', { pos: [10, 4] });
const hits = () => h.hooksOf('damaged').filter((c) => c.source === u && c.dmg.isAttack);
h.run(2);
assert.ok(hits().length > 0 && hits().every((c) => c.type === 'arts'), `${id}: active S2 deals arts damage`);
h.run(u.skill.timeLeft + h.TICK);
assert.equal(u.skill.active, false);
assert.equal(u.skill.ready, false);
const endedAt = hits().length;
h.run(3);
const late = hits().slice(endedAt);
assert.ok(late.length > 0 && late.every((c) => c.type === 'phys'), `${id}: normal physical attacks after expiry`);
assert.equal(u.skill.activations, 1);
assert.deepEqual(h.b.errors, []);
h.invariants();
}
});
test('SP: time regen, starts at initSp, capped at cost; DEFAULT waits for an enemy in the initial range', () => {
const h = makeBattle({
defs: { chess: { t_sn: sniperWith({ spCost: 5, initSp: 2, duration: 3, bb: { atk: 1 } }) }, enemies: { enemy_dummy: dummy() } },
@@ -288,3 +433,28 @@ test('spCostMul changes (绝技-style) keep SP within bounds and convert overflo
assert.equal(u.skill.charges, 1);
assert.ok(u.skill.sp <= u.skill.spCost);
});
test('a deploy-time passive fires the skill animation window; a passive with a duration does not (yet)', () => {
// 琳琅诗怀雅 S1 仗义疏财 / S2 “见面礼”: kind 'passive', no duration — the sim used to start them silently, so the
// client got no 'skill' event and never played the clip the manifest carries (player report follow-up). The window is
// the same one an instant cast uses: on at the deployment, off 0.5 s later (SKILL_ANIM_WINDOW), the passive itself
// staying active (it never ends before the unit does).
const h = makeBattle({ units: [{ chessId: 'chess_char_3_04_a', row: 10, col: 6 }], autoFinish: false, timeLimit: 30 });
h.run(0.2);
const u = h.unit('chess_char_3_04_a');
assert.equal(u.skill.kind, 'passive');
assert.equal(u.skill.active, true, 'the passive is active from the deployment');
assert.deepEqual(h.eventsOf('skill').map((e) => e[2]), [1], 'the window opens at the deployment');
assert.ok(u.skillAnimUntil > h.b.time, 'skillAnimUntil drives ANIM.SKILL on the client');
h.run(0.6);
assert.deepEqual(h.eventsOf('skill').map((e) => e[2]), [1, 0], 'and closes 0.5 s later');
assert.equal(u.skill.active, true, 'the passive itself never ends');
// a passive WITH a duration (缄默德克萨斯 S2 阵雨连绵, 8 s): left alone — the sim holds it active until death, so how
// long its stance should show is a separate question
const t = makeBattle({ units: [{ chessId: 'chess_char_4_16_a', row: 10, col: 6, skillIndex: 1 }], autoFinish: false, timeLimit: 30 });
t.run(0.6);
assert.equal(t.unit('chess_char_4_16_a').skill.kind, 'passive');
assert.equal(t.unit('chess_char_4_16_a').skill.active, true);
assert.deepEqual(t.eventsOf('skill'), [], 'no window for a timed passive');
});
+80 -2
View File
@@ -6,7 +6,7 @@ import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { bgmKeyFor, resolveBgm, SfxLimiter, AudioManager, normalAttackSfx } from '../../public/js/audio.js';
import { bgmKeyFor, resolveBgm, SfxLimiter, AudioManager, normalAttackSfx, installAudio, audio, combatTrackFor, COMBAT_TRACK_SWITCH_ROUND } from '../../public/js/audio.js';
import { mediaUrl } from '../../public/js/media.js';
import { PHASE } from '../../shared/constants.js';
@@ -32,18 +32,53 @@ describe('bgm selection', () => {
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT }), 'combat');
// 联防 has its own track: the official escaped_single / escaped_multi levels declare bgmEvent = corrosion
assert.equal(bgmKeyFor('game', { phase: PHASE.UNITE }), 'unite');
// 开战 BGM: the round's own track (combatTrackFor) indexes the manifest's `bgm.combatAlts`
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT }, 0), 'combat:0');
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT }, 1), 'combat:1');
assert.equal(bgmKeyFor('game', { phase: PHASE.UNITE }, 1), 'unite', '联防 keeps its own track, not the round index');
assert.equal(bgmKeyFor('game', { phase: PHASE.PREP }, 1), 'prep');
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT, bossId: 'boss_4' }, 1), 'boss:boss_4');
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT, bossId: 'boss_4' }), 'boss:boss_4');
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT }), 'boss');
assert.equal(bgmKeyFor('game', { phase: PHASE.HIDDEN_CORE, bossId: 'boss_1', hiddenBossId: 'boss_9' }), 'boss:boss_9');
assert.equal(bgmKeyFor('game', { phase: PHASE.HIDDEN_CORE, bossId: 'boss_1', hiddenBossId: 'boss_9' }, 1), 'boss:boss_9');
assert.equal(bgmKeyFor('game', { phase: PHASE.RESULT }), 'lobby');
assert.equal(bgmKeyFor('weird', null), null);
});
test('开战 BGM: the track is fixed per round, not drawn (无畏者 1–7, 骑士之日 8–13)', () => {
// The mode does not draw its battle theme: the official schedule plays 无畏者 through the early rounds and
// 骑士之日 from round 8 on (reviewer note — review had this as a per-match 0.5 draw before).
for (const r of [1, 2, 3, 4, 5, 6, 7]) assert.equal(combatTrackFor(r), 1, `round ${r} plays 无畏者`);
for (const r of [8, 9, 10, 11, 12, 13]) assert.equal(combatTrackFor(r), 0, `round ${r} plays 骑士之日`);
assert.equal(COMBAT_TRACK_SWITCH_ROUND, 7, 'the switch sits between round 7 and 8');
// the boss rounds (14 最终攻势 / 15 隐秘核心) have their own tracks and never ask for combat:<i>
assert.equal(bgmKeyFor('game', { phase: PHASE.FINAL_ASSAULT, round: 14 }, combatTrackFor(14)), 'boss');
// an unknown round falls back to the manifest's plain combat track (older manifest / no round yet)
for (const bad of [null, undefined, 0, -1, NaN, 'x']) assert.equal(combatTrackFor(bad), null, `${bad} ⇒ no index`);
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT, round: 3 }, combatTrackFor(null)), 'combat');
// both ends of a real run: a solo 标准 match is 9 rounds, so it hears 无畏者 and then 骑士之日
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT, round: 7 }, combatTrackFor(7)), 'combat:1');
assert.equal(bgmKeyFor('game', { phase: PHASE.COMBAT, round: 8 }, combatTrackFor(8)), 'combat:0');
// the index↔track mapping this table assumes, from docs/ASSETS.md: combatAlts[0] = m_bat_kazimierz2_1 骑士之日,
// combatAlts[1] = m_bat_kazimierz2_2 无畏者 (a reordering upstream breaks this test, not the players' ears)
const alt = manifest.audio.bgm.combatAlts;
assert.ok(alt[0].loop.includes('m_bat_kazimierz2_1'), `combatAlts[0] is 骑士之日: ${alt[0].loop}`);
assert.ok(alt[1].loop.includes('m_bat_kazimierz2_2'), `combatAlts[1] is 无畏者: ${alt[1].loop}`);
});
test('resolveBgm uses the manifest (boss fallback, intro optional)', () => {
const lobby = resolveBgm(manifest, 'lobby');
assert.ok(lobby && typeof lobby.loop === 'string');
const b4 = resolveBgm(manifest, 'boss:boss_4');
assert.equal(b4.loop, manifest.audio.bossBgm.boss_4.loop);
assert.equal(resolveBgm(manifest, 'boss:nope').loop, manifest.audio.bgm.boss.loop);
// 开战 BGM: combat:<i> → bgm.combatAlts[i], and back to the default combat track when the index (or the whole
// array, e.g. an older manifest) is missing
const alts = manifest.audio.bgm.combatAlts;
assert.ok(Array.isArray(alts) && alts.length >= 2, 'manifest carries the mode’s own battle tracks');
assert.equal(resolveBgm(manifest, 'combat:0').loop, alts[0].loop);
assert.equal(resolveBgm(manifest, 'combat:1').loop, alts[1].loop);
assert.notEqual(alts[0].loop, manifest.audio.bgm.combat.loop, 'a real battle track, not the shop loop');
assert.equal(resolveBgm(manifest, 'combat:9').loop, manifest.audio.bgm.combat.loop);
assert.equal(resolveBgm({ audio: { bgm: { combat: { loop: '/shop.mp3' } } } }, 'combat:0').loop, '/shop.mp3');
assert.equal(resolveBgm(manifest, 'prep').intro, manifest.audio.bgm.prep.intro ?? null);
// 联防's own track (bgm.unite = corrosion, the official escaped levels' bgmEvent), and the fallback for an older
// manifest that has no `unite` entry (the music must not go silent)
@@ -57,6 +92,49 @@ describe('bgm selection', () => {
assert.equal(resolveBgm(manifest, null), null);
assert.equal(resolveBgm(manifest, 'nope'), null);
});
test('installAudio: the round\'s own 开战 track, fixed per round and the same on every client', () => {
const calls = [];
const origPlay = audio.playBgm;
audio.playBgm = (k) => { calls.push(k); };
try {
const pub = (o) => ({ phase: PHASE.PREP, round: 1, stageId: 'st1', players: [{ playerId: 'p1' }], ...o });
const state = { route: 'game', match: { public: pub({}) } };
let fire = null;
/** One client: its own store subscription (the expected keys below are what its own round yields — the point of
* the test is that the track follows the round, never moves inside a battle and never differs between clients). */
const wire = () => installAudio({
getManifest: () => manifest, getState: () => state, selectRoute: (s) => s.route,
subscribe: (fn) => { fire = fn; return () => {}; },
});
wire();
assert.equal(calls.at(-1), 'prep', 'the prep keeps the shop track');
// round 1 ⇒ 无畏者 (combatAlts[1])
state.match.public = pub({ phase: PHASE.COMBAT });
fire(state, {}); assert.equal(calls.at(-1), 'combat:1');
// a re-render / teammate view inside the same battle never moves
fire(state, {}); assert.equal(calls.at(-1), 'combat:1');
// 联防 has its OWN track (#110, the official escaped levels' `bgmEvent = corrosion`) — it does not inherit the
// round's 开战 track
state.match.public = pub({ phase: PHASE.UNITE });
fire(state, {}); assert.equal(calls.at(-1), 'unite');
// …and back to the round's own track in the 作战
state.match.public = pub({ phase: PHASE.COMBAT });
fire(state, {}); assert.equal(calls.at(-1), 'combat:1', "back to the round's own track");
// a second client of the same room hears the same track (its own installAudio, same round)
calls.length = 0; wire();
assert.deepEqual(calls, ['combat:1'], 'every client of the match hears the same track');
// round 7 is the last on 无畏者, round 8 switches to 骑士之日 (combatAlts[0]) — whoever sits in seat 1
state.match.public = pub({ round: 7, phase: PHASE.COMBAT });
fire(state, {}); assert.equal(calls.at(-1), 'combat:1');
state.match.public = pub({ round: 8, phase: PHASE.COMBAT });
fire(state, {}); assert.equal(calls.at(-1), 'combat:0');
state.match.public = pub({ round: 8, phase: PHASE.COMBAT, players: [{ playerId: 'p2' }] });
fire(state, {}); assert.equal(calls.at(-1), 'combat:0', 'the track does not depend on the seats');
// the boss rounds keep their own tracks
state.match.public = pub({ phase: PHASE.FINAL_ASSAULT, bossId: 'boss_4' });
fire(state, {}); assert.equal(calls.at(-1), 'boss:boss_4');
} finally { audio.playBgm = origPlay; }
});
});
describe('SfxLimiter', () => {
+97
View File
@@ -0,0 +1,97 @@
// Issue #142: the real match HUD in the mock harness, using the DOM field so optional art / WebGL is not required.
// SP_E2E=1 CHROME_PATH=/path/to/chrome node --test test/ui/bond-collapse.e2e.test.js
import { test, describe, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const CHROME = process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const ENABLED = process.env.SP_E2E === '1' && existsSync(CHROME);
const OUT = fileURLToPath(new URL('../e2e/out/', import.meta.url));
describe('collapsible bond strip (issue #142)', { skip: !ENABLED && 'set SP_E2E=1 and CHROME_PATH to run' }, () => {
let srv, browser;
before(async () => {
const { startServer } = await import('../../server/index.js');
const puppeteer = (await import('puppeteer-core')).default;
srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true });
browser = await puppeteer.launch({ executablePath: CHROME, headless: true, args: ['--no-sandbox', '--no-proxy-server'] });
mkdirSync(OUT, { recursive: true });
});
after(async () => { await browser?.close(); await srv?.close(); });
for (const [width, height, touch] of [[1920, 1080, false], [640, 360, true]]) {
test(`collapse releases field input and keeps current bonds at ${width}×${height}`, async () => {
const page = await browser.newPage();
const errors = [];
page.on('pageerror', (e) => errors.push(e.message));
const activate = (selector) => touch ? page.tap(selector) : page.click(selector);
const expanded = () => page.$eval('.bonds-toggle', (el) => el.getAttribute('aria-expanded'));
try {
await page.setViewport({ width, height, isMobile: touch, hasTouch: touch });
await page.goto(`${srv.url}/dev/game-mock.html?shot=1&render=fallback&phase=PREP`, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.ff-piece');
await page.waitForSelector('.bslot .bond');
assert.equal(await expanded(), 'true');
const toggle = await page.$eval('.bonds-toggle', (el) => {
const r = el.getBoundingClientRect();
return { width: r.width, height: r.height, controls: el.getAttribute('aria-controls') };
});
assert.ok(toggle.width >= 44 && toggle.height >= 44, 'visible touch target is at least 44×44');
assert.equal(toggle.controls, 'match-bond-strip');
const covered = await page.$eval('.bslot .bond', (el) => {
const r = el.getBoundingClientRect();
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
});
await activate('.bslot .bond');
await page.waitForSelector('.bpop');
await activate('.bonds-toggle');
await page.waitForSelector('#match-bond-strip', { hidden: true });
assert.equal(await expanded(), 'false');
assert.equal(await page.$('.bpop'), null, 'collapsing closes the strip popup');
assert.equal(await page.$eval('.bonds-toggle', (el) => el.getAttribute('aria-label')), '展开盟约');
assert.ok(await page.evaluate(({ x, y }) => !!document.elementFromPoint(x, y)?.closest('.gm__field'), covered),
'the former bond position now hits the field');
await page.evaluate(() => {
globalThis.__fieldPresses = 0;
document.querySelector('.gm__field').addEventListener('pointerdown', () => globalThis.__fieldPresses++);
});
if (touch) await page.touchscreen.tap(covered.x, covered.y);
else await page.mouse.click(covered.x, covered.y);
assert.equal(await page.evaluate(() => globalThis.__fieldPresses), 1, 'pointer input reaches the field');
await page.screenshot({ path: `${OUT}/bonds-collapsed-${width}.png` });
// Hidden content must keep following state changes, including the empty-bonds state.
await page.evaluate(() => globalThis.__MOCK__.mutate((s) => { s.priv.board = []; s.priv.hand.fill(null); }));
await activate('.bonds-toggle');
await page.waitForSelector('.bstrip--empty', { visible: true });
assert.equal(await expanded(), 'true');
await page.focus('.bonds-toggle');
await page.keyboard.press('Space');
await page.waitForSelector('#match-bond-strip', { hidden: true });
assert.equal(await page.evaluate(() => globalThis.__MOCK__.S().priv.ready), false, 'Space on the toggle does not ready the player');
assert.equal(await page.$('.modal'), null, 'Space does not open the funds confirmation');
await page.keyboard.press('Enter');
await page.waitForSelector('.bstrip--empty', { visible: true });
await activate('.bonds-toggle');
await activate('.team__row:not(.is-self) .team__btn');
if (await page.$('.team__ob')) await activate('.team__ob');
await page.waitForSelector('.gm__watching');
assert.equal(await expanded(), 'false', 'watching a teammate preserves the collapse choice');
await activate('.bonds-toggle');
await page.waitForSelector('.bstrip__owner', { visible: true });
assert.ok(await page.$eval('.bstrip', (el) => el.getAttribute('data-owner')), 'expanded bonds belong to the watched teammate');
await activate('.bonds-toggle');
await page.evaluate(() => globalThis.__MOCK__.setPhase('COMBAT'));
await page.waitForSelector('.gm--combat');
assert.equal(await expanded(), 'false', 'phase changes preserve the collapse choice');
await activate('.bonds-toggle');
await page.waitForSelector('.bslot .bond', { visible: true });
assert.equal(await expanded(), 'true');
await page.screenshot({ path: `${OUT}/bonds-expanded-${width}.png` });
assert.deepEqual(errors, []);
} finally { await page.close(); }
});
}
});
+19 -1
View File
@@ -28,7 +28,7 @@ globalThis.fetch = async (url) => {
const { modeOffBonds } = await import('../../public/js/ui/gameLogic.js');
const { BondChips } = await import('../../public/js/ui/detailPanel.js');
const { ChessCard } = await import('../../public/js/ui/shopBar.js');
const { BondPopup } = await import('../../public/js/ui/bondStrip.js');
const { BondPopup, BondStrip } = await import('../../public/js/ui/bondStrip.js');
const { data, getMode } = await import('../../public/js/data.js');
const { DATA, makeMatch, give, legalTileFor } = await import('../match/harness.js');
const { tileKey } = await import('../../server/match/board.js');
@@ -120,3 +120,21 @@ test('the game screen hands the mode-disabled set to the shop bar, the detail ca
assert.match(src, /<\$\{DetailPanel\}[\s\S]*?offBonds=\$\{offBonds\}/);
assert.match(src, /<\$\{BondPopup\}[\s\S]*?off=\$\{offBonds\.has\(bondPop\.bondId\)\}/);
});
test('hidden-layer bonds show activation and tiers without a stack badge or layer count', () => {
for (const bondId of ['maniShip', 'emptyShip', 'soloShip', 'suntShip']) {
const entry = { bondId, active: true, count: 2, tier: 1, layers: 99 };
const pop = BondPopup({ bondId, entry, priv: { board: [], hand: [] }, onClose() {} });
assert.match(textOf(pop), /层数不显示/);
assert.doesNotMatch(textOf(pop), /99|99 层/);
const strip = BondStrip({ bonds: [entry], onOpen() {} });
const disc = [...walk(strip)].find((v) => v.props?.name === DATA.bonds[bondId].name);
assert.ok(disc);
assert.equal(disc.props.layers, undefined);
assert.equal(disc.props.active, true);
assert.equal(disc.props.tier, 1);
}
const disc = [...walk(BondStrip({ bonds: [{ bondId: 'yanShip', active: true, layers: 12 }], onOpen() {} }))]
.find((v) => v.props?.name === DATA.bonds.yanShip.name);
assert.equal(disc.props.layers, 12, 'stacking bonds keep their badge');
});
+72 -5
View File
@@ -7,7 +7,7 @@
//
// The chess: elite 野鬃 (chess_char_1_19_b). Default = S2 夹枪冲锋 (25/40 SP) + module 长枪替补套装 (ATK +40, ASPD +3).
// Chosen here: S1 骑枪刺击 (ON_DEPLOY: "部署后攻击速度+100" for 25 s) + 不装备 — so right after the unit deploys the
// local battle must show the S1 buff (wildmn:s1, ASPD +100, skill active) and the no-module stats (ATK 524, ASPD 100).
// local battle must show S1 active with a draining duration bar, ASPD +100 and the no-module stats (ATK 524, ASPD 100).
// The server is test/e2e/fastServer.mjs with its starter-kit hook (SP_START_CHESS: the elite in the hand at round 1);
// everything else is the real match engine and the real UI driven by real mouse input.
// Screenshots: test/e2e/out/loadout-battle-*.png.
@@ -16,6 +16,9 @@ import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { Client, ROOT, sleep, hasChrome, startRealServer, problemsOf } from '../e2e/client.mjs';
import { makeBattle } from '../helpers/battleHarness.js';
import { UF } from '../../shared/constants.js';
import { COLORS } from '../../public/js/render/style.js';
const ENABLED = process.env.SP_E2E === '1' && hasChrome() && existsSync(path.join(ROOT, 'public/assets'));
const BASE = 'chess_char_1_19_a';
@@ -129,15 +132,16 @@ describe('user playtest #2 item 1 — loadout chosen in the UI fights in the loc
const e = r && [...r._entries.values()].find((x) => x.own && x.battle);
if (!e) return false;
const u = e.battle.allyUnits.find((x) => x.uid === uid);
if (!u || !u.findBuff?.('wildmn:s1')) return false;
const b = u.findBuff('wildmn:s1');
if (!u?.skill?.active || u.skill.id !== 'skchr_wildmn_1') return false;
const tuple = e.battle.snapshot().units.find((t) => t[0] === u.id);
const entry = (e.spec.players || []).flatMap((p) => p.units || []).find((x) => x.uid === uid) || null;
return {
authoritative: e.authoritative, t: e.battle.time,
spec: entry && { chessId: entry.chessId, skillIndex: entry.skillIndex, moduleId: entry.moduleId },
skill: u.skill?.id ?? null, source: u.kit?.skillSource ?? null, active: !!u.skill?.active,
loadout: u.def?.loadout ?? null, atk: u.def?.stats?.atk, aspd: u.def?.stats?.aspd,
buff: { aspd: b.mods?.aspd ?? null },
liveAspd: u.s.aspd, timeLeft: u.skill.timeLeft, duration: u.skill.duration, ready: u.skill.ready,
bar: tuple && { value: tuple[5], max: tuple[6] },
};
}, { timeout: 30000, polling: 100 }, piece.uid).then((h) => h.jsonValue())
.catch(async (err) => {
@@ -157,7 +161,11 @@ describe('user playtest #2 item 1 — loadout chosen in the UI fights in the loc
assert.equal(got.atk, rec.statsBase.atk, 'no module: base ATK (the default module adds +40)');
assert.equal(got.aspd, rec.statsBase.aspd, 'no module: base ASPD');
assert.notEqual(rec.stats.atk, rec.statsBase.atk, 'control: the default module would change ATK');
assert.equal(got.buff.aspd, s1.bb.attack_speed, 'S1 signature: ASPD +100 on deploy');
assert.equal(got.liveAspd - got.aspd, s1.bb.attack_speed, 'S1 signature: ASPD +100 on deploy');
assert.equal(got.ready, false, 'deployment consumed its sole charge');
assert.equal(got.duration, s1.duration);
assert.equal(got.bar.max, s1.duration, 'zero-SP skill has a duration bar');
assert.ok(Math.abs(got.bar.value - got.timeLeft) <= 0.06);
assert.ok(got.active, 'S1 is active right after deploying');
assert.ok(got.t < 10, `S1 fires on deploy (t = ${got.t}), long before S2 could charge (25/40 SP)`);
await sleep(800);
@@ -171,4 +179,63 @@ describe('user playtest #2 item 1 — loadout chosen in the UI fights in the loc
await srv.stop();
}
});
test('野鬃 S1 duration bar: full on deploy, half remaining, hidden after expiry (real renderer)', { timeout: 120000 }, async () => {
const h = makeBattle({
stageId: 'act2autochess_m01', autoFinish: false, timeLimit: 60,
units: [{ chessId: ELITE, skillIndex: 0, moduleId: 'none', row: 9, col: 5 }],
});
h.b.start();
const u = h.unit(ELITE);
assert.equal(u.skill.id, 'skchr_wildmn_1');
const meta = { ...h.b.fieldMeta(), stageId: 'act2autochess_m01' };
const frames = [];
for (const [name, elapsed, share] of [['full', 0, 1], ['half', u.skill.duration / 2, 0.5], ['ended', u.skill.duration / 2 + h.TICK, 0]]) {
h.run(elapsed);
const { t, ...rest } = h.snapshot();
frames.push({ name, share, snap: { ...rest, t: 'b.snap', gt: t } });
assert.equal(u.skill.ready, false, `${name}: no remaining deploy charge`);
assert.equal(u.skill.active, name !== 'ended');
}
const srv = await startRealServer();
const P = (await import('puppeteer-core')).default;
const c = new Client(P, srv.base, 'duration', { prefix: 'loadout-battle' });
try {
await c.open();
await c.page.goto(`${srv.base}/dev/render-demo.html?scene=normal-m01&paused=1&panel=0`);
await c.page.waitForFunction('window.__demo && (window.__demo.ready || window.__demo.error)', { timeout: 30000 });
assert.equal(await c.page.evaluate(() => window.__demo.error ?? null), null);
for (const f of frames) {
await c.page.evaluate((meta, snap) => {
const v = window.__demo.view;
v.enterBattle(meta);
v.setCamera('normal', { rect: meta.rect, side: 'L', instant: true });
v.setLocalFeed({ on: true, speed: 2 });
v.pushSnapshot(snap);
}, meta, f.snap);
await c.page.waitForFunction((id, spMax) => {
const x = window.__demo.view.debug.views.get(id);
return x?.spineReady && x.hud.visible && x.hud.alpha > 0.99 && x.hpFill.visible && x.spMax === spMax;
}, { timeout: 15000 }, u.id, f.snap.units.find((t) => t[0] === u.id)[6]);
const bar = await c.page.evaluate((id) => {
const x = window.__demo.view.debug.views.get(id);
return { visible: x.spFill.visible, bg: x.spBg.visible, share: x.spFill.width / (x.spBg.width - 2),
tint: x.spFill.tint, flags: x.flags, glow: x.spGlow.visible, sp: x.sp, max: x.spMax };
}, u.id);
assert.equal(bar.glow, false, `${f.name}: no ready highlight`);
assert.equal(!!(bar.flags & UF.SKILL), f.name !== 'ended');
assert.equal(bar.visible, f.name !== 'ended');
assert.equal(bar.bg, f.name !== 'ended');
if (bar.visible) {
assert.ok(Math.abs(bar.share - f.share) < 0.002, `${f.name}: drawn share ${bar.share}`);
assert.equal(bar.tint, COLORS.spActive);
} else assert.deepEqual([bar.sp, bar.max], [0, 0]);
await c.shot(`duration-${f.name}`);
}
assert.deepEqual(problemsOf([c]), []);
} finally {
await c.close();
await srv.stop();
}
});
});
+11
View File
@@ -41,6 +41,17 @@ describe('the scouted prep board renders the hand like the own bench', () => {
assert.match(app, /function scoutItemInfo\(info\)/);
});
test('ItemView syncs battle samples: a hand item rides the scouted field without breaking syncBattle (render layer: source)', () => {
// syncBattle syncs EVERY unit of the field's snapshot (render/app.js `v.sync(s, renderT)`); the ItemView class
// needs a sync of its own or the frame loop throws `v.sync is not a function` on every tick and the scouted
// board's operators never draw (found in playtesting: only teammates with an item in hand / temp broke).
const units = read('public/js/render/units.js');
const start = units.indexOf('export class ItemView');
assert.ok(start > 0, 'ItemView exists');
const body = units.slice(start, units.indexOf('export class ', start + 1));
assert.match(body, /\bsync\(s\) \{ this\.x = s\.x; this\.y = s\.y; \}/);
});
test('tapping a held item on the scouted board opens its card (resolveDetail unit → item)', () => {
const IT = 'chess_item_1_01_e_a';
const d = resolveDetail({ kind: 'unit', unit: { id: 3, kind: 'item', side: 'ally', ownerId: 'p2', defId: IT } }, new Map());
+16
View File
@@ -29,6 +29,14 @@ import { EMOTE_CATALOG } from '../../shared/constants.js';
* models come from the local client only (tools/local-extract ENEMY_SPINES): `localEnemySpines` adds them as the
* optional `spineLocal` overlay, which the client draws when data/local-assets.json lists its files (user feedback
* after 0.1.0, D3: 灼热源石虫 / 炽焰源石虫 were drawn as the plain 源石虫 everywhere).
*
* Why no dump carries them: isHarryh/Ark-Models *indexes* `1305_mhslim` / `1305_mhslim_2` but with an EMPTY
* `assetList` — registered, never uploaded — so `arkModel()` returns nothing for them and the alias chain below falls
* back to `enemy_1007_slime`: a different enemy rather than a variant of it, which is why the renderer tints it
* (render/units.js ALIAS_TINT). The *mobile* build does ship their own model
* (`enemy_spine/<enemyId>/<enemyId>.{skel,atlas,png}`), but the only public mirror of that build is a community wiki,
* not a GitHub dump, so downloading from it would add a source the project deliberately does not use (docs/ASSETS.md
* "Enemy aliases"). The tinted alias therefore stays the web model until a GitHub dump carries these two.
*/
export const ENEMY_SPINE_ALIAS = Object.freeze({
enemy_1305_mhslim: 'enemy_1007_slime',
@@ -497,11 +505,19 @@ export function buildPlan({ assets07, ops03, enemies05, maps05, audio, modelsDat
// bank is missing (the client then falls back to `bgm.combat`; public/js/audio.js resolveBgm), so the manifest stays
// valid for an older audio_data.
const unite = flatBgm(bgmLeaf('battle.ON_GAME_READY.corrosion'));
// 开战 BGM: the two official battle tracks of the mode's own 卡西米尔 act (music_act13side_1 骑士之日 /
// music_act13side_2 无畏者, 塞壬唱片). The client does not draw them: the round decides (audio.js combatTrackFor —
// 无畏者 for rounds 1–7, 骑士之日 from round 8 on), so index 0 must stay `bat_kazimierz2_1` and index 1
// `bat_kazimierz2_2` (test/ui/audio.test.js pins both the order and the round table).
// Kept out of `bgm` below when the index has neither bank, so the manifest stays valid for an older audio_data.
const combatAlts = ['battle.ON_GAME_READY.bat_kazimierz2_1', 'battle.ON_GAME_READY.bat_kazimierz2_2']
.map((n) => flatBgm(bgmLeaf(n))).filter(Boolean);
const bgm = {
lobby: flatBgm(bgmLeaf('sys.ON_ACTIVITY_LOADED.act2autochess')),
prep: flatBgm(bgmLeaf('battle.ON_GAME_READY.act1autochess_shop')),
combat: flatBgm(bgmLeaf('battle.ON_GAME_READY.act1autochess_shop')),
boss: flatBgm(bgmLeaf('battle.ON_GAME_READY.rglk1phantomcastle')),
...(combatAlts.length ? { combatAlts } : {}),
...(unite ? { unite } : {}),
};
const bossBgm = {};