feedback5 WM: merge feedback4 (896dd5d, the coming 0.1.4) — PRs #150 #66 #146 #74 #72 #103 #149 #109 #160 ported into the split layout

Ported by hand:
- #109 kit hunks → kits/ops (the tier files stay re-export shims): 宴 chess_char_1_18-utage.js, 野鬃
  chess_char_1_19-wildmn.js, 砾 chess_char_2_12-gravel.js, 斯卡蒂 chess_char_3_05-skadi.js, 伊内丝
  chess_char_4_04-ines.js, 缄默德克萨斯 chess_char_4_16-texas2.js, 耀骑士临光 chess_char_6_17-nearl2.js. The +/- lines
  are identical to #109's tier1/2/3/4/6 diffs.
- #109 generic.js against W5D's own-duration passives (一击即退): the deployment window is computed where #109 computes
  it, with W5D's case included. Both forms run #109's duration lifecycle (activateOnDeploy, spType 'none', the
  skill's own mods), and W5D's 冲锋号令 DP start effect is kept.
- #103 room.js through i18n phase 1: t('{name}邀请你加入卫戍协议:盟约【{difficulty}】') with the translated difficulty
  name (the Chinese text is the PR's), plus its English line in public/i18n/en.json.
- data/assets.json: feedback5's manifest plus #72's audio.bgm.combatAlts (after boss, the plan order). files +3 and
  bytes +5 037 595 as measured on feedback4; the hash is recomputed with the generator's contentHash. No other data/
  file changed.
- docs: DESIGN §5.6 (#109's timed deployment paragraph before feedback5's trigger rules; "Permanent passive skills are
  always on"), SIM §7.4 (both generic passive cases).
- Auto-merged and checked: #149 bond-bar fold (still in screens/game.js), #160 skills.js / spine.js, #109 skills.js /
  snapshot.js, #66 addon/battle.js (restTile / isDown live in sim/battle/tiles.js), #150 units.js, #72 audio.js /
  plan.mjs, #74 plan.mjs / docs.

Where the PRs met feedback5 work:
- 突袭 (W5L, GitHub #49): raidPoll counts a skill that needs no SP while it is on. That is a passive, as before, or a
  #109 deploy-timed skill while its window runs. Once the window has ended only the idle trigger is left. Without this
  change 缄默德克萨斯 / 宴 lost #49. research 02 §3.18 says so.
- Tests changed to #109's representation: bonds_addon #49 test (kind 'duration', activateOnDeploy) plus a new test
  (window over → idle trigger only), and standin.test.js's 预备干员 PASSIVE branch (the skill's mods instead of the
  generic:passive buff).

Golden regenerated on the merged code. Attribution: each PR's gameplay change was reverted alone in a scratch copy, and
all three together. With all three reverted the digests equal feedback5's exactly. Every moved scenario was replayed
and fields the unit that explains it.
- #160 only (0.5 s skill events of deploy passives; events.skill only): roster-001/006/025/049, bond-arcaneShip-low,
  bond-emptyShip-high, bond-investShip-high, bond-kjeragShip-low, bond-siracusaShip-low/high, bond-victoriaShip-low,
  bond-yanShip-high, boss-boss_2/4/6-solo, hidden-boss_8/10-solo, standin-03/05/08/10, diy-005/006 (普罗旺斯 S1,
  琳琅诗怀雅 S1/S2, 星熊 S2, 凯瑟琳 S1, Misery S3 as stand-in / 自选 prototype).
- #109 only (deploy-timed skill state and lifecycle; 突袭 技能就绪 limited to the window; generic 一击即退):
  roster-003/004/005/021/023/024/039/041/042/046/047, bond-egirShip-high, bond-indomShip-high,
  bond-kazimierzShip-low/high, hidden-boss_8-pair, standin-01, coop4-FUNNY-6, coop2-NORMAL-8-serverrun.
- #66 only (灵巧's aura stays only for a killed member, from its body tile): roster-028, coop2-NORMAL-3,
  coop3-ABYSS-5, coop4-ABYSS-7, solo-HARD-9-boosted.
- #109 + #160: roster-002, roster-029, bond-investShip-low, bond-raidShip-low/high, bond-suntShip-low,
  bond-visiShip-high.
- #66 + #160: roster-008, roster-027.
- #66 + #109: roster-022, roster-040, bond-indomShip-low, boss-boss_2/4/6-pair, hidden-boss_10-pair, standin-06,
  coop2-HARD-4, coop2-NORMAL-14-standins.
- all three: roster-010, roster-012, roster-031.
69 scenarios. Every other digest is unchanged.
This commit is contained in:
sganggs committed 2026-10-05 23:59:05 +08:00
commit a0a7660287
55 files changed
+2140 -736

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"version":1,"hash":"c259e54bcb92","generator":"tools/fetch-assets.mjs","stats":{"files":5310,"bytes":415518495,"chars":210,"charsWithBack":207,"enemies":252,"enemiesWithSpine":251,"tokens":58,"tokensWithSpine":13,"spineModels":673,"bonds":23,"items":59,"bands":40,"skills":525,"modules":131,"ui":602,"sfxUnits":484},"chars":{"char_003_kalts":{"avatar":"/assets/char/avatar/char_003_kalts.png","avatarE2":"/assets/char/avatar/char_003_kalts_2.png","portrait":"/assets/char/portrait/char_003_kalts_1.png","portraitE2":"/assets/char/portrait/char_003_kalts_2.png","spine":{"front":{"skel":"/assets/spine/op/char_003_kalts/front/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/front/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/front/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Die":1,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":-227.579,"y":-138.244,"width":532.04,"height":551.479}},"back":{"skel":"/assets/spine/op/char_003_kalts/back/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/back/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/back/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":410.785,"y":-2.26,"width":531.785,"height":416.492}}}},"char_010_chen":{"avatar":"/assets/char/avatar/char_010_chen.png","avatarE2":"/assets/char/avatar/char_010_chen_2.png","portrait":"/assets/char/portrait/char_010_chen_1.png","portraitE2":"/assets/char/portrait/char_010_chen_2.png","spine":{"front":{"skel":"/assets/spine/op/char_010_chen/front/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/front/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/front/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill_2","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Die":1,"Idle":2,"Skill":1,"Skill_2":1.167,"Skill_3":2.933,"Skill_End":0.6,"Skill_End_2":0.6,"Skill_End_3":0.3,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467],"Skill_2":[0.467],"Skill_3":[0.6,0.8,1,1.2,1.4,1.6,1.8,2,2.2,2.833,2.933]},"bounds":{"x":-322.335,"y":-109.274,"width":663.549,"height":524.806}},"back":{"skel":"/assets/spine/op/char_010_chen/back/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/back/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/back/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill","end":"Skill_End","index":1,"idle":null},"2":{"begin":null,"loop":"Skill","end":"Skill_End","index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Idle":2,"Skill":1,"Skill_End":0.6,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467]},"bounds":{"x":-173.082,"y":-13.829,"width":315.887,"height":404.623}}}},"char_017_huang":{"avatar":"/assets/char/avatar/char_017_huang.png","avatarE2":"/assets/char/avatar/char_017_huang_2.png","portrait":"/assets/char/portrait/char_017_huang_1.png","portraitE2":"/assets/char/portrait/char_017_huang_2.png","spine":{"front":{"skel":"/assets/spine/op/char_017_huang/front/char_017_huang.skel","atlas":"/assetLine truncated
{"version":1,"hash":"9f8bca0934cd","generator":"tools/fetch-assets.mjs","stats":{"files":5313,"bytes":420556090,"chars":210,"charsWithBack":207,"enemies":252,"enemiesWithSpine":251,"tokens":58,"tokensWithSpine":13,"spineModels":673,"bonds":23,"items":59,"bands":40,"skills":525,"modules":131,"ui":602,"sfxUnits":484},"chars":{"char_003_kalts":{"avatar":"/assets/char/avatar/char_003_kalts.png","avatarE2":"/assets/char/avatar/char_003_kalts_2.png","portrait":"/assets/char/portrait/char_003_kalts_1.png","portraitE2":"/assets/char/portrait/char_003_kalts_2.png","spine":{"front":{"skel":"/assets/spine/op/char_003_kalts/front/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/front/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/front/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Die":1,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":-227.579,"y":-138.244,"width":532.04,"height":551.479}},"back":{"skel":"/assets/spine/op/char_003_kalts/back/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/back/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/back/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":410.785,"y":-2.26,"width":531.785,"height":416.492}}}},"char_010_chen":{"avatar":"/assets/char/avatar/char_010_chen.png","avatarE2":"/assets/char/avatar/char_010_chen_2.png","portrait":"/assets/char/portrait/char_010_chen_1.png","portraitE2":"/assets/char/portrait/char_010_chen_2.png","spine":{"front":{"skel":"/assets/spine/op/char_010_chen/front/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/front/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/front/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill_2","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Die":1,"Idle":2,"Skill":1,"Skill_2":1.167,"Skill_3":2.933,"Skill_End":0.6,"Skill_End_2":0.6,"Skill_End_3":0.3,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467],"Skill_2":[0.467],"Skill_3":[0.6,0.8,1,1.2,1.4,1.6,1.8,2,2.2,2.833,2.933]},"bounds":{"x":-322.335,"y":-109.274,"width":663.549,"height":524.806}},"back":{"skel":"/assets/spine/op/char_010_chen/back/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/back/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/back/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill","end":"Skill_End","index":1,"idle":null},"2":{"begin":null,"loop":"Skill","end":"Skill_End","index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Idle":2,"Skill":1,"Skill_End":0.6,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467]},"bounds":{"x":-173.082,"y":-13.829,"width":315.887,"height":404.623}}}},"char_017_huang":{"avatar":"/assets/char/avatar/char_017_huang.png","avatarE2":"/assets/char/avatar/char_017_huang_2.png","portrait":"/assets/char/portrait/char_017_huang_1.png","portraitE2":"/assets/char/portrait/char_017_huang_2.png","spine":{"front":{"skel":"/assets/spine/op/char_017_huang/front/char_017_huang.skel","atlas":"/assetLine truncated
+19 -10
View File
@@ -86,7 +86,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` |
@@ -157,11 +157,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,
@@ -272,10 +274,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
@@ -305,7 +305,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), `ACTIVE_RANGE` (the owner's deliberate deviation of 2026-10-05, 0.2.0: a MANUAL skill on the basic strategy — or, by the owner's further decisions of the same day, on the `SEARCH` row, and 深巡 S2 on top of its §21.29 DEFAULT — whose attack range while it runs strictly contains the operator's own range — its rangeId grid, or the own range grown by `ability_range_forward_extend` — checks the DEFAULT condition on that running range every tick, no attack needed; `customRangeGrid` = the running range, `rawRule` the official row (DEFAULT, SEARCH, 深巡's TAKE_DAMAGE); 39 skills / 78 records, of them 薄绿 S1, 蜜蜡 S1, 卡涅利安 S3, 玛恩纳 S2 and 安洁莉娜 S3 from SEARCH; 烛煌 S2 / S3, whose ranges do not contain her 3-1, keep DEFAULT), `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 — 深巡 S2 then `ACTIVE_RANGE` on its 3-2; and 余 S2, `SKILL_RANGE` on its x-1 (§22.10); the 重装 of data/backups.json — the 补位 stand-ins 预备干员-重装 / Mechanist and every 自选 重装 — cast with an enemy in range, `DEFAULT` (or `ACTIVE_RANGE` when the skill's running range is larger), by the owner's decisions of 2026-10-05), `SP_FULL` (immediately — 执旗手 / 战术家 / 吟游者 MANUAL skills, AUTO DP skills such as 焰尾 S1, 凛御银灰 S1 (伺夜 S1 / S2 too, but only while his pack is on the field — PRTS 备注 「仅场上存在狼群时可触发技能」, the kit's own cast; the owner's decision of 2026-10-05 for S1; S1 also while the pack sits in its 战术点形态, which the cast ends — S2 waits for it to come back [ASSUMED]), and, by their kits, AUTO skills whose effects act on allies only: 引星棘刺 S1, 浊心斯卡蒂 S2, 纯烬艾雅法拉 S1 — the owner's decision of 2026-10-05 for the last), `CUSTOM_RANGE` (enemy inside the custom grid), `SEARCH` (an enemy inside the initial range, checked every tick — no attack needed; 解放者 / 阵法术师 MANUAL skills and 安洁莉娜 S2, those without a larger running range), `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), `ACTIVE_RANGE` (the owner's deliberate deviation of 2026-10-05, 0.2.0: a MANUAL skill on the basic strategy — or, by the owner's further decisions of the same day, on the `SEARCH` row, and 深巡 S2 on top of its §21.29 DEFAULT — whose attack range while it runs strictly contains the operator's own range — its rangeId grid, or the own range grown by `ability_range_forward_extend` — checks the DEFAULT condition on that running range every tick, no attack needed; `customRangeGrid` = the running range, `rawRule` the official row (DEFAULT, SEARCH, 深巡's TAKE_DAMAGE); 39 skills / 78 records, of them 薄绿 S1, 蜜蜡 S1, 卡涅利安 S3, 玛恩纳 S2 and 安洁莉娜 S3 from SEARCH; 烛煌 S2 / S3, whose ranges do not contain her 3-1, keep DEFAULT), `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 — 深巡 S2 then `ACTIVE_RANGE` on its 3-2; and 余 S2, `SKILL_RANGE` on its x-1 (§22.10); the 重装 of data/backups.json — the 补位 stand-ins 预备干员-重装 / Mechanist and every 自选 重装 — cast with an enemy in range, `DEFAULT` (or `ACTIVE_RANGE` when the skill's running range is larger), by the owner's decisions of 2026-10-05), `SP_FULL` (immediately — 执旗手 / 战术家 / 吟游者 MANUAL skills, AUTO DP skills such as 焰尾 S1, 凛御银灰 S1 (伺夜 S1 / S2 too, but only while his pack is on the field — PRTS 备注 「仅场上存在狼群时可触发技能」, the kit's own cast; the owner's decision of 2026-10-05 for S1; S1 also while the pack sits in its 战术点形态, which the cast ends — S2 waits for it to come back [ASSUMED]), and, by their kits, AUTO skills whose effects act on allies only: 引星棘刺 S1, 浊心斯卡蒂 S2, 纯烬艾雅法拉 S1 — the owner's decision of 2026-10-05 for the last), `CUSTOM_RANGE` (enemy inside the custom grid), `SEARCH` (an enemy inside the initial range, checked every tick — no attack needed; 解放者 / 阵法术师 MANUAL skills and 安洁莉娜 S2, those without a larger running range), `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
@@ -314,6 +321,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
@@ -447,6 +455,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 {...}`
@@ -518,6 +528,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).
@@ -1286,7 +1298,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).
@@ -2138,3 +2150,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
@@ -765,6 +765,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
@@ -99,6 +99,8 @@
干员属于 1–2 个盟约。场上**不同名**的成员达到人数后激活(大多只数棋盘上的,「远见」「奇迹」「投资人」连整备区也算)。
盟约栏挡住棋盘时,点左侧的「收起」隐藏列表,再点「盟约」展开。收起会关闭从盟约栏打开的详情,原来图标覆盖的位置可以直接点选或拖动棋子;本局切换回合、查看队友时保留收起状态。
- **核心盟约**(8 个势力:炎、萨尔贡、维多利亚、谢拉格、拉特兰、阿戈尔、叙拉古、卡西米尔):3 名激活,6 名获得强力的额外效果。
- **附加盟约**(精准、迅捷、灵巧、奥术、坚守、助力、远见、奇迹、投资人、突袭、不屈、调和、协防干员、独行、绝技):多为 2 / 3 名激活;「独行」需要场上**恰好 1 名**,「绝技」按场上的精锐数量计算,没有势力盟约的干员算「协防干员」。
- **本局禁用**:「标准模拟」与官方相同,关闭拉特兰、阿戈尔、卡西米尔、灵巧、奥术、奇迹、投资人、突袭、独行、绝技这 10 个盟约,它们不会激活。你有这些盟约的成员时(奇迹、投资人也算整备区里的成员),盟约栏末尾显示**灰色带 ✕ 的盟约图标和「本局禁用」**,干员卡、盟约标签和盟约详情也这样标注。「远见」「奇迹」「投资人」计入整备区,盟约详情的「成员」人数也一样计入整备区里的成员(临时整备区不算)。
+27 -4
View File
@@ -160,6 +160,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
@@ -854,6 +856,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
@@ -903,6 +910,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).
@@ -930,9 +948,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)
@@ -975,9 +994,11 @@ 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; for the skill's own `duration` from every deployment when an ON_DEPLOY passive says "部署后…" with no
`duration` key — 一击即退, 10 s) and the self/counter effects — their scales describe procs that need a kit.
`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), and so does an ON_DEPLOY passive
that says "部署后…" with no `duration` key, for the skill's own `duration` (一击即退, 10 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.
"立即获得N点部署费用" + `cost` ⇒ +cost DP at the start (冲锋号令); such an AUTO skill with nothing else to do fires at full
SP (`trigger: 'SP_FULL'`, as 德克萨斯's kit casts it). `genericTalents(def)` — for 补位 stand-ins without a kit only — applies
every unconditional stat talent ("攻击力+8%", "防御力+10%", "攻击速度+9"; `statTalentMods`: only stat clauses whose numbers
@@ -1200,6 +1221,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
+3 -1
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
@@ -920,7 +922,7 @@ Members (5 chess, 4 in current shop pool; by tier in shop: {'2': 1, '3': 1, '5':
**Implementable spec**
- **[2 distinct]** If a 突袭 member has not attacked for 10 s, or its skill is ready, and no enemy is in its range: it is immediately redeployed (keeping SP) onto a tile next to a ground enemy; while in that redeployed state its ATK and maxHP x(1 + 0.25 + 0.01*L). A passive skill that is on counts as ready (GitHub #49: the reporter's footage shows 缄默德克萨斯 jumping within her passive's 10 s; the engine keeps a passive on for the whole deployment [ASSUMED]); the landing tile must have the target in range (GitHub #51 [ASSUMED]).
- **[2 distinct]** If a 突袭 member has not attacked for 10 s, or its skill is ready, and no enemy is in its range: it is immediately redeployed (keeping SP) onto a tile next to a ground enemy; while in that redeployed state its ATK and maxHP x(1 + 0.25 + 0.01*L). A passive skill that is on counts as ready (GitHub #49: the reporter's footage shows 缄默德克萨斯 jumping within her passive's 10 s; the engine keeps a passive on for the whole deployment [ASSUMED]), and so does a deploy-timed skill while its window runs (#109 made 宴 S2, 斯卡蒂 S2, 伊内丝 S3, 缄默德克萨斯 S1–S3 and 耀骑士临光 S2 duration skills that start at each deployment; once the window has ended only the idle trigger is left); the landing tile must have the target in range (GitHub #51 [ASSUMED]).
- **[L >= 50]** ALL operators ASPD +50.
- Formulas: `atkHpMultiplier = 1.25 + 0.01*L`; `idleSec = 10`; `aspdAll(L>=50) = 50`
- How layers are gained: 史尔特尔 部署时 +8 (<=50/battle); 休谟斯 each 2 kills +1; 瑕光 +4/deploy; 斯卡蒂; 伊内丝 +5; 机变 "斯卡蒂的盟誓" +8, "德克萨斯的盟誓" +10.
+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); }
+1
View File
@@ -83,6 +83,7 @@
"移除 AI 队友": "Remove AI Teammate",
"已复制同盟密钥 {code}": "Alliance Key {code} copied",
"已复制邀请链接": "Invite link copied",
"{name}邀请你加入卫戍协议:盟约【{difficulty}】": "{name} invites you to Stronghold Protocol: Alliance [{difficulty}]",
"复制失败,请手动复制": "Copy failed, please copy manually",
"同盟密钥": "Alliance Key",
"同盟密钥 {code}": "Alliance Key {code}",
+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
@@ -163,6 +163,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);
@@ -1211,8 +1212,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}
+5 -3
View File
@@ -155,9 +155,11 @@ 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));
// the copied link carries an invitation line (#103): 「{name}邀请你加入卫戍协议:盟约【{difficulty}】」
const invite = t('{name}邀请你加入卫戍协议:盟约【{difficulty}】', { name: name ?? '', difficulty: DIFFICULTY_NAMES[difficulty] ? t(DIFFICULTY_NAMES[difficulty]) : '' });
const ok = await copyText(what === 'code' ? code : `${inviteLink(code)} ${invite}`);
if (ok) toast(what === 'code' ? t('已复制同盟密钥 {code}', { code }) : t('已复制邀请链接'), 'success');
else toast(t('复制失败,请手动复制'), 'warn');
};
@@ -282,7 +284,7 @@ export function RoomScreen() {
<h1 class="topbar__title">${coop ? t('同盟模拟') : t('独立模拟')}<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>${t('仅限 1 名博士')}</span></div>`}
${coop ? html`<${InviteBox} code=${room.code} name=${me.name} difficulty=${room.difficulty} />` : html`<div class="solo-note"><${MicroLabel}>SINGLE OPERATOR<//><span>${t('仅限 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">
+15 -6
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, 同名效果取最高:
@@ -31,7 +32,7 @@
// + 脆弱 ×damage_scale for weak[limit] s
// 助力 deputShip all operators DEF +(base + per·L), redeploy time ×(1 + respawn_time)
// 突袭 raidShip member idle ≥ no_attack_duration s (or skill ready — a passive skill that is on counts, GitHub
// #49) with no enemy in range → "保留技力立即再部署"
// #49, and so does a deploy-timed skill while it runs, #109) with no enemy in range → "保留技力立即再部署"
// next to the most advanced ground enemy it can reach: on a free tile its position may be deployed
// on from which its range covers that enemy (GitHub issue #51 [ASSUMED]: the first of the 8 most
// advanced that has such a tile; none → it stays and the next poll looks again, never a jump that
@@ -220,10 +221,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);
}
}
@@ -317,8 +322,12 @@ function raidPoll(battle, st) {
// 缄默德克萨斯 jumping within her passive's 10 s with no enemy in range). The engine keeps a passive on for the whole
// deployment, so such a member may jump whenever nothing is in its range [ASSUMED: "技能就绪" of a passive = the
// skill being on]; the landing rule below keeps it from hopping. Here only — the global `ready` stays as it is.
// A deploy-timed skill (#109: kind 'duration' with no SP — spType 'none' — run from the deployment: 宴 S2, 斯卡蒂 S2,
// 伊内丝 S3, 缄默德克萨斯 S1–S3, 耀骑士临光 S2, the generic "部署后…N秒内" passives) counts the same way while its
// window runs — the 10 s of the reporter's footage; once it has ended only the idle trigger is left.
const sk = u.skill;
const ready = !!(sk && !sk.noSkill && (sk.ready || (sk.kind === 'passive' && sk.active)) && !(sk.active && sk.isTimed));
const deploySkillOn = !!(sk && sk.active && (sk.kind === 'passive' || sk.spType === 'none'));
const ready = !!(sk && !sk.noSkill && ((sk.ready && !(sk.active && sk.isTimed)) || deploySkillOn));
const idleOk = battle.time - since >= idle - 1e-9;
if (!(ready || idleOk)) continue;
if (battle.enemiesInKeys(u.rangeKeys || [], u, u.profile).length) continue;
+20 -18
View File
@@ -28,9 +28,10 @@
// "附带…凋亡/灼燃/神经损伤" + 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秒内": 宴; for the skill's own duration
// when an ON_DEPLOY passive says "部署后…" without one in its blackboard: 一击即退, duration 10 — PRTS 技能 持续 10) and
// the self/counter effects above — their scales describe procs (bombs, sword rain, counters) that need a kit.
// Passive skills with stat mods + bb.duration + "N秒内" use a deployment duration (宴), and so do the ON_DEPLOY passives that
// say "部署后…" with the skill's own duration and none in their blackboard (一击即退, duration 10 — PRTS 技能 持续 10):
// kind 'duration', activateOnDeploy (#109); 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.
// "立即获得N点部署费用" + cost ⇒ +cost DP for the player at the start (冲锋号令); an AUTO skill that does nothing else
// fires as soon as its SP is full (as 德克萨斯's kit casts the same skill: kits/ops/chess_char_1_08-texas.js).
// genericTalents(def): the talents a generic kit can apply exactly — an unconditional stat line ("攻击力+8%",
@@ -112,10 +113,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);
@@ -154,9 +153,21 @@ 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';
// a passive stat buff limited in time ("部署后…在14秒内攻击力+65%", 宴) runs as a duration skill from every deployment;
// so does an ON_DEPLOY passive whose duration is the skill's own (一击即退 "部署后攻击力+X%,防御力+Y%", duration 10, no bb key)
const ownDuration = num(bb.duration) === undefined && sk.duration > 0 && /^部署后/.test(desc) ? sk.duration : 0;
const passiveTimed = passive && Object.keys(mods).length
? (num(bb.duration) > 0 && /\d+(\.\d+)?秒内/.test(desc) ? num(bb.duration) : ownDuration)
: 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');
@@ -278,17 +289,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; so do the
// ON_DEPLOY passives whose duration is the skill's own (一击即退 "部署后攻击力+X%,防御力+Y%", duration 10, no bb key)
const ownDuration = num(bb.duration) === undefined && sk.duration > 0 && /^部署后/.test(desc) ? sk.duration : 0;
const passiveTimed = passive && Object.keys(mods).length
? (num(bb.duration) > 0 && /\d+(\.\d+)?秒内/.test(desc) ? num(bb.duration) : ownDuration)
: 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 = {};
@@ -303,10 +309,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'] }));
}
if (gainsDp) {
starts.push(({ battle, unit }) => {
battle.addDp(unit.ownerId, dpGain);
@@ -12,7 +12,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: {
@@ -24,16 +23,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', () => {
@@ -25,11 +25,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: {
@@ -15,7 +15,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);
@@ -34,10 +34,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;
@@ -52,6 +53,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) {
@@ -20,10 +20,8 @@ export default {
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: [
@@ -39,21 +39,9 @@ export default withDefaults({
},
}),
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;
@@ -77,6 +65,7 @@ export default withDefaults({
}
battle.fx('beam', { x: ax, y: ay, tx: bx, ty: by, id: unit.id });
},
onEnd({ battle, unit }) { battle.removeBuff(unit, 'ines:s3'); },
}),
}),
skill: {
@@ -146,11 +135,27 @@ export default withDefaults({
],
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 });
}
@@ -18,17 +18,12 @@ export default withDefaults({
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'] });
@@ -39,16 +34,14 @@ export default withDefaults({
};
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))))) {
@@ -57,28 +50,62 @@ export default withDefaults({
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) => {
@@ -94,7 +121,7 @@ export default withDefaults({
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'] }) });
@@ -103,7 +130,7 @@ export default withDefaults({
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
@@ -46,23 +46,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
@@ -57,6 +57,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
@@ -216,12 +222,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() {
@@ -454,7 +475,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
@@ -96,13 +96,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));
+157 -5
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);
});
@@ -414,8 +546,9 @@ test('突袭 #51: the most advanced enemy out of reach → the jump goes to the
// GitHub #49: skills.js `ready` is false for every passive skill, so a passive 突袭 member (缄默德克萨斯, 宴 …) only jumped on
// the 10 s idle trigger; the reporter's footage of the official game shows 缄默德克萨斯 jumping within her passive's 10 s.
// raidPoll (only there) counts a passive skill that is on as 技能就绪; the landing rule (#51) still keeps it from hopping.
test('突袭 #49: a passive skill that is on counts as 技能就绪 — 缄默德克萨斯 jumps within a few seconds, then stays while her enemy is in range', () => {
// raidPoll (only there) counts a passive skill that is on as 技能就绪, and — since #109 made her skills deploy-timed
// duration skills — a deploy-timed skill while its window runs; the landing rule (#51) still keeps it from hopping.
test('突袭 #49: a passive skill that is on, or a deploy-timed skill while it runs (#109), counts as 技能就绪 — 缄默德克萨斯 jumps within a few seconds, then stays while her enemy is in range', () => {
const tex = 'chess_char_4_16_a';
const h = makeBattle({
defs: { enemies: DUMMY }, bonds: { raidShip: bond(1, 10) }, enemies: [{ key: 'enemy_addon_dummy', pos: [9, 8] }],
@@ -423,7 +556,7 @@ test('突袭 #49: a passive skill that is on counts as 技能就绪 — 缄默
});
h.step();
const u = h.unit(tex), e = h.enemy('enemy_addon_dummy');
assert.deepEqual([u.skill.kind, u.skill.ready, u.skill.active], ['passive', false, true], 'her passive: on, and the global `ready` unchanged');
assert.deepEqual([u.skill.kind, !!u.skill.spec.activateOnDeploy, u.skill.ready, u.skill.active], ['duration', true, false, true], 'her deploy-timed skill (#109): on, and the global `ready` false');
assert.ok(!inRange(u, e), 'nothing in her range');
assert.ok(h.runUntil(() => raidJumps(h).length > 0, 3), 'jumped within 3 s (no 10 s idle wait)');
assert.ok(h.b.time < bondBb('raidShip').no_attack_duration - 5, `at ${h.b.time.toFixed(2)} s`);
@@ -433,6 +566,25 @@ test('突袭 #49: a passive skill that is on counts as 技能就绪 — 缄默
checkInvariants(h.b);
});
test('突袭 #49 with #109: a deploy-timed skill counts only while its window runs — once it has ended, only the idle trigger is left', () => {
const tex = 'chess_char_4_16_a';
const idle = bondBb('raidShip').no_attack_duration;
const h = makeBattle({
defs: { enemies: DUMMY }, bonds: { raidShip: bond(1, 10) }, enemies: [{ key: 'enemy_addon_dummy', pos: [9, 8] }],
units: [{ chessId: tex, row: 12, col: 3 }], hooks: ['deploy', 'death'], autoFinish: false, timeLimit: 60,
});
h.step();
const u = h.unit(tex);
u.skill.end('duration'); // her window over before the first poll, as after its duration
assert.deepEqual([u.skill.active, u.skill.ready], [false, false], 'ended: no charge until the next deployment');
h.run(3);
assert.equal(raidJumps(h).length, 0, 'no 技能就绪 jump once the window has ended');
assert.ok(h.runUntil(() => raidJumps(h).length > 0, idle + 1), 'the idle trigger');
assert.ok(h.b.time >= idle - 1e-6, `at ${h.b.time.toFixed(2)} s`);
assert.ok(u.skill.active, 'the landing is a new deployment: a new window');
checkInvariants(h.b);
});
test('突袭 #49: non-passive members unchanged — a charged skill jumps at once, an uncharged one waits for the idle time; a passive one jumps at once', () => {
const idle = bondBb('raidShip').no_attack_duration;
const defs = {
+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
@@ -369,19 +369,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
@@ -1018,7 +1018,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);
@@ -1028,15 +1032,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
@@ -961,6 +961,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);
@@ -968,6 +972,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 }] });
@@ -979,6 +986,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 %', () => {
+3 -1
View File
@@ -231,7 +231,9 @@ test('the eight 预备干员: every chess record they replace fields the stand-i
const dp0 = h.b.players[0].dp;
if (sk.skillType === 'PASSIVE') {
h.step();
assert.deepEqual(u.findBuff(`generic:passive:${sk.skillId}`)?.mods, statMods(sk.bb), `${where}: deploy buff`);
// a deploy-timed passive runs the duration lifecycle from the deployment (#109): the skill's own mods
assert.deepEqual([u.skill.kind, !!u.skill.spec.activateOnDeploy, u.skill.active], ['duration', true, true], `${where}: deploy-timed skill`);
assert.deepEqual(u.findBuff(`skill:${u.id}`)?.mods, statMods(sk.bb), `${where}: deploy buff`);
} else {
assert.ok(h.runUntil(() => u.skill.activations > 0, 30), `${where}: the skill fires`);
h.step();
+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)
+81 -81
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,1,1,8552,38604],"enemy_1061_zomshd":[11,11,0,983,179484]},
"hooks": [1,23,4184,327,327,641,632,9770,13,14,16,15,0,6375,15,29,13,1,1,15,0,0,2],
"events": {"atk":422,"deploy":8,"die":13,"dmg":622,"fx":104,"heal":22,"leak":1,"skill":31,"spawn":23,"status":44},
"events": {"atk":422,"deploy":8,"die":13,"dmg":622,"fx":104,"heal":22,"leak":1,"skill":33,"spawn":23,"status":44},
"fx": {"block":7,"dodge":2,"flame":1,"garrisonGrant":1,"riposte":1,"rockslide":7,"shieldBreak":10,"slash":40,"thorns":33,"truesilver":2},
"snaps": "48d2a0bc 7a5c72a3 37963453 9c6110e3 fcf46ff8 04b08d2c c03bf731 3792e3d9 e4639ebf decf37ce a41ea7f2 bd40f194 8e5c993d 300fcece"
},
@@ -339,13 +339,13 @@
"rngDraws": 20,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":17,"total":18,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":86959,"bossDmg":0,"heal":1523,"deaths":5,"layerGains":{"indomShip":2}}
"p1": {"killed":17,"total":18,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":86976,"bossDmg":0,"heal":1523,"deaths":5,"layerGains":{"indomShip":2}}
},
"units": [
["p1",1,"op","chess_char_1_20_a",1,null,1627.2,1,4,0,623,571,0,37,0],
["p1",1,"op","chess_char_1_20_a",1,null,1627.2,1,4,0,640,571,0,38,0],
["p1",2,"op","chess_char_4_11_a",1,null,0,0,5,0,68,2585,0,3,1],
["p1",3,"op","chess_char_4_12_a",2,null,0,0,7,0,11547,3277,1523,33,2],
["p1",4,"op","chess_char_2_12_a",1,null,1107,1,34,0,881,1107,0,13,0],
["p1",4,"op","chess_char_2_12_a",1,null,1107,1,34,0,881,1107,0,13,2],
["p1",5,"op","chess_char_4_09_a",1,null,2044.67,1,10,8,40549,93,0,15,2],
["p1",6,"op","chess_char_4_10_a",1,null,1029.36,1,6,7,17689,259,0,17,1],
["p1",7,"op","chess_char_4_13_a",1,null,0,0,9,2,10229,1587,0,21,4],
@@ -356,11 +356,11 @@
["-","~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_1158_divman":[15,15,0,2849,57024],"enemy_1160_hvyslr":[2,1,1,5740,23023],"enemy_1161_tidmag":[1,1,0,2166,6912]},
"hooks": [1,33,2879,213,213,305,307,1042,22,23,11,12,0,8131,210,8,18,1,1,22,1,0,9],
"events": {"atk":256,"deploy":12,"die":22,"dmg":307,"fx":48,"layer":1,"leak":1,"skill":22,"spawn":31,"status":99},
"enemies": {"enemy_1158_divman":[15,15,0,2849,57024],"enemy_1160_hvyslr":[2,1,1,5740,23040],"enemy_1161_tidmag":[1,1,0,2166,6912]},
"hooks": [1,33,2879,214,214,306,308,1042,22,23,13,13,0,8131,210,8,18,1,1,22,1,0,9],
"events": {"atk":257,"deploy":12,"die":22,"dmg":308,"fx":48,"layer":1,"leak":1,"skill":26,"spawn":31,"status":99},
"fx": {"dilemma":2,"displace":7,"dodge":9,"explosion":1,"frostNova":4,"garrison":1,"levitate":7,"overclock":1,"pull":1,"shield":2,"slippery":1,"splash":2,"tornado":2,"tornadoPulse":8},
"snaps": "8bec34f2 edddc261 3a83c8ff fe539554 b9c685a0 fc37ba44 27c41a48 755c7acf f9c428e6 8ba53346"
"snaps": "8db24db5 bd49abba e851f738 07d3cf13 3b2ba3a7 f0b55220 093a961a 3f9a349d b6a73354 da1b7817"
},
"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",
@@ -374,28 +374,28 @@
"time": 190,
"ticks": 5700,
"reason": "timeout",
"killed": 14,
"killed": 13,
"total": 22,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 298,
"rngDraws": 306,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":14,"total":22,"perfect":false,"leaks":8,"leaksUncounted":0,"coins":0,"dmg":263129,"bossDmg":0,"heal":70003,"deaths":19,"layerGains":{}}},
"players": {"p1":{"killed":13,"total":22,"perfect":false,"leaks":9,"leaksUncounted":0,"coins":0,"dmg":304172,"bossDmg":0,"heal":65632,"deaths":22,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_1_20_b",1,"uniequip_002_liskam",61.47,1,48,2,37022,31443,7160,94,6],
["p1",2,"op","chess_char_3_16_b",1,"uniequip_002_snakek",3100,1,5,0,0,0,2643,0,0],
["p1",1,"op","chess_char_1_20_b",1,"uniequip_002_liskam",2892,1,50,2,31083,30784,5313,80,7],
["p1",2,"op","chess_char_3_16_b",1,"uniequip_002_snakek",3100,1,5,0,0,0,1569,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",1929.76,1,42,1,15616,31872,9053,196,0],
["p1",6,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,49,3,64788,4346,4371,63,4],
["p1",7,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,47,0,0,7103,6480,0,2],
["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,46,0,0,11712,24394,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",1268.06,1,44,1,15616,31872,8570,196,4],
["p1",6,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,48,2,61006,4225,3289,58,3],
["p1",7,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,49,0,0,7103,6023,0,2],
["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,47,0,0,11712,24351,33,4]
],
"enemies": {"enemy_1166_dusbr_2":[14,12,2,28217,144069],"enemy_1170_dushld_2":[4,2,2,45909,73715],"enemy_9011_acrefr":[4,0,2,46339,45345]},
"hooks": [1,49,5700,831,831,1190,1169,4295,33,39,24,26,81,4034,210,59,22,6,1,33,0,0,21],
"events": {"atk":1224,"deploy":27,"die":33,"dmg":1166,"fx":163,"heal":167,"leak":6,"skill":47,"spawn":30,"status":541},
"fx": {"bandage":4,"dodge":21,"extraAttack":42,"garrisonGrant":1,"mote":39,"redistribute":30,"revive":16,"reweave":2,"sandChains":4,"shield":4},
"snaps": "bcca108c b78f4774 704b4447 e8becbbe 55de3ed6 7a40bda1 9e89e327 6796cf0f 31697842 2e290595 675d935c 576bc3f1 49c78987 a3206632 6bd55a9d e6a7dcae 9f209030 137f71a3 4b456e75"
"enemies": {"enemy_1166_dusbr_2":[14,11,3,27513,136496],"enemy_1170_dushld_2":[4,1,3,36563,52797],"enemy_9011_acrefr":[4,1,2,57607,114879]},
"hooks": [1,52,5700,834,834,1143,1124,3859,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":193,"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 ec9cb900 84379a83 c02cd949 c7444986 f9e23c1c e0aa99f1 eeea552b 24eb1e39 15dcee36 e5d5538f fbefb93b 04cf81c1 90a2259d 859536f6 b9c7eade fb8513f0 ac9b1324 54084832"
},
"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",
@@ -422,7 +422,7 @@
["p1",3,"op","chess_char_5_14_a",1,null,1388,1,5,0,8599,895,731,29,5],
["p1",4,"op","chess_char_4_19_a",2,null,487.76,1,8,2,3429,1072,0,21,1],
["p1",5,"op","chess_char_3_04_a",1,null,667.08,1,9,4,17229,2310,0,29,0],
["p1",6,"op","chess_char_4_16_a",2,null,1246,1,42,4,11741,1246,0,20,0],
["p1",6,"op","chess_char_4_16_a",2,null,1246,1,42,4,11741,1246,0,20,3],
["p1",7,"op","chess_char_2_19_a",1,null,0,0,6,4,9565,1021,0,24,1],
["p1",8,"op","chess_char_4_20_a",2,null,1009.8,1,7,3,12298,207,0,17,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
@@ -445,10 +445,10 @@
["p1","~16","token","token_10031_swire2_gdtrap",null,null,1000,1,45,0,0,0,0,0,null]
],
"enemies": {"enemy_1158_divman":[15,15,0,1399,44550],"enemy_1160_hvyslr":[2,2,0,3890,18000],"enemy_1161_tidmag":[1,1,0,1463,5400]},
"hooks": [1,45,2498,223,223,306,307,478,21,33,9,10,0,6503,44,10,18,0,1,22,0,0,9],
"events": {"atk":234,"deploy":25,"die":33,"dmg":303,"fx":89,"skill":18,"spawn":44,"status":70},
"hooks": [1,45,2498,223,223,306,307,478,21,33,12,12,0,6503,44,10,18,0,1,22,0,0,9],
"events": {"atk":234,"deploy":25,"die":33,"dmg":303,"fx":89,"skill":28,"spawn":44,"status":70},
"fx": {"aoe":6,"coin":19,"dodge":9,"explode":13,"featherArrow":1,"flame":1,"garrisonGrant":1,"revive":1,"riposte":7,"rockslide":1,"shieldBreak":4,"summon":16,"swordRain":6,"swordStorm":3,"zone":1},
"snaps": "df1c09b6 c5c9f7d2 71bce589 e2853803 32ae9ac8 34b10ae9 82dae7e0 86ec8e63 94c9fbcb"
"snaps": "1ecc8642 832be356 348fd8fd 002fcccf 012b93d9 f75ce414 18574635 facf960a abfbfba2"
},
"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",
@@ -484,7 +484,7 @@
],
"enemies": {"enemy_1229_darmy":[10,10,0,0,48384],"enemy_1229_darmy_2":[7,7,0,0,44352],"enemy_1275_dwlock_2":[3,3,0,3570,48384],"enemy_5601_entlec":[2,2,0,0,5786]},
"hooks": [1,33,2143,108,108,161,354,291,22,23,7,5,42,3991,0,3,22,0,1,62,0,0,0],
"events": {"atk":118,"deploy":8,"die":23,"dmg":199,"fx":55,"skill":12,"spawn":33,"status":3},
"events": {"atk":118,"deploy":8,"die":23,"dmg":199,"fx":55,"skill":14,"spawn":33,"status":3},
"fx": {"anchor":2,"aoe":1,"beam":1,"candle":2,"coin":4,"downed":1,"ember":1,"revive":1,"splash":42},
"snaps": "62c7bcd4 674c8844 6a9bd237 91a64467 cba2cbbe e9220cd3 1da987ee fb3307c8"
},
@@ -514,7 +514,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,4,26393,0,0,19,1],
["p1",4,"op","chess_char_1_19_a",1,null,0,0,9,0,15120,4341,0,35,1],
["p1",5,"op","chess_char_2_12_a",1,null,0,0,10,1,24825,8504,3348,59,0],
["p1",5,"op","chess_char_2_12_a",1,null,0,0,10,1,24825,8504,3348,59,1],
["p1",6,"op","chess_char_4_21_a",1,null,1271,1,4,0,0,0,5770,35,1],
["p1",7,"op","chess_char_4_24_a",1,null,1726,1,5,3,8044,0,0,12,2],
["p1",8,"op","chess_char_4_25_a",2,null,140.06,1,11,0,0,4598,4369,0,1],
@@ -523,10 +523,10 @@
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1116_liprr":[11,11,0,4898,69696],"enemy_1116_liprr_2":[4,4,0,179,34560],"enemy_1121_lifbos":[1,1,0,8539,23040]},
"hooks": [1,27,2084,242,242,384,384,1800,18,18,7,7,0,7493,12,17,16,0,1,18,1,0,0],
"events": {"atk":265,"deploy":8,"die":18,"dmg":376,"fx":59,"heal":48,"layer":1,"skill":13,"spawn":27,"status":49},
"hooks": [1,27,2084,242,242,384,384,1800,18,18,8,7,0,7493,12,17,16,0,1,18,1,0,0],
"events": {"atk":265,"deploy":8,"die":18,"dmg":376,"fx":59,"heal":48,"layer":1,"skill":15,"spawn":27,"status":49},
"fx": {"displace":16,"garrison":1,"healField":1,"liberate":4,"redistribute":15,"reweave":1,"sandChains":2,"shield":1,"slash":17,"truesilver":1},
"snaps": "0bfff00d 05c25821 afccda40 f76042f7 01a83e51 376ea44f e7830c09"
"snaps": "ad09e96f 6bdb7b5b 6eeee792 1fcaf8dd 4f9ad0eb 979b5a35 added99f"
},
"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",
@@ -553,17 +553,17 @@
["p1",3,"op","chess_char_5_08_b",2,"uniequip_002_horn",605.79,1,7,10,69588,4063,1893,52,2],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,8,0,20635,0,0,16,2],
["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,9,3,85930,1971,0,51,2],
["p1",6,"op","chess_char_2_12_b",1,"uniequip_002_gravel",0,0,37,2,45298,4646,0,40,0],
["p1",6,"op","chess_char_2_12_b",1,"uniequip_002_gravel",0,0,37,2,45298,4646,0,40,2],
["p1",7,"op","chess_char_3_17_b",1,"uniequip_002_shotst",3441.6,1,4,7,84143,0,0,29,3],
["p1",8,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,6,0,2897,0,0,10,2],
["-","~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,147057],"enemy_1061_zomshd":[5,5,0,4181,132516]},
"hooks": [1,41,2585,237,237,430,545,3521,33,33,13,13,0,5133,998,10,30,0,1,46,0,0,5],
"events": {"atk":237,"deploy":9,"die":33,"dmg":425,"fx":55,"skill":24,"spawn":40,"status":123},
"hooks": [1,41,2585,237,237,430,545,3521,33,33,15,13,0,5133,998,10,30,0,1,46,0,0,5],
"events": {"atk":237,"deploy":9,"die":33,"dmg":425,"fx":55,"skill":28,"spawn":40,"status":123},
"fx": {"aoe":25,"displace":15,"dodge":5,"flame":2,"overload":2,"riposte":1,"shield":2,"volley":3},
"snaps": "8c049284 94ec5278 5b651787 1fd27218 f0da3e8f e10280c8 9a24dc65 cc785d71 f60edecd"
"snaps": "5a72bba9 0659d371 b8310d82 76b71b85 cf872b16 b13b69ba 59b7c64f 9c4213f3 8327e347"
},
"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",
@@ -608,7 +608,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,47,1315,63,63,120,120,0,27,35,3,3,0,5130,1,0,27,0,1,27,1,0,0],
"events": {"atk":63,"deploy":17,"die":35,"dmg":120,"fx":50,"layer":1,"skill":6,"spawn":47,"status":2},
"events": {"atk":63,"deploy":17,"die":35,"dmg":120,"fx":50,"layer":1,"skill":8,"spawn":47,"status":2},
"fx": {"buff":1,"coin":12,"explode":8,"garrison":1,"garrisonGrant":1,"spGift":14,"strike":2,"summon":9,"volley":2},
"snaps": "907cbf77 fce5049b cd63c811 19d26f73 4b87ef36"
},
@@ -964,14 +964,14 @@
"round": 5,
"seed": 806507299,
"waveId": "act1autochess_05",
"time": 50.23,
"ticks": 1507,
"time": 53.47,
"ticks": 1604,
"reason": "cleared",
"killed": 17,
"total": 17,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 21,
"rngDraws": 22,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
@@ -983,28 +983,28 @@
"coins": 0,
"dmg": 126144,
"bossDmg": 0,
"heal": 2669,
"heal": 2419,
"deaths": 0,
"layerGains": {"preciShip":4,"raidShip":1}
}
},
"units": [
["p1",1,"op","chess_char_4_17_a",1,null,2849,1,3,0,0,0,0,0,0],
["p1",2,"op","chess_char_4_18_a",1,null,4587.2,1,5,1,10682,19,19,12,2],
["p1",2,"op","chess_char_4_18_a",1,null,4085.2,1,5,2,12767,521,19,15,2],
["p1",3,"op","chess_char_2_09_a",1,null,2047.24,1,17,3,21927,0,0,26,1],
["p1",4,"op","chess_char_4_19_a",2,null,1195.44,1,9,2,23164,365,0,30,1],
["p1",5,"op","chess_char_1_18_a",1,null,1706.55,1,32,7,45410,7067,2650,53,0],
["p1",6,"op","chess_char_4_13_a",1,null,1587,1,4,0,7729,0,0,12,4],
["p1",4,"op","chess_char_4_19_a",2,null,1101.88,1,9,2,24100,458,0,32,2],
["p1",5,"op","chess_char_1_18_a",1,null,3087.71,1,14,5,40597,4537,2400,48,3],
["p1",6,"op","chess_char_4_13_a",1,null,1587,1,4,0,8671,0,0,14,4],
["p1",7,"op","chess_char_4_14_a",1,null,1276,1,6,0,0,0,0,0,0],
["p1",8,"op","chess_char_4_20_a",2,null,1217,1,8,4,17232,0,0,16,0],
["p1",8,"op","chess_char_4_20_a",2,null,1217,1,8,5,18081,0,0,17,0],
["-","~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_1116_liprr":[11,11,0,350,69696],"enemy_1116_liprr_2":[4,4,0,757,34560],"enemy_1118_lidbox_2":[2,2,0,19,21888]},
"hooks": [1,32,1507,195,195,229,226,85,17,22,8,11,0,4625,227,18,17,0,1,17,2,0,8],
"events": {"atk":208,"deploy":13,"die":22,"dmg":211,"fx":42,"heal":54,"layer":2,"skill":15,"spawn":27,"status":65},
"fx": {"aoe":5,"blink":5,"dodge":8,"flame":1,"frostNova":4,"garrison":2,"garrisonGrant":1,"liberate":2,"riposte":8,"rockslide":2,"shieldBreak":4},
"snaps": "c2c19547 f7f121f8 342b4210 25ce41d3 2fc3e7fa"
"enemies": {"enemy_1116_liprr":[11,11,0,443,69696],"enemy_1116_liprr_2":[4,4,0,757,34560],"enemy_1118_lidbox_2":[2,2,0,521,21888]},
"hooks": [1,30,1604,201,201,235,230,80,17,20,12,11,0,4816,246,18,17,0,1,17,2,0,8],
"events": {"atk":214,"deploy":11,"die":20,"dmg":215,"fx":40,"heal":49,"layer":2,"skill":25,"spawn":27,"status":63},
"fx": {"aoe":3,"blink":3,"dodge":8,"flame":2,"frostNova":4,"garrison":2,"garrisonGrant":1,"liberate":3,"riposte":8,"rockslide":2,"shieldBreak":4},
"snaps": "f6e5416c 58911567 9a06225b ca1af778 00aa1906 6c2de306"
},
"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",
@@ -1015,8 +1015,8 @@
"round": 10,
"seed": 3301807152,
"waveId": "act1autochess_h03",
"time": 61.43,
"ticks": 1843,
"time": 61.5,
"ticks": 1845,
"reason": "cleared",
"killed": 24,
"total": 24,
@@ -1025,26 +1025,26 @@
"rngDraws": 0,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":508861,"bossDmg":0,"heal":4200,"deaths":0,"layerGains":{"preciShip":8}}
"p1": {"killed":24,"total":24,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":508861,"bossDmg":0,"heal":4030,"deaths":0,"layerGains":{"preciShip":8}}
},
"units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,4,0,0,0,0,0,0],
["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",23974.92,1,46,4,98103,0,0,15,2],
["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",23974.92,1,45,4,122836,1469,180,19,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",18169.4,1,40,20,404123,21798,4200,60,0],
["p1",6,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,0,0,0,0,0],
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,0,6635,0,0,6,0],
["p1",5,"op","chess_char_1_18_b",1,"uniequip_002_utage",18333.94,1,30,20,366686,16890,3850,55,8],
["p1",6,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,7235,0,0,5,3],
["p1",7,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,0,12105,0,0,13,0],
["p1",8,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,9,0,0,0,0,0,0],
["-","~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],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1010_demon":[7,7,0,2359,130637],"enemy_1422_lrsldr_2":[14,14,0,0,191601],"enemy_1425_lrcmra_2":[3,3,0,2669,186624]},
"hooks": [1,46,1843,96,96,97,107,75,24,35,2,10,0,1717,0,16,24,0,1,24,1,0,0],
"events": {"atk":98,"deploy":19,"die":35,"dmg":101,"fx":23,"heal":60,"layer":1,"skill":3,"spawn":35,"status":27},
"fx": {"aoe":10,"blink":11,"garrison":1,"garrisonGrant":1},
"snaps": "2edb5682 ac7f24ca 074fdc6d 19a401d3 da20371c 72e9aa89 fabecb2b"
"enemies": {"enemy_1010_demon":[7,7,0,2139,130637],"enemy_1422_lrsldr_2":[14,14,0,0,191601],"enemy_1425_lrcmra_2":[3,3,0,2803,186624]},
"hooks": [1,45,1845,107,107,111,119,74,24,34,13,12,0,2316,0,16,24,0,1,24,1,0,0],
"events": {"atk":109,"deploy":18,"die":34,"dmg":113,"fx":23,"heal":58,"layer":1,"skill":27,"spawn":35,"status":9},
"fx": {"aoe":8,"blink":10,"explosion":3,"garrison":1,"garrisonGrant":1},
"snaps": "1131be33 27e0b982 bfaeaa73 76dfd40e 79e7f64e f4827c69 435ba556"
},
"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",
@@ -1150,7 +1150,7 @@
],
"enemies": {"enemy_1010_demon":[3,3,0,7516,25920],"enemy_1010_demon_2":[1,1,0,6145,17280],"enemy_1071_dftman":[15,15,0,880,48384]},
"hooks": [1,28,2257,235,235,291,315,0,24,24,4,4,0,4413,27,28,19,0,1,24,0,0,0],
"events": {"atk":235,"deploy":9,"die":24,"dmg":315,"fx":48,"skill":8,"spawn":28,"status":53},
"events": {"atk":235,"deploy":9,"die":24,"dmg":315,"fx":48,"skill":12,"spawn":28,"status":53},
"fx": {"aoe":7,"coin":4,"crit":6,"dp":1,"strike":29,"summon":1},
"snaps": "d4f0d969 5a4a40d8 3c484b76 b9530121 cbe87f7f d5bacb4f 94dfe238 7e0baa1c"
},
@@ -1188,7 +1188,7 @@
],
"enemies": {"enemy_1158_divman":[20,20,0,6116,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1507,59720]},
"hooks": [1,39,2230,313,313,469,473,2,29,29,6,5,0,3754,38,2,26,0,1,31,3,0,0],
"events": {"atk":314,"deploy":11,"die":29,"dmg":473,"fx":29,"heal":2,"layer":3,"skill":11,"spawn":37,"status":75},
"events": {"atk":314,"deploy":11,"die":29,"dmg":473,"fx":29,"heal":2,"layer":3,"skill":13,"spawn":37,"status":75},
"fx": {"aoe":3,"bondProc":9,"crit":5,"dp":4,"garrison":3,"wolfShadow":2,"wolfShadowLost":3},
"snaps": "d1ead0d3 cda7c00c ccc1aada c0883850 2ba76678 a36bad1d 085c7f88 f7baafc7"
},
@@ -1463,7 +1463,7 @@
["p1",2,"op","chess_char_4_17_a",1,null,2818.99,1,5,2,1418,30,0,4,0],
["p1",3,"op","chess_char_4_18_a",1,null,4587.2,1,8,3,8181,0,0,9,1],
["p1",4,"op","chess_char_4_12_a",2,null,0,0,10,5,17855,2150,396,17,1],
["p1",5,"op","chess_char_4_16_a",2,null,1246,1,33,0,6418,1246,0,26,0],
["p1",5,"op","chess_char_4_16_a",2,null,1246,1,33,0,6418,1246,0,26,2],
["p1",6,"op","chess_char_1_01_b",1,"uniequip_002_inside",1421,1,6,7,23924,0,0,32,1],
["p1",7,"op","chess_char_4_13_a",1,null,1587,1,7,2,6599,0,0,18,5],
["p1",8,"op","chess_char_4_14_a",1,null,1276,1,9,2,27804,0,0,14,4],
@@ -1472,10 +1472,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,1739,34560],"enemy_1251_lysyta":[15,15,0,121,47520],"enemy_1328_cbjedi":[1,1,0,1566,11520]},
"hooks": [1,33,1604,143,143,216,211,276,23,23,12,13,14,6222,470,20,21,0,1,23,0,0,0],
"events": {"atk":143,"deploy":9,"die":23,"dmg":208,"fx":57,"skill":24,"spawn":32,"status":42},
"hooks": [1,33,1604,143,143,216,211,276,23,23,14,13,14,6222,470,20,21,0,1,23,0,0,0],
"events": {"atk":143,"deploy":9,"die":23,"dmg":208,"fx":57,"skill":29,"spawn":32,"status":42},
"fx": {"displace":26,"explosion":4,"frostNova":5,"pull":1,"rockslide":1,"shieldBreak":8,"sp":2,"swordStorm":2,"thorns":2,"tornado":1,"tornadoPulse":5},
"snaps": "8bc49724 100254d9 abd4d59e e5605c1e 302243e9 045c0db6"
"snaps": "d4d5e757 8604a676 5217be11 0c4d8f95 9d7986ea ac98758f"
},
"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",
@@ -1634,7 +1634,7 @@
],
"enemies": {"enemy_1116_liprr":[11,11,0,305,69696],"enemy_1116_liprr_2":[4,4,0,429,34560],"enemy_1118_lidbox_2":[2,2,0,9762,21888]},
"hooks": [1,44,1985,210,210,367,394,52,18,34,6,3,0,6985,15,16,17,0,1,18,0,1,0],
"events": {"atk":210,"deploy":25,"die":34,"dmg":394,"fx":114,"heal":42,"skill":9,"spawn":44,"status":48},
"events": {"atk":210,"deploy":25,"die":34,"dmg":394,"fx":114,"heal":42,"skill":11,"spawn":44,"status":48},
"fx": {"aoe":10,"burst":1,"coin":20,"counter":5,"explode":16,"liberate":3,"overload":1,"strike":40,"summon":17,"taunt":1},
"snaps": "3a6154e2 bbed839c a9f7e3a7 0a1cc28d bec91f1f 6279cb1e 7645ebd2"
},
@@ -1737,8 +1737,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],
@@ -1747,10 +1747,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",
@@ -1839,7 +1839,7 @@
],
"enemies": {"enemy_1019_jshoot_2":[7,7,0,1075,47029],"enemy_1381_winman":[9,9,0,0,129890],"enemy_1389_winbab_2":[5,5,0,5215,87091]},
"hooks": [1,46,2270,120,120,188,222,1034,22,30,11,9,5,5355,1,4,21,0,1,23,0,0,0],
"events": {"atk":120,"deploy":22,"die":30,"dmg":222,"fx":44,"heal":26,"skill":20,"spawn":46,"status":2},
"events": {"atk":120,"deploy":22,"die":30,"dmg":222,"fx":44,"heal":26,"skill":24,"spawn":46,"status":2},
"fx": {"coin":14,"downed":1,"explode":8,"splash":5,"summon":11,"yanyouFlame":3,"yanyouSummon":2},
"snaps": "b2b94c31 1e5707be a1b26ab3 10db4b56 bcd4d5e3 2c5a291d 646238a4 8471a2e0"
}
+2 -2
View File
@@ -208,7 +208,7 @@
],
"enemies": {"enemy_1019_jshoot_2":[57,37,20,9953,112024],"enemy_1299_ymkilr_2":[27,6,20,25821,123649],"enemy_1422_lrsldr_2":[39,20,19,1584,123193]},
"hooks": [1,154,6900,835,835,3335,3375,0,83,142,12,15,0,16427,38,83,123,59,1,83,0,0,0],
"events": {"atk":851,"deploy":31,"die":83,"dmg":3367,"fx":150,"leak":59,"skill":22,"spawn":135,"status":47},
"events": {"atk":851,"deploy":31,"die":83,"dmg":3367,"fx":150,"leak":59,"skill":36,"spawn":135,"status":47},
"fx": {"aoe":83,"displace":3,"dp":26,"slash":38},
"snaps": "aa4b712f 51a2360b 59868653 c815c7eb 7dd47d06 3f3f03f4 89e883c3 60e1e7f6 2355716a 5d734980 75cde05f c0dd657c 4e5bd257 0f8a040d b1aafe05 c04cbb36 e6d37890 8457998a a19143e5 d709bc2b 4283543b 0d9edf54 6ef9963d"
},
@@ -247,7 +247,7 @@
],
"enemies": {"enemy_1267_nhpbr_2":[27,5,22,7765,89733],"enemy_1270_nhstlk_2":[36,0,36,14278,68347],"enemy_1272_nhtank_2":[18,0,18,24706,27121]},
"hooks": [1,113,6307,262,262,449,476,16,35,111,6,21,0,5828,86,24,81,76,1,35,0,0,0],
"events": {"atk":283,"deploy":32,"die":35,"dmg":441,"fx":58,"heal":15,"leak":76,"skill":12,"spawn":93,"status":142},
"events": {"atk":283,"deploy":32,"die":35,"dmg":441,"fx":58,"heal":15,"leak":76,"skill":42,"spawn":93,"status":142},
"fx": {"aoe":7,"displace":21,"spGain":6,"zone":24},
"snaps": "1dcf4706 8bd55683 d93e4d02 ab832ad5 132699ab f37b039d f12175ca fe30225b 6b2b77c0 2caefa3a 1018f9e1 a60a2364 f81df3c0 d0f2780e 0e940f53 372c90ef 46af3a81 a6b2adf1 63234976 3b8b7c59 b62aa8d4"
}
+179 -179
View File
@@ -140,42 +140,42 @@
"killed": 7,
"total": 8,
"unspawned": 0,
"bossHpLeft": 2362809,
"bossHpLeft": 2356149,
"rngDraws": 412,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":595986,"bossDmg":293941,"heal":23195,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":813515,"bossDmg":339023,"heal":3410,"deaths":3,"layerGains":{}}
"p1": {"killed":7,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":606132,"bossDmg":324770,"heal":21014,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":816500,"bossDmg":315268,"heal":3306,"deaths":2,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,1,211687,1042,4,71,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,5584,15859,339,52,1],
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,30371,2465,513,60,18],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,3270,0,0,32,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,76910,0,1721,39,8],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,73126,0,0,53,5],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,1,27999,2122,811,52,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,41,0,125628,1611,0,40,14],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,19758,25,3],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,41410,1483,49,37,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,10,0,0,4718,933,0,2],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,12,0,45745,0,0,58,4],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,51,1,265397,879,451,286,8],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,14,0,2138,2476,93,10,6],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,50,0,89813,6040,587,178,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1749.68,1,16,0,79565,1028,0,70,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,1672,0,0,16,1],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,1,90491,0,0,66,3],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,18,0,106763,1346,1346,159,44],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,20,3,131930,0,0,65,15],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,1,202994,1042,0,71,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,8474,11961,300,48,1],
["p1",3,"op","chess_char_5_14_b",1,"none",1994,1,7,0,34133,2238,179,55,19],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,11086,0,0,80,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,109255,0,2041,49,10],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,81091,0,0,53,5],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,1,25904,2295,811,48,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,41,0,91104,1611,0,25,12],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,17670,23,3],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,42090,1393,13,34,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",1697.41,1,45,0,0,4941,348,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,12,0,32476,0,0,45,3],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,46,1,271164,879,451,280,8],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,14,0,2411,2646,263,13,6],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,47,0,77003,4837,555,181,8],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1851.91,1,16,0,85508,1270,344,75,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,1366,0,0,13,1],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,1,100869,0,0,70,3],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,18,0,112248,1346,1346,167,46],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,20,3,133456,0,0,65,16],
["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,3827,62190],"enemy_1430_lrrook":[2,1,1,0,714488],"enemy_9017_achunt":[2,0,0,37242,637191]},
"hooks": [1,49,6000,1371,1382,2556,2589,3595,11,27,145,150,22,41877,1544,0,10,1,1,12,0,10,1],
"events": {"atk":1420,"deploy":39,"die":26,"dmg":2584,"fx":535,"heal":58,"leak":1,"skill":285,"spawn":32,"status":544},
"fx": {"aoe":36,"appear":2,"blink":15,"burst":10,"catShield":1,"charge":6,"counter":22,"crit":10,"dilemma":5,"disappear":2,"dodge":1,"explosion":15,"frostNova":44,"iceSpike":41,"overclock":2,"pull":4,"ripple":16,"sentry":6,"sermon":1,"shadowWeave":10,"shield":6,"slippery":3,"soul":3,"sp":10,"stealth":8,"swordRain":87,"swordStorm":11,"tornado":4,"tornadoPulse":20,"weightless":5,"zone":129},
"snaps": "b55c9e85 7a53e855 bd415e40 c6d98e32 079e14b9 388a2ed2 1a3c6a26 858ad1f9 3ec7c9ee 77777386 0b9072d2 2975a009 e34e5cff b1c370dc 67cec109 fefbe0f8 43db203a 256d921c 02e85967 13b012d9"
"enemies": {"enemy_1005_yokai_2":[6,6,0,3949,62190],"enemy_1430_lrrook":[2,1,1,0,720539],"enemy_9017_achunt":[2,0,0,32509,643851]},
"hooks": [1,45,6000,1397,1409,2577,2608,3285,10,22,155,149,20,41642,1572,0,10,1,1,11,0,11,2],
"events": {"atk":1453,"deploy":35,"die":21,"dmg":2604,"fx":554,"heal":57,"leak":1,"skill":304,"spawn":32,"status":586},
"fx": {"aoe":35,"appear":2,"blink":11,"burst":11,"catShield":1,"charge":6,"counter":20,"crit":19,"dilemma":5,"disappear":2,"dodge":2,"explosion":16,"frostNova":46,"iceSpike":40,"overclock":1,"pull":3,"ripple":19,"sentry":4,"sermon":1,"shadowWeave":8,"shield":5,"slippery":3,"soul":3,"sp":10,"stealth":8,"swordRain":63,"swordStorm":8,"tornado":3,"tornadoPulse":15,"weightless":5,"zone":179},
"snaps": "bded3f8f 3be8bd3e 953a4177 0b7ebd10 0f36b9fa 98a31839 7097940b ccf68cd9 9f1e238b dbe2e9a7 c44e08ee 2e00e3ad e7215027 389a6d2f e881db8e e3b3ba28 ac1c13c3 18a7c70a 66ee300b 08fe4144"
},
"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",
@@ -210,7 +210,7 @@
],
"enemies": {"enemy_1249_lysdb_2":[5,5,0,9852,107495],"enemy_1430_lrrook":[2,0,2,0,58068],"enemy_9017_achunt":[1,0,0,20757,137787]},
"hooks": [1,21,6000,385,385,1021,1424,4322,8,10,25,24,0,11667,66,5,8,2,1,12,0,3,1],
"events": {"atk":400,"deploy":13,"die":8,"dmg":1420,"fx":263,"heal":39,"leak":2,"skill":48,"spawn":18,"status":410},
"events": {"atk":400,"deploy":13,"die":8,"dmg":1420,"fx":263,"heal":39,"leak":2,"skill":52,"spawn":18,"status":410},
"fx": {"bandage":2,"block":3,"burst":3,"charge":7,"dodge":1,"featherArrow":1,"flame":5,"garrisonGrant":1,"healField":1,"jungleSoul":3,"mote":102,"redistribute":45,"reweave":3,"riposte":1,"rockslide":2,"sandChains":3,"sandChainsCharged":2,"shieldBreak":9,"slash":55,"spGift":5,"thorns":6,"truesilver":3},
"snaps": "c067d0c5 9f4c5a58 85fccb2d c89eed8f 09312c88 452b8138 ee2ad798 da21566b cb07f503 e1edca84 9e18d3aa dbfd29d7 66bcd79a 979872f8 086680fd 722ac503 c2d3bae4 480a95f5 140b0229 df277c2a"
},
@@ -346,83 +346,81 @@
"time": 200,
"ticks": 6000,
"reason": "forced",
"killed": 12,
"killed": 10,
"total": 14,
"unspawned": 0,
"bossHpLeft": 2972244,
"rngDraws": 460,
"bossHpLeft": 2825291,
"rngDraws": 471,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":12,"total":14,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1255814,"bossDmg":425605,"heal":54784,"deaths":15,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1012343,"bossDmg":802151,"heal":6103,"deaths":12,"layerGains":{}}
"p1": {"killed":10,"total":14,"perfect":false,"leaks":4,"leaksUncounted":0,"coins":0,"dmg":1339007,"bossDmg":473761,"heal":54412,"deaths":13,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1103445,"bossDmg":900948,"heal":4247,"deaths":11,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,47,2,133309,8083,269,20,1],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",0,0,50,1,18681,9522,580,15,2],
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,54,0,33839,12822,1552,24,7],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,51,0,37009,13601,1966,34,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",804.59,1,55,3,316362,3438,10048,106,14],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",269.13,1,56,0,121735,10803,421,39,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1197.87,1,19,0,155484,4336,225,209,6],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1147.01,1,57,2,294282,2529,454,59,20],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1047.42,1,7,0,0,4920,38994,66,4],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1263.87,1,15,0,145114,4336,275,71,3],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,47,2,138926,8083,269,21,2],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",1875.61,1,50,0,16890,9787,775,13,2],
["p1",3,"op","chess_char_5_14_b",1,"none",1445.61,1,54,0,49842,12274,1523,26,10],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,52,0,39707,13906,2210,35,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",850.6,1,55,2,367176,3392,13317,106,17],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",908.73,1,56,2,130196,11108,599,39,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1250.45,1,19,0,164370,4283,221,209,6],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1202.87,1,57,2,279814,2473,454,58,20],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1113.15,1,7,0,0,4854,34778,69,4],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1316.45,1,15,0,152086,4283,265,71,3],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,46,0,0,6742,0,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,45,0,25731,7033,2569,10,1],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3402.43,1,26,0,411317,2671,233,308,9],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,44,0,14571,5178,97,17,5],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",915.83,1,27,1,165010,13734,586,194,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,52,1,138696,7139,369,47,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,53,0,12367,3470,348,16,1],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1159.66,1,12,1,85418,4336,467,68,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,20,1,113586,5757,1001,153,39],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1194.87,1,58,0,45645,2490,433,14,9],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,45,0,29962,5888,1424,11,2],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3468.16,1,26,0,443585,2606,119,325,9],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,44,0,17702,5178,97,19,7],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2322.38,1,27,0,157329,6484,329,208,2],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,51,1,159918,7145,375,53,5],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,53,1,12062,3470,348,16,1],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1736.19,1,12,0,74688,4283,467,68,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,20,0,132635,5655,655,186,49],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1247.45,1,58,0,75565,2437,433,30,10],
["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,9842,200612],"enemy_1438_dspred":[10,8,2,25539,839788],"enemy_1521_dslily":[1,0,0,89761,1227756]},
"hooks": [1,56,6000,1367,1479,2977,3105,7373,40,43,137,136,8,38476,1606,17,15,2,1,42,0,37,7],
"events": {"atk":1587,"deploy":41,"die":41,"dmg":3100,"fx":759,"heal":246,"leak":2,"skill":272,"spawn":37,"status":934},
"enemies": {"enemy_1010_demon":[4,4,0,9842,200612],"enemy_1438_dspred":[10,6,4,20511,867131],"enemy_1521_dslily":[1,0,0,86181,1374709]},
"hooks": [1,56,6000,1451,1565,3107,3244,6682,34,40,160,157,13,41191,1906,17,15,4,1,36,0,37,4],
"events": {"atk":1663,"deploy":41,"die":36,"dmg":3234,"fx":807,"heal":272,"leak":4,"skill":317,"spawn":37,"status":905},
"fx": {
"aoe": 34,
"appear": 12,
"aoe": 42,
"appear": 11,
"blink": 2,
"burst": 37,
"charge": 5,
"counter": 8,
"crit": 10,
"charge": 7,
"counter": 13,
"crit": 7,
"dilemma": 5,
"disappear": 12,
"disappear": 11,
"displace": 24,
"dodge": 7,
"explosion": 9,
"frostNova": 39,
"dodge": 4,
"explosion": 10,
"frostNova": 49,
"grow": 1,
"iceSpike": 163,
"levitate": 2,
"overclock": 1,
"pull": 1,
"pull": 2,
"ripple": 22,
"rockfall": 18,
"sentry": 1,
"sermon": 2,
"shadowWeave": 1,
"shield": 3,
"shield": 5,
"slippery": 4,
"soul": 9,
"sp": 8,
"splash": 1,
"sp": 11,
"stealth": 9,
"swordRain": 29,
"swordStorm": 3,
"swordRain": 8,
"swordStorm": 2,
"tentacle": 6,
"tide": 5,
"tornado": 1,
"tornadoPulse": 5,
"tornado": 2,
"tornadoPulse": 10,
"weightless": 4,
"zone": 256
"zone": 299
},
"snaps": "c1c7217a 86efd97d 51b1b9fa ce151b29 6599ccfa 8cf091f9 637a0e81 2fabff0e 83588130 dd2824f5 bad2b5b0 fcf15857 0c842834 9ab9e7fb cc4469ab 5bab359d ccfc5635 30457b93 119c5f24 0aabb086"
"snaps": "d5d054b2 cb90423b fee6602b ae24e1c0 e3c68927 26da52e2 6b690f9e cc8b62da 7602985f 437d670e 7b48851f 42782701 8c223b0c d5735dbe d7c8a039 0958be10 ecae9db7 bda3f543 442eb94c 6e3370b7"
},
"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 +457,7 @@
],
"enemies": {"enemy_1010_demon_2":[2,2,0,7219,107495],"enemy_1438_dspred":[5,2,3,27889,136924],"enemy_1521_dslily":[1,0,0,68800,413498]},
"hooks": [1,23,6000,568,568,962,1070,7648,8,11,25,24,0,13395,133,10,8,3,1,8,0,31,0],
"events": {"atk":763,"deploy":13,"die":8,"dmg":1052,"fx":336,"heal":102,"leak":3,"skill":49,"spawn":20,"status":523},
"events": {"atk":763,"deploy":13,"die":8,"dmg":1052,"fx":336,"heal":102,"leak":3,"skill":51,"spawn":20,"status":523},
"fx": {"bandage":1,"block":18,"burst":31,"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": "889b80ca 2554f68d 135727ee c74793bb 39ae3d9e 2f63625f 35d2d1d0 ab151905 58401106 8d34f3b8 1bed3837 592255cb 58e436d2 7cfc39b2 dd3f9d1c 6d83e822 e5e306e4 07c6dca8 5430994c 32254003"
},
@@ -622,32 +620,32 @@
"killed": 4,
"total": 6,
"unspawned": 0,
"bossHpLeft": 2744355,
"bossHpLeft": 2726484,
"rngDraws": 136,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":957592,"bossDmg":517994,"heal":5866,"deaths":6,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":855506,"bossDmg":717652,"heal":1349,"deaths":13,"layerGains":{}}
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":956228,"bossDmg":516630,"heal":5866,"deaths":6,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":875767,"bossDmg":736886,"heal":1349,"deaths":13,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,2,128856,0,0,35,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,51547,0,0,0,15],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,0,15412,4549,4549,16,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,2,335190,0,1317,71,12],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,206485,0,0,75,6],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,2,335436,0,1317,71,12],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,204001,0,0,75,6],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,40,0,19031,3258,0,21,1],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,41,0,146104,3222,0,50,16],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,41,0,146978,3222,0,50,16],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,42,0,1934,3220,0,0,0],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,53033,0,0,40,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,38,0,0,12227,333,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,46,0,137499,9146,872,177,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",527.19,1,53,0,51762,8463,144,167,0],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",527.19,1,53,0,50030,8463,144,167,5],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,49,0,170880,5555,0,71,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,288314,0,0,93,4],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,310308,0,0,107,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,50,0,126161,3978,0,113,29],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,51,0,80890,4878,0,27,5],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null],
@@ -657,14 +655,14 @@
"enemy_10028_vtswd": [4,4,0,0,0],
"enemy_10029_vtshld": [2,2,0,0,0],
"enemy_10030_vtwand": [2,2,0,0,0],
"enemy_10085_hllevi_2": [2,0,2,0,376840],
"enemy_10085_hllevi_2": [2,0,2,0,377867],
"enemy_1010_demon": [4,4,0,16776,200612],
"enemy_9032_aclionk": [1,0,0,41719,1235645]
"enemy_9032_aclionk": [1,0,0,41719,1253516]
},
"hooks": [1,51,6000,971,978,1548,1639,1575,31,35,104,105,0,31121,1258,4,15,2,1,27,0,8,1],
"events": {"atk":1064,"deploy":36,"die":33,"dmg":1624,"fx":475,"heal":8,"leak":2,"skill":205,"spawn":37,"status":769},
"hooks": [1,51,6000,985,992,1563,1663,1575,31,35,109,105,0,31092,1258,4,15,2,1,27,0,8,1],
"events": {"atk":1079,"deploy":36,"die":33,"dmg":1647,"fx":475,"heal":8,"leak":2,"skill":214,"spawn":37,"status":770},
"fx": {"aoe":39,"appear":4,"blink":2,"burst":8,"crit":1,"dilemma":4,"disappear":4,"displace":14,"dodge":1,"equip":10,"explosion":5,"frostNova":29,"garrisonGrant":1,"iceSpike":17,"overclock":1,"revive":2,"ripple":20,"sentry":3,"shadowWeave":3,"shield":1,"slippery":4,"soul":2,"sp":17,"stealth":6,"sword":2,"swordRain":20,"swordStorm":5,"telegraph":14,"vest":2,"weightless":6,"zone":228},
"snaps": "2e35c9af 1d134604 3072cfc9 ea16c0d0 3a75e4ec 87fad341 313be83c 0fbc2152 4662b897 2958cd1d 9a5096ce f6f29ba2 24f9b887 2f7f1188 064e6615 559057ce 81d9836a 8e384066 ece9a960 8ef465fa"
"snaps": "67c68c34 87636137 f5a31952 ab1da9a5 b1b9fa49 914f12bb 25261c33 394b5256 240747e9 0a21097a 58de0dfb f7f72619 6d5001dd e6829d8b 7761d7f9 3e5f6259 689b106e 52d8928a ac7284a0 448e2f14"
},
"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 +711,7 @@
"enemy_9032_aclionk": [1,0,0,40445,102806]
},
"hooks": [1,47,6000,190,190,281,479,1232,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,"leak":2,"skill":42,"spawn":36,"status":187},
"events": {"atk":220,"deploy":21,"die":32,"dmg":208,"fx":90,"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": "0bc7ed75 fd621b74 943602ec 1ce73978 7eacd037 6f2ca0e5 5c055acf 20ca0a27 1c880376 72dc5f46 b8f2dcf8 62ed54d6 fb3d8d3c 49c21d20 99bfce59 3443456c fa01dd99 a8e70810 51b46f88 c9b8fd8c"
},
@@ -839,93 +837,95 @@
"time": 200,
"ticks": 6000,
"reason": "forced",
"killed": 14,
"killed": 13,
"total": 18,
"unspawned": 0,
"bossHpLeft": 6037678,
"rngDraws": 613,
"bossHpLeft": 6018290,
"rngDraws": 684,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":14,"total":18,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1809106,"bossDmg":812936,"heal":51857,"deaths":19,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":605650,"bossDmg":349386,"heal":8020,"deaths":26,"layerGains":{}}
"p1": {"killed":13,"total":18,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":1938099,"bossDmg":992806,"heal":51019,"deaths":17,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":381327,"bossDmg":188904,"heal":6914,"deaths":26,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,79,3,271515,30808,624,34,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",3988,1,85,1,26291,12825,3248,6,6],
["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",2340,1,90,3,821462,2340,19307,93,13],
["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,7235,719,8,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1248.8,1,72,2,202716,5518,323,20,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,1,55061,10170,0,16,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,91,0,0,3288,644,0,0],
["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",2149,1,89,2,26514,15781,71,63,5],
["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",1619,1,93,1,86724,17473,858,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,177775,4640,344,141,3],
["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",7814,1,87,2,88478,21913,689,42,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",2847.9,1,76,0,6045,15126,739,25,2],
["p1",3,"op","chess_char_5_14_b",1,"none",3988,1,88,0,42463,15591,4140,40,8],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2260.62,1,86,0,31022,24337,1819,21,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1214.62,1,81,5,960839,4155,16322,100,13],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",0,0,79,3,428143,14006,338,38,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,1,1,144400,4077,819,12,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",0,0,64,1,24893,6619,175,14,2],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,71,0,0,12336,25861,23,2],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",0,0,77,1,211816,10287,117,11,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,74,0,0,7013,271,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,70,0,16804,5349,885,6,1],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",0,0,82,1,15203,15182,83,71,4],
["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",0,0,84,1,91608,19229,327,30,10],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,62,2,85162,9516,232,23,2],
["p2",17,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,89,0,18343,1561,0,158,3],
["p2",18,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,18,0,110240,6337,343,59,2],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,73,0,30032,6533,2555,36,13],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,61,0,5121,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,143925,888736],
"enemy_10027_vtsk": [6,1,4,144022,770521],
"enemy_1005_yokai": [4,4,0,0,1600],
"enemy_1229_darmy_2": [8,8,0,2986,294231],
"enemy_9013_acstmk_2": [1,0,0,52887,1162322],
"enemy_9014_acstma": [6,5,0,7032,17081],
"enemy_9015_acstmb": [3,2,0,13762,73414],
"enemy_9016_acstmr": [8,7,0,0,51]
"enemy_1229_darmy_2": [8,8,0,1944,294231],
"enemy_9013_acstmk_2": [1,0,0,47998,1181710],
"enemy_9014_acstma": [5,4,0,6191,10472],
"enemy_9015_acstmb": [3,2,0,12821,83514],
"enemy_9016_acstmr": [8,7,0,0,49]
},
"hooks": [1,91,6000,739,741,2760,2868,5716,74,79,73,82,3,18333,838,15,36,4,1,76,0,4,8],
"events": {"atk":932,"deploy":55,"die":75,"dmg":2867,"fx":601,"heal":173,"leak":4,"skill":146,"spawn":58,"status":652},
"hooks": [1,88,6000,818,818,2739,2842,6739,70,75,79,76,7,20346,731,14,35,4,1,70,0,0,5],
"events": {"atk":955,"deploy":53,"die":71,"dmg":2841,"fx":634,"heal":146,"leak":4,"skill":155,"spawn":57,"status":683},
"fx": {
"aoe": 19,
"appear": 7,
"aoe": 18,
"appear": 10,
"beam": 46,
"blink": 17,
"burst": 4,
"charge": 12,
"counter": 3,
"crit": 72,
"dilemma": 3,
"disappear": 7,
"dodge": 8,
"explode": 8,
"blink": 15,
"charge": 9,
"counter": 7,
"crit": 85,
"dilemma": 2,
"disappear": 10,
"displace": 1,
"dodge": 5,
"explode": 7,
"explosion": 1,
"frostNova": 14,
"iceSpike": 5,
"palsy": 1,
"frostNova": 13,
"iceSpike": 12,
"levitate": 4,
"overclock": 1,
"palsy": 5,
"phase": 1,
"pull": 1,
"quadShot": 3,
"quadShot": 2,
"revive": 2,
"ripple": 5,
"ripple": 1,
"sentry": 5,
"sermon": 1,
"shadowWeave": 6,
"sermon": 2,
"shadowWeave": 10,
"shell": 8,
"shield": 3,
"shield": 5,
"slippery": 2,
"soul": 4,
"sp": 10,
"spGain": 3,
"stealth": 5,
"sp": 5,
"spGain": 4,
"splash": 1,
"stealth": 4,
"summon": 4,
"swordRain": 74,
"swordStorm": 11,
"swordRain": 70,
"swordStorm": 10,
"telegraph": 6,
"tornado": 1,
"tornadoPulse": 5,
"weightless": 3,
"zone": 211
"weightless": 4,
"zone": 230
},
"snaps": "22e07f1e 41a16486 3c5d78bd b5778e6c 952c561f b5d14c3c a800523c a6825729 603a09dc 5fd03161 96efcc3e 9f13862f 6541683d 1e15b931 1d5feabf d90fbdae 230b1290 c9c16dce e05a47fd 09bdfad0"
"snaps": "264b53d3 3ee7ee43 f3f2921c 6864a65d 84ad689b c6b276f5 bd8cd5a4 4b9cc96b 46aef8e5 b338b3d6 37a849e7 3c63cb44 b806b557 ef133aaa d4ea6b4c dadce104 8a17da59 ff167e0f 33de0e99 8bedfdbc"
},
"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",
@@ -973,7 +973,7 @@
"enemy_9016_acstmr": [7,7,0,0,25]
},
"hooks": [1,54,6000,267,267,494,540,687,39,46,13,13,0,5501,23,3,27,7,1,33,0,0,0],
"events": {"atk":302,"deploy":23,"die":39,"dmg":534,"fx":159,"heal":53,"leak":7,"skill":25,"spawn":41,"status":85},
"events": {"atk":302,"deploy":23,"die":39,"dmg":534,"fx":159,"heal":53,"leak":7,"skill":27,"spawn":41,"status":85},
"fx": {"beam":30,"blink":9,"block":6,"charge":5,"explode":8,"garrisonGrant":1,"healField":4,"mote":3,"phase":1,"reweave":1,"rockslide":4,"sandChains":1,"shell":7,"shieldBreak":6,"slash":57,"spGift":4,"summon":5,"telegraph":3,"thorns":1,"truesilver":3},
"snaps": "d1aaefe9 f284f922 d74a8f1d 9602e109 b0305cfc 7b811d98 c4991959 ad45bb9a c0314dcb 5738a584 6b481a5e e722f224 6c75c6d1 4ab59316 634e74cd 6b7487d0 c53dedc9 5845fde7 ed4678e2 1504d879"
},
@@ -1155,34 +1155,34 @@
"killed": 12,
"total": 12,
"unspawned": 0,
"bossHpLeft": 6540517,
"rngDraws": 469,
"bossHpLeft": 6523847,
"rngDraws": 459,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":422920,"bossDmg":252016,"heal":10011,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":971066,"bossDmg":807466,"heal":4490,"deaths":6,"layerGains":{}}
"p1": {"killed":12,"total":12,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":438996,"bossDmg":268088,"heal":10923,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":971661,"bossDmg":808065,"heal":4457,"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],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,9,0,0,0,0,0,0],
["p1",3,"op","chess_char_5_14_b",1,"none",3489.5,1,15,0,0,0,0,0,0],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,17,2,16620,5835,2257,36,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,2,183087,0,4523,19,5],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,0,62072,2958,3,55,5],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,51,2,112106,2847,273,221,6],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,17,2,16622,6178,2600,38,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1989,1,11,3,198811,0,5122,22,6],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,19,1,62074,3102,3,55,5],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,49,0,112453,2673,99,214,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,2955,4,2],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,7,0,0,0,3100,4,2],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,13,0,49035,0,0,30,3],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,48,0,0,4098,0,0,1],
["p2",12,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,46,0,7998,2420,188,13,1],
["p2",13,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,30,0,316123,0,0,249,8],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,47,0,1824,6232,1228,4,2],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,38,0,29495,2204,585,341,0],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",1162.48,1,20,8,347065,3041,1426,122,6],
["p2",14,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",0,0,47,0,1824,6222,1217,4,2],
["p2",15,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,38,0,30117,2204,585,341,2],
["p2",16,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2233.4,1,20,9,346666,2569,1426,122,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,99350,416,416,111,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,22,1,110921,608,607,200,49],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,4,0,47822,0,0,16,5],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,22,0,111844,585,585,200,49],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,4,0,47272,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],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
@@ -1191,17 +1191,17 @@
"enemies": {
"enemy_1402_tgshd_2": [2,2,0,7907,173864],
"enemy_9010_acpupp": [2,2,0,5741,160520],
"enemy_9021_acduml_2": [1,0,0,0,1059483],
"enemy_9022_acdumm": [1,0,0,759,0],
"enemy_9023_acdums": [12,12,0,10079,120]
"enemy_9021_acduml_2": [1,0,0,0,1076153],
"enemy_9022_acdumm": [1,0,0,193,0],
"enemy_9023_acdums": [12,12,0,10463,120]
},
"hooks": [1,49,6000,1262,1392,2332,2445,4079,23,24,100,98,0,33262,1840,8,18,0,1,25,0,3,0],
"events": {"atk":1368,"deploy":29,"die":24,"dmg":2436,"fx":879,"heal":65,"skill":197,"spawn":42,"status":665},
"hooks": [1,49,6000,1271,1399,2370,2478,4004,23,24,103,99,0,33138,1829,9,18,0,1,25,0,4,0],
"events": {"atk":1376,"deploy":29,"die":24,"dmg":2444,"fx":892,"heal":72,"skill":202,"spawn":42,"status":665},
"fx": {
"appear": 2,
"beam": 13,
"blink": 1,
"burst": 3,
"burst": 4,
"charge": 2,
"crit": 11,
"dilemma": 6,
@@ -1212,7 +1212,7 @@
"explosion": 5,
"expose": 200,
"frostNova": 49,
"iceSpike": 185,
"iceSpike": 176,
"overclock": 1,
"phase": 33,
"pull": 1,
@@ -1222,18 +1222,18 @@
"shield": 1,
"shieldBreak": 2,
"slippery": 4,
"sp": 2,
"sp": 3,
"stealth": 8,
"summon": 8,
"swordRain": 14,
"swordRain": 15,
"swordStorm": 2,
"telegraph": 2,
"tornado": 1,
"tornadoPulse": 5,
"weightless": 5,
"zone": 95
"zone": 114
},
"snaps": "7c28a7f2 4ae4beb3 a4073390 75a1e14a e9c32c98 b8f590e1 12f86dd5 5b09053f 629ea5ed 067999f7 d600d102 c2eccdd9 1877d4e1 225869f7 279142c0 13833543 77802a58 3eeb404a 40d29a70 ff38818c"
"snaps": "25ef01ee 4abf4c16 b375ed07 8d3c80f2 d3cb4ab8 7558361c 01ebe8a3 81c99dce dccd0467 18020580 509655e7 f0474cd0 7634409f 417bf8f0 c74c69b7 6b815153 874d16ed 19d663e0 b751da53 2e4691bb"
},
"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",
@@ -1274,7 +1274,7 @@
"enemy_9023_acdums": [10,10,0,38449,100]
},
"hooks": [1,26,6000,602,602,1242,1839,4975,13,14,27,24,0,13945,59,10,15,1,1,17,0,11,0],
"events": {"atk":703,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":151,"leak":1,"skill":51,"spawn":25,"status":257},
"events": {"atk":703,"deploy":11,"die":13,"dmg":1826,"fx":463,"heal":151,"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": "94ed0146 7aa5e4e9 a874864d 30318786 bdfb16b6 6918b2ce 7730d01a d9e4bd51 7ee58629 31867138 dca52206 d53f4ec5 d7497fdf b13092f9 af40e2ec fbf47558 faca1653 c3c78c84 013d42c0 76aed4b1"
},
+31 -31
View File
@@ -592,7 +592,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":2992890,"gold":139,"itemsEquipped":12,"kills":333,"leaks":10,"lpLost":9,"merges":5,"perfectRounds":11,"refreshes":23},
"stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":3012080,"gold":139,"itemsEquipped":12,"kills":333,"leaks":10,"lpLost":9,"merges":5,"perfectRounds":11,"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",18,"kazimierzShip 1/2, kjeragShip 0/2, raidShip 1/3, skillfulShip 1/3, soloShip 1/3, steadShip 1/5, 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",24,28,4,53]
},
{
"round": 8,
@@ -703,7 +703,7 @@
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2447629,"gold":126,"itemsEquipped":8,"kills":204,"leaks":86,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2480376,"gold":126,"itemsEquipped":8,"kills":208,"leaks":86,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
"lineup": [],
"bonds": ["kazimierzShip 0/45","kjeragShip 0/2","miraShip 0/7","preciShip 0/8","raidShip 0/42","skillfulShip 0/9","soloShip 0/3","steadShip 0/11","swiftShip 0/38","yanShip 0/6"]
},
@@ -717,7 +717,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1355731,"gold":114,"itemsEquipped":11,"kills":160,"leaks":73,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":19},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1339854,"gold":114,"itemsEquipped":11,"kills":156,"leaks":77,"lpLost":33,"merges":3,"perfectRounds":4,"refreshes":19},
"lineup": [],
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/59","sargonShip 0/4","siracusaShip 0/8","skillfulShip 0/6","swiftShip 0/23"]
}
@@ -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]
},
@@ -806,13 +806,13 @@
{
"round": 12,
"ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/16, kazimierzShip 0/2, kjeragShip 0/61, miraShip 0/12, siracusaShip 0/6, skillfulShip 0/69, soloShip 0/28, steadShip 0/55, suntShip 0/16, swiftShip 0/26",14,57,43,122],
"ai_1": [1,4,13,0,0,6,8,"fedd02aa",227,"deputShip 2/48, indomShip 1/23, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 1/39, soloShip 1/28, steadShip 1/16, suntShip 0/16, swiftShip 0/25, victoriaShip 1/73",17,57,40,113],
"ai_1": [1,4,13,0,0,6,8,"8d26c229",219,"deputShip 2/48, indomShip 1/19, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 1/39, soloShip 1/28, steadShip 1/16, suntShip 0/16, swiftShip 0/25, victoriaShip 1/69",18,57,39,113],
"ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",null,null,null,101]
},
{
"round": 13,
"ai_0": [0,0,0,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/16, kazimierzShip 0/2, kjeragShip 0/61, miraShip 0/12, siracusaShip 0/6, skillfulShip 0/69, soloShip 0/28, steadShip 0/55, suntShip 0/16, swiftShip 0/26",null,null,null,122],
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/52, indomShip 0/27, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 0/43, soloShip 0/32, steadShip 0/26, suntShip 0/16, swiftShip 0/25, victoriaShip 0/89",5,26,21,124],
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/52, indomShip 0/23, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 0/43, soloShip 0/32, steadShip 0/26, suntShip 0/16, swiftShip 0/25, victoriaShip 0/85",5,26,21,124],
"ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",null,null,null,101]
}
],
@@ -827,7 +827,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4492000,"gold":122,"itemsEquipped":17,"kills":275,"leaks":72,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":15},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4511751,"gold":122,"itemsEquipped":17,"kills":277,"leaks":70,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":15},
"lineup": [],
"bonds": ["deputShip 0/44","indomShip 0/16","kazimierzShip 0/2","kjeragShip 0/61","miraShip 0/12","siracusaShip 0/6","skillfulShip 0/69","soloShip 0/28","steadShip 0/55","suntShip 0/16","swiftShip 0/26"]
},
@@ -841,9 +841,9 @@
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3467151,"gold":124,"itemsEquipped":13,"kills":274,"leaks":102,"lpLost":51,"merges":3,"perfectRounds":5,"refreshes":20},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3464200,"gold":124,"itemsEquipped":13,"kills":273,"leaks":101,"lpLost":51,"merges":3,"perfectRounds":5,"refreshes":20},
"lineup": [],
"bonds": ["deputShip 0/52","indomShip 0/27","kazimierzShip 0/11","kjeragShip 0/5","skillfulShip 0/43","soloShip 0/32","steadShip 0/26","suntShip 0/16","swiftShip 0/25","victoriaShip 0/89"]
"bonds": ["deputShip 0/52","indomShip 0/23","kazimierzShip 0/11","kjeragShip 0/5","skillfulShip 0/43","soloShip 0/32","steadShip 0/26","suntShip 0/16","swiftShip 0/25","victoriaShip 0/85"]
},
"ai_2": {
"alive": false,
@@ -934,7 +934,7 @@
{
"round": 9,
"ai_0": [1,26,12,0,0,4,8,"fc454676",169,"deputShip 1/47, kjeragShip 0/2, preciShip 2/40, sargonShip 1/44, steadShip 1/38",30,30,0,74],
"ai_1": [1,45,12,0,0,4,8,"13fccde5",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",26,30,4,70],
"ai_1": [1,45,12,0,0,4,8,"fc8e5237",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",29,30,1,70],
"ai_2": [1,28,12,0,0,4,8,"6965081a",70,"sargonShip 0/8, siracusaShip 1/58, swiftShip 1/12, visiShip 1/0, yanShip 0/1",30,30,0,72],
"ai_3": [1,31,13,0,0,4,8,"5f784848",153,"deputShip 1/16, emptyShip 1/12, kjeragShip 1/30, preciShip 1/72, siracusaShip 0/3, swiftShip 1/23, yanShip 0/1",30,30,0,76]
},
@@ -978,7 +978,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1695990,"gold":140,"itemsEquipped":25,"kills":328,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1671568,"gold":140,"itemsEquipped":25,"kills":325,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
"lineup": ["6_08_a@11,6[3_03_e_a+3_02_e_a]","6_12_a@11,7[4_08_e_a+6_07_e_b]","2_14_b@10,3","2_17_b@10,4[4_02_e_b+5_01_e_a]","2_08_a@9,3[4_04_e_a+1_01_e_b]","6_06_a@9,4[3_08_e_a+3_01_e_b]","1_06_b@9,8","5_02_b@9,9[5_02_e_a+3_06_e_b]"],
"bonds": ["deputShip 1/61","emptyShip 1/6","kjeragShip 0/7","preciShip 2/158","sargonShip 1/172","steadShip 1/88"]
},
@@ -992,7 +992,7 @@
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":207,"bossDamage":12338,"dmgDealt":1288377,"gold":129,"itemsEquipped":20,"kills":237,"leaks":46,"lpLost":0,"merges":6,"perfectRounds":9,"refreshes":20},
"stats": {"activatedLayers":207,"bossDamage":12384,"dmgDealt":1296113,"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"]
},
@@ -1006,7 +1006,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":258,"bossDamage":70366,"dmgDealt":1649146,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
"stats": {"activatedLayers":258,"bossDamage":70321,"dmgDealt":1649095,"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/234","swiftShip 1/24","yanShip 0/1"]
},
@@ -1143,7 +1143,7 @@
"title": null,
"trophies": 1,
"reward": 149,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":822006,"gold":62,"itemsEquipped":5,"kills":63,"leaks":34,"lpLost":31,"merges":2,"perfectRounds":3,"refreshes":5},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":820341,"gold":62,"itemsEquipped":5,"kills":63,"leaks":34,"lpLost":31,"merges":2,"perfectRounds":3,"refreshes":5},
"lineup": [],
"bonds": ["arcaneShip 0/2","deputShip 0/6","kazimierzShip 0/27","kjeragShip 0/2","preciShip 0/2","sargonShip 0/8","siracusaShip 0/6","skillfulShip 0/4","soloShip 0/2","steadShip 0/2","swiftShip 0/34"]
},
@@ -1187,7 +1187,7 @@
"combat": "server",
"boost": null,
"setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":23,"bossPoolLeft":0,"virtualMs":121400},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":25,"bossPoolLeft":0,"virtualMs":121400},
"rng": {"setup":25,"shop":535,"waves":0,"draft":34,"bots":18101,"meta":103},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
@@ -1248,12 +1248,12 @@
},
{
"round": 12,
"ai_0": [1,15,12,0,2,6,8,"ac215c06",147,"arcaneShip 1/22, indomShip 0/8, kjeragShip 0/7, miraShip 1/10, preciShip 0/7, siracusaShip 2/98, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",33,46,13,118],
"ai_0": [1,15,12,0,2,6,8,"ac215c06",147,"arcaneShip 1/22, indomShip 0/8, kjeragShip 0/7, miraShip 1/10, preciShip 0/7, siracusaShip 2/98, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",35,46,11,118],
"ai_1": [1,17,12,0,0,6,8,"c4e0303a",121,"arcaneShip 1/24, emptyShip 1/0, indomShip 0/8, kjeragShip 0/4, preciShip 2/69, siracusaShip 1/28, soloShip 1/0, steadShip 0/4, victoriaShip 0/8, visiShip 0/8",46,46,0,115]
},
{
"round": 13,
"ai_0": [1,6,14,0,0,6,8,"953e06bc",186,"arcaneShip 1/31, indomShip 0/8, kjeragShip 0/7, miraShip 1/13, preciShip 0/7, siracusaShip 2/125, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",30,39,9,132],
"ai_0": [1,8,14,0,0,6,8,"953e06bc",186,"arcaneShip 1/31, indomShip 0/8, kjeragShip 0/7, miraShip 1/13, preciShip 0/7, siracusaShip 2/125, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",32,39,7,132],
"ai_1": [1,17,12,0,0,6,8,"eaf7de57",159,"arcaneShip 2/34, emptyShip 1/0, indomShip 0/8, kjeragShip 0/4, preciShip 2/87, siracusaShip 1/38, soloShip 1/0, steadShip 0/4, victoriaShip 0/8, visiShip 0/8",39,39,0,126]
}
],
@@ -1263,12 +1263,12 @@
"victory": true,
"roundsPassed": 14,
"eliminatedRound": null,
"lp": 6,
"lp": 8,
"bandId": "band_doberm",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":217,"bossDamage":427385,"dmgDealt":3482385,"gold":143,"itemsEquipped":20,"kills":242,"leaks":42,"lpLost":24,"merges":2,"perfectRounds":7,"refreshes":25},
"stats": {"activatedLayers":217,"bossDamage":394870,"dmgDealt":3465584,"gold":143,"itemsEquipped":20,"kills":247,"leaks":38,"lpLost":22,"merges":2,"perfectRounds":7,"refreshes":25},
"lineup": ["1_17_a@11,6[1_02_e_a+1_05_e_a]","3_19_a@11,7[3_05_e_b+5_01_e_a]","5_20_a@10,3[3_02_e_a+3_03_e_b]","4_10_a@10,4[4_03_e_a+1_01_e_b]","3_18_a@10,7[6_09_e_a+3_11_e_b]","1_08_b@10,8[4_11_e_a+3_08_e_a]","6_05_a@9,8","4_16_a@9,9"],
"bonds": ["arcaneShip 1/37","indomShip 0/8","kjeragShip 0/7","miraShip 1/13","preciShip 1/7","raidShip 1/0","siracusaShip 2/143","soloShip 1/11","steadShip 0/2","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"]
},
@@ -1282,7 +1282,7 @@
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":193,"bossDamage":247615,"dmgDealt":3734549,"gold":138,"itemsEquipped":20,"kills":269,"leaks":12,"lpLost":11,"merges":3,"perfectRounds":11,"refreshes":25},
"stats": {"activatedLayers":193,"bossDamage":280130,"dmgDealt":3810535,"gold":138,"itemsEquipped":20,"kills":267,"leaks":12,"lpLost":11,"merges":3,"perfectRounds":11,"refreshes":25},
"lineup": ["4_25_a@12,3","6_12_a@11,6[2_01_e_a+1_05_e_b]","1_17_b@11,7[4_08_e_a+1_01_e_a]","5_02_a@10,3","4_10_a@10,7[3_01_e_b+1_02_e_b]","3_18_a@10,8","5_12_a@9,8[1_05_e_a]","1_08_a@9,9[4_10_e_a+5_07_e_a]"],
"bonds": ["arcaneShip 2/49","emptyShip 1/0","indomShip 0/8","kjeragShip 0/8","preciShip 2/87","siracusaShip 1/57","soloShip 1/0","steadShip 0/8","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8"]
}
@@ -1352,7 +1352,7 @@
},
{
"round": 13,
"ai_0": [1,399,12,0,2,6,8,"5091cd4e",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,150]
"ai_0": [1,399,12,0,2,6,8,"5091cd4e",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,150]
}
],
"players": {
@@ -1366,9 +1366,9 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 0,
"reward": 425,
"stats": {"activatedLayers":2283,"bossDamage":1145000,"dmgDealt":3047851,"gold":175,"itemsEquipped":26,"kills":366,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":35},
"stats": {"activatedLayers":2277,"bossDamage":1145000,"dmgDealt":3047851,"gold":175,"itemsEquipped":26,"kills":366,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":35},
"lineup": ["4_02_b@12,7[3_08_e_a+3_03_e_b]","6_01_a@12,8[3_02_e_a]","6_16_a@11,5[1_05_e_b+3_03_e_a]","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_b@10,4[4_07_e_a+1_02_e_b]","6_05_a@10,5","4_01_b@10,6[6_03_e_a+4_06_e_a]","1_08_b@9,9"],
"bonds": ["arcaneShip 2/395","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/353","skillfulShip 0/343","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
"bonds": ["arcaneShip 2/395","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/353","skillfulShip 0/343","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
}
}
},
@@ -1545,22 +1545,22 @@
{
"round": 10,
"h_0": [1,26,12,0,0,5,8,"59b986b7",73,"arcaneShip 1/10, kazimierzShip 1/19, preciShip 1/14, raidShip 0/48, skillfulShip 1/6, soloShip 1/3, swiftShip 1/21, yanShip 0/1",31,31,0,87],
"ai_0": [1,22,12,0,0,5,8,"fa71d132",198,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/91, preciShip 1/25, sargonShip 0/2, skillfulShip 1/16, suntShip 1/16, swiftShip 1/50",31,31,0,88]
"ai_0": [1,22,12,0,0,5,8,"fa71d132",191,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/84, preciShip 1/25, sargonShip 0/2, skillfulShip 1/16, suntShip 1/16, swiftShip 1/50",31,31,0,88]
},
{
"round": 11,
"h_0": [1,22,12,0,0,5,8,"b7278144",146,"arcaneShip 0/10, kazimierzShip 1/34, preciShip 2/19, raidShip 1/61, skillfulShip 0/9, soloShip 1/6, suntShip 1/0, swiftShip 1/26, yanShip 0/1",21,25,4,99],
"ai_0": [1,12,12,0,0,5,8,"e240abda",236,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/99, preciShip 1/33, raidShip 1/5, sargonShip 0/2, skillfulShip 1/21, suntShip 1/16, swiftShip 1/57, visiShip 1/5",14,25,11,100]
"ai_0": [1,17,12,0,0,5,8,"e240abda",229,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/92, preciShip 1/33, raidShip 1/5, sargonShip 0/2, skillfulShip 1/21, suntShip 1/16, swiftShip 1/57, visiShip 1/5",20,25,5,100]
},
{
"round": 12,
"h_0": [1,15,12,0,0,6,8,"955db8af",172,"arcaneShip 0/10, kazimierzShip 1/42, preciShip 2/24, raidShip 1/69, skillfulShip 0/9, soloShip 1/6, suntShip 1/0, swiftShip 1/31, yanShip 0/1",24,31,7,111],
"ai_0": [1,2,12,0,2,6,8,"3c27819a",274,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/107, preciShip 1/41, raidShip 1/10, sargonShip 0/2, skillfulShip 1/26, suntShip 1/16, swiftShip 1/64, visiShip 1/10",18,31,13,113]
"ai_0": [1,7,12,0,2,6,8,"3c27819a",267,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 1/100, preciShip 1/41, raidShip 1/10, sargonShip 0/2, skillfulShip 1/26, suntShip 1/16, swiftShip 1/64, visiShip 1/10",18,31,13,113]
},
{
"round": 13,
"h_0": [1,5,12,0,0,6,8,"1bd1e6d0",244,"arcaneShip 1/10, kazimierzShip 1/54, preciShip 2/34, raidShip 1/105, skillfulShip 0/9, soloShip 0/6, suntShip 1/0, swiftShip 1/41, yanShip 0/1",20,57,37,122],
"ai_0": [0,0,14,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 0/115, preciShip 0/49, raidShip 0/15, sargonShip 0/2, skillfulShip 0/31, suntShip 0/16, swiftShip 0/71, visiShip 0/15",15,57,42,126]
"ai_0": [0,0,14,0,0,6,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/18, kazimierzShip 0/8, lateranoShip 0/108, preciShip 0/49, raidShip 0/15, sargonShip 0/2, skillfulShip 0/31, suntShip 0/16, swiftShip 0/71, visiShip 0/15",15,57,42,126]
}
],
"players": {
@@ -1588,9 +1588,9 @@
"title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"},
"trophies": 3,
"reward": 280,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2105193,"gold":126,"itemsEquipped":15,"kills":227,"leaks":67,"lpLost":31,"merges":3,"perfectRounds":9,"refreshes":23},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2118906,"gold":126,"itemsEquipped":15,"kills":233,"leaks":61,"lpLost":26,"merges":3,"perfectRounds":9,"refreshes":23},
"lineup": [],
"bonds": ["arcaneShip 0/2","deputShip 0/18","kazimierzShip 0/8","lateranoShip 0/115","preciShip 0/49","raidShip 0/15","sargonShip 0/2","skillfulShip 0/31","suntShip 0/16","swiftShip 0/71","visiShip 0/15"]
"bonds": ["arcaneShip 0/2","deputShip 0/18","kazimierzShip 0/8","lateranoShip 0/108","preciShip 0/49","raidShip 0/15","sargonShip 0/2","skillfulShip 0/31","suntShip 0/16","swiftShip 0/71","visiShip 0/15"]
}
},
"standIns": {
+238 -235
View File
@@ -62,7 +62,7 @@
"enemy_9010_acpupp": [1,0,0,0,959]
},
"hooks": [1,50,2700,403,403,633,637,325,34,34,25,21,19,14445,66,36,31,0,1,35,0,0,0],
"events": {"atk":425,"bounty":4,"deploy":16,"die":34,"dmg":637,"fx":65,"heal":319,"skill":45,"spawn":47,"status":127},
"events": {"atk":425,"bounty":4,"deploy":16,"die":34,"dmg":637,"fx":65,"heal":319,"skill":49,"spawn":47,"status":127},
"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 737544d2 e8326082 a526432e a0c50d61 50ebdcd8 bc51feb9 9efd18d0 e4933e87"
},
@@ -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,3,6102,233,0,9,1],
["p1",4,"op","chess_char_1_08_b",1,"uniequip_002_texas",0,0,7,0,10101,3473,0,5,2],
["p1",5,"op","chess_char_1_18_a",1,null,0,0,60,1,2095,6778,200,4,0],
["p1",5,"op","chess_char_1_18_a",1,null,0,0,60,1,2095,6778,200,4,2],
["p1",6,"op","chess_char_1_19_a",1,null,0,0,57,0,6210,3853,0,9,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,44001,3695,0,46,2],
@@ -118,10 +118,10 @@
"enemy_2005_axetro": [1,0,0,5090,2107],
"enemy_9011_acrefr": [1,0,0,0,1796]
},
"hooks": [1,62,3300,341,341,383,395,3119,45,56,11,16,31,7390,31,10,44,11,1,46,0,0,0],
"events": {"atk":359,"bounty":4,"deploy":18,"die":45,"dmg":395,"fx":48,"heal":11,"leak":11,"skill":22,"spawn":56,"status":70},
"hooks": [1,62,3300,341,341,383,395,3119,45,56,13,16,31,7390,31,10,44,11,1,46,0,0,0],
"events": {"atk":359,"bounty":4,"deploy":18,"die":45,"dmg":395,"fx":48,"heal":11,"leak":11,"skill":32,"spawn":56,"status":66},
"fx": {"aoe":4,"crit":21,"displace":9,"dp":2,"ember":1,"pulse":7,"revive":1,"summon":1,"taunt":2},
"snaps": "8776586b 475e50d7 071c9999 3f2ee9c1 ce8ea3d6 8deb7035 325c7285 cb230d4d 41ede204 5a0ac937 0e02c7e0"
"snaps": "7504f6da cf86e400 f495d65e 8677c52e e9aa3eb9 d0a4f672 0c91538a 8ccd440e d7b188a3 c3e5957d 0ac4da56"
},
"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,298,4,3],
["p1",2,"op","chess_char_1_12_b",1,"uniequip_002_estell",2042.2,1,7,4,1786,818,173,29,2],
["p1",3,"op","chess_char_1_20_b",1,"uniequip_002_liskam",2697.7,1,10,12,23549,748,0,74,1],
["p1",4,"op","chess_char_1_18_b",1,"uniequip_002_utage",1059.8,1,11,8,23248,8723,6429,94,0],
["p1",4,"op","chess_char_1_18_b",1,"uniequip_002_utage",1059.8,1,11,8,23248,8723,6429,94,1],
["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,14,8,43612,8315,0,80,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",0,0,12,9,874,2612,0,18,1],
@@ -181,10 +181,10 @@
"enemy_2008_flking": [1,0,0,11860,0],
"enemy_9016_acstmr": [1,1,0,0,8]
},
"hooks": [1,105,3300,574,574,636,728,216,87,91,20,19,0,15527,205,28,85,0,1,91,1,0,0],
"events": {"atk":638,"bounty":7,"deploy":17,"die":91,"dmg":707,"fx":89,"heal":191,"layer":1,"skill":39,"spawn":101,"status":200},
"hooks": [1,105,3300,574,574,636,728,216,87,91,21,20,0,15537,205,28,85,0,1,91,1,0,0],
"events": {"atk":638,"bounty":7,"deploy":17,"die":91,"dmg":707,"fx":89,"heal":191,"layer":1,"skill":41,"spawn":101,"status":198},
"fx": {"aoe":1,"blink":1,"displace":8,"ember":1,"explode":8,"garrison":1,"heal":3,"pulse":31,"reveal":1,"sleep":6,"summon":25,"undying":1,"zone":2},
"snaps": "1d3a19f7 d53d9d14 c0cf6dac 9771afb9 f66451da 2e24494b 0cf0c702 d7815c21 35a21883 d5c94b14 262d9144"
"snaps": "ba9efbdf a02eb193 460ac297 ade04e6a 7abe4489 4fa14e24 2d31bcf9 b7f6436a 82e99758 65a1db5b 88f5cc4f"
},
"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,86,27,1],
["p1",7,"op","chess_char_2_13_a",1,null,0,0,13,1,10519,3434,31,20,1],
["p1",8,"op","chess_char_2_16_a",1,null,3098.43,1,54,1,5847,4790,139,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,11702,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,4031,4830,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,1362,23,50,15,17,0,10435,29,27,36,27,1,30,0,2,0],
"events": {"atk":493,"bounty":1,"deploy":17,"die":23,"dmg":1384,"fx":20,"heal":91,"leak":27,"skill":30,"spawn":52,"status":161},
"hooks": [1,56,3296,431,431,1432,1451,1362,23,50,17,17,0,10435,29,27,36,27,1,30,0,2,0],
"events": {"atk":493,"bounty":1,"deploy":17,"die":23,"dmg":1384,"fx":20,"heal":91,"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": "7746810f f9f6f51a 5e52a277 dd305c4d 0be5e4d8 7dadd4d5 5ab2efb1 7b545442 735ce61b 91c1a49b 348b32f8"
"snaps": "7d5f4d88 88048f51 e9e719f0 b8d2b5ba a2a4bfe3 65d9b602 5dd73922 02ffbdd4 7bbdb4c0 21b1e9f8 6f1bc8cf"
},
"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,447,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,918,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":137,"leak":1,"skill":71,"spawn":97,"status":437},
"hooks": [1,102,4200,712,712,1693,1684,918,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":137,"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 27aa6baf 8473b310 4db9d080 26bd2a23 a88a78a3 52ebcf02 94d5144f d8cd9dd8 a76d8d6d 1b4c740e ad293d0e 591f82a8"
"snaps": "de0d6fce 6d6804e9 7c63987a 5372ddc5 6318b9f9 8f5d84b6 0b837d52 c2d66a0b 1598e056 15aad829 332efbf8 dec13c8b 769c352f 59d742ad"
},
"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",
@@ -371,7 +371,7 @@
"enemy_2017_csphts": [1,1,0,1838,9600]
},
"hooks": [1,108,4200,360,360,645,631,1626,77,93,21,21,0,19948,63,35,78,0,1,77,1,0,4],
"events": {"atk":368,"bounty":5,"deploy":30,"die":93,"dmg":631,"fx":83,"heal":10,"layer":1,"skill":42,"spawn":108,"status":47},
"events": {"atk":368,"bounty":5,"deploy":30,"die":93,"dmg":631,"fx":83,"heal":10,"layer":1,"skill":44,"spawn":108,"status":47},
"fx": {"aoe":2,"buff":1,"coin":21,"dodge":9,"explode":18,"garrison":1,"phase":2,"shieldBreak":1,"sp":1,"summon":18,"takeoff":3,"zone":6},
"snaps": "79c25635 2c57e2ca b98e04a2 8946ed61 cadfd56b 51cf42cb 5e3497b9 7a91ce48 dfbaac2d b12d3b6c e7817d41 5d4a2879 120fdfad e1177af4"
},
@@ -450,7 +450,7 @@
"time": 170,
"ticks": 5100,
"reason": "timeout",
"killed": 38,
"killed": 39,
"total": 87,
"unspawned": 0,
"bossHpLeft": null,
@@ -458,13 +458,13 @@
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 38,
"killed": 39,
"total": 87,
"perfect": false,
"leaks": 49,
"leaks": 48,
"leaksUncounted": 1,
"coins": 8,
"dmg": 412296,
"dmg": 417516,
"bossDmg": 0,
"heal": 5204,
"deaths": 18,
@@ -475,13 +475,13 @@
["p1",1,"op","chess_char_3_06_b",1,"uniequip_002_philae",0,0,6,0,1997,7128,0,4,1],
["p1",2,"op","chess_char_3_12_b",2,"uniequip_002_blemsh",0,0,20,6,32008,6013,5204,18,1],
["p1",3,"op","chess_char_3_02_b",1,"uniequip_002_ayer",0,0,10,1,28221,2861,0,38,1],
["p1",4,"op","chess_char_3_03_b",1,"uniequip_002_swire",0,0,122,2,24154,5092,0,25,1],
["p1",5,"op","chess_char_3_04_b",1,"uniequip_002_swire2",0,0,126,6,69447,15476,0,41,0],
["p1",4,"op","chess_char_3_03_b",1,"uniequip_002_swire",0,0,123,2,24154,5092,0,25,1],
["p1",5,"op","chess_char_3_04_b",1,"uniequip_002_swire2",0,0,127,7,74667,15476,0,44,0],
["p1",6,"op","chess_char_3_05_b",2,"uniequip_002_skadi",0,0,15,9,47006,13898,0,28,1],
["p1",7,"op","chess_char_3_21_a",2,null,0,0,2,0,17820,1464,0,13,1],
["p1",8,"op","chess_char_3_01_b",2,"uniequip_002_angel",0,0,3,2,29291,2037,0,39,1],
["p1",9,"op","chess_char_3_08_b",1,"uniequip_002_mint",0,0,4,4,28387,6186,0,6,1],
["p1",10,"op","chess_char_3_09_b",1,"uniequip_002_haini",0,0,119,1,30877,2604,0,28,1],
["p1",10,"op","chess_char_3_09_b",1,"uniequip_002_haini",0,0,120,1,30877,2604,0,28,1],
["p1",11,"op","chess_char_3_10_b",1,"uniequip_002_pinecn",0,0,1,2,21588,2470,0,8,1],
["p1",12,"op","chess_char_3_11_b",1,"uniequip_002_snhunt",0,0,5,5,81499,1950,0,31,8],
["p1","~1","token","token_10031_swire2_gdtrap",null,null,1000,0,13,0,0,0,0,0,null],
@@ -498,13 +498,15 @@
["p1","~12","token","token_10031_swire2_gdtrap",null,null,1000,0,88,0,0,0,0,0,null],
["p1","~13","token","token_10031_swire2_gdtrap",null,null,1000,0,90,0,0,0,0,0,null],
["p1","~14","token","token_10031_swire2_gdtrap",null,null,1000,0,92,0,0,0,0,0,null],
["p1","~15","token","token_10031_swire2_gdtrap",null,null,1000,0,111,0,0,0,0,0,null],
["p1","~16","token","token_10031_swire2_gdtrap",null,null,1000,0,113,0,0,0,0,0,null],
["p1","~17","token","token_10031_swire2_gdtrap",null,null,1000,0,116,0,0,0,0,0,null],
["p1","~18","token","token_10031_swire2_gdtrap",null,null,1000,0,127,0,0,0,0,0,null],
["p1","~15","token","token_10031_swire2_gdtrap",null,null,1000,0,96,0,0,0,0,0,null],
["p1","~16","token","token_10031_swire2_gdtrap",null,null,1000,0,112,0,0,0,0,0,null],
["p1","~17","token","token_10031_swire2_gdtrap",null,null,1000,0,114,0,0,0,0,0,null],
["p1","~18","token","token_10031_swire2_gdtrap",null,null,1000,0,117,0,0,0,0,0,null],
["p1","~19","token","token_10031_swire2_gdtrap",null,null,1000,0,128,0,0,0,0,0,null],
["p1","~20","token","token_10031_swire2_gdtrap",null,null,1000,0,129,0,0,0,0,0,null],
["p1","~21","token","token_10031_swire2_gdtrap",null,null,1000,1,130,0,0,0,0,0,null]
["p1","~21","token","token_10031_swire2_gdtrap",null,null,1000,0,130,0,0,0,0,0,null],
["p1","~22","token","token_10031_swire2_gdtrap",null,null,1000,0,131,0,0,0,0,0,null],
["p1","~23","token","token_10031_swire2_gdtrap",null,null,1000,1,132,0,0,0,0,0,null]
],
"enemies": {
"enemy_10030_vtwand": [1,0,0,0,0],
@@ -519,13 +521,13 @@
"enemy_1305_mhslim_2": [1,1,0,614,3110],
"enemy_1415_mmkabi_2": [1,0,1,0,17402],
"enemy_1513_dekght": [1,0,1,0,652],
"enemy_2021_syfish": [60,31,29,1948,244588],
"enemy_2021_syfish": [60,32,28,1948,249808],
"enemy_2025_syufo": [13,1,9,22098,66768]
},
"hooks": [1,130,5100,461,461,842,912,335,56,125,18,22,0,6797,71,39,88,46,1,61,11,8,0],
"events": {"atk":524,"bounty":4,"deploy":42,"die":79,"dmg":901,"fx":149,"heal":334,"layer":11,"leak":46,"skill":36,"spawn":121,"status":116},
"fx": {"blink":2,"buff":1,"burst":8,"coin":23,"displace":12,"explode":34,"garrison":6,"layer":5,"pull":12,"revive":5,"shield":1,"shieldBreak":1,"stone":1,"strike":8,"summon":22,"volley":8},
"snaps": "6bb6de80 e2903552 baa8ca04 62661ec3 51af3c30 19b8edc3 c105c3a8 554d90a2 610da2f3 bc874302 277d0d69 7526ecb4 3ee91009 d7ca6abb ed551b3b fcf3e300 b5e1725f"
"hooks": [1,132,5100,464,464,847,918,335,57,127,18,22,0,6797,72,39,88,45,1,62,11,8,0],
"events": {"atk":527,"bounty":4,"deploy":44,"die":82,"dmg":907,"fx":153,"heal":334,"layer":11,"leak":45,"skill":44,"spawn":123,"status":116},
"fx": {"blink":2,"buff":1,"burst":8,"coin":23,"displace":12,"explode":36,"garrison":6,"layer":5,"pull":12,"revive":5,"shield":1,"shieldBreak":1,"stone":1,"strike":8,"summon":24,"volley":8},
"snaps": "6bb6de80 e2903552 baa8ca04 62661ec3 51af3c30 19b8edc3 c105c3a8 554d90a2 79ebbf55 4f31b823 17567113 3d634b42 d4660efa 6a69f79b c304311c cbf8817f 45df2010"
},
"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",
@@ -600,28 +602,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],
@@ -630,26 +632,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 18eacc32 1699cf76 8b2f0b6b 5240d19a ecba5ee3 17279f2f 238d5681 c234e792 4a6595ac c041dfc2 cf7c60f7 32747652 29b222f3 f7165fb6 0d6d0b9b"
"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 a258b427 863efa4f 43f80da4 c6c6f65e 7aab7b2c 09e96778 49e81712 765f2fc7 05846969 695663e8 a03aa97d 7282e471 60f7d2fd 7acacb36 d0ea0f47"
},
"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",
@@ -720,31 +722,31 @@
"round": 10,
"seed": 1517795683,
"waveId": "act1autochess_h03",
"time": 172.23,
"ticks": 5167,
"time": 168.8,
"ticks": 5064,
"reason": "cleared",
"killed": 98,
"total": 99,
"total": 98,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 235,
"rngDraws": 269,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":98,"total":99,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":10,"dmg":998581,"bossDmg":0,"heal":33710,"deaths":12,"layerGains":{"preciShip":16}}
"p1": {"killed":98,"total":98,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":12,"dmg":1014531,"bossDmg":0,"heal":28275,"deaths":11,"layerGains":{"preciShip":16}}
},
"units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,7,0,855,3513,0,14,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",5964,1,10,19,186703,14571,14431,39,24],
["p1",3,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,105,0,2798,7776,0,3,2],
["p1",4,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,98,10,69837,6390,1926,26,3],
["p1",5,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,124,2,25015,6354,0,29,2],
["p1",6,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,8,6,81587,14105,0,54,2],
["p1",7,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,9,0,4490,2893,0,42,0],
["p1",8,"op","chess_char_4_10_b",1,"uniequip_002_aroma",319.84,1,4,38,315663,1344,0,45,4],
["p1",9,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,5,1,23193,1926,0,48,19],
["p1",10,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",526,1,106,19,228549,4348,0,32,14],
["p1",11,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,92,3,59891,2838,0,23,2],
["p1",12,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,6,0,0,641,17353,43,2],
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,7,1,684,3513,0,18,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",5849.95,1,10,15,197322,12343,12089,47,26],
["p1",3,"op","chess_char_4_11_b",1,"uniequip_002_cathy",0,0,104,0,1866,7466,0,2,2],
["p1",4,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,97,15,71683,6330,1866,26,3],
["p1",5,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,81,7,46030,4236,0,32,2],
["p1",6,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,8,6,79400,11299,0,54,2],
["p1",7,"op","chess_char_4_16_b",2,"uniequip_002_texas2",1619,1,9,0,4991,2637,0,46,1],
["p1",8,"op","chess_char_4_10_b",1,"uniequip_002_aroma",319.84,1,4,37,302772,1344,0,45,4],
["p1",9,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,5,0,28650,2013,0,56,22],
["p1",10,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",276,1,105,15,217355,4648,0,29,14],
["p1",11,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,91,2,63780,2838,0,24,2],
["p1",12,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,6,0,0,728,14319,41,2],
["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],
@@ -758,54 +760,55 @@
"enemy_1062_rager_2": [1,1,0,2729,34560],
"enemy_1160_hvyslr_2": [1,1,0,273,16128],
"enemy_1183_mlasrt": [1,1,0,2290,7488],
"enemy_1207_sfji_2": [1,1,0,1379,16128],
"enemy_1208_msfji_2": [3,3,0,0,90],
"enemy_1267_nhpbr": [45,45,0,10108,233280],
"enemy_1267_nhpbr_2": [24,24,0,4593,152064],
"enemy_1207_sfji_2": [1,1,0,2759,16128],
"enemy_1208_msfji_2": [2,2,0,0,60],
"enemy_1267_nhpbr": [45,45,0,7161,233280],
"enemy_1267_nhpbr_2": [24,24,0,6347,152064],
"enemy_1270_nhstlk": [1,1,0,0,13824],
"enemy_1270_nhstlk_2": [15,15,0,14627,276480],
"enemy_1270_nhstlk_2": [15,15,0,11782,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,19570,48532],
"enemy_2050_smsha": [1,1,0,9201,46080]
"enemy_1525_blkswb": [1,1,0,19723,64512],
"enemy_2050_smsha": [1,1,0,4403,46080]
},
"hooks": [1,122,5167,519,519,1453,1683,1131,110,114,74,74,0,15011,1694,49,99,1,1,117,2,1,5],
"events": {"atk":805,"bounty":5,"deploy":20,"die":113,"dmg":1666,"fx":376,"heal":85,"layer":2,"leak":1,"skill":147,"spawn":116,"status":551},
"hooks": [1,120,5064,549,549,1427,1629,1096,109,112,80,80,0,15333,1661,61,98,0,1,118,2,1,15],
"events": {"atk":812,"bounty":6,"deploy":19,"die":112,"dmg":1612,"fx":394,"heal":78,"layer":2,"skill":161,"spawn":115,"status":508},
"fx": {
"beam": 6,
"blink": 2,
"beam": 4,
"blink": 1,
"block": 3,
"burst": 1,
"displace": 29,
"dodge": 5,
"explode": 6,
"displace": 35,
"dodge": 15,
"explode": 7,
"explosion": 14,
"featherArrow": 2,
"flame": 2,
"frostNova": 19,
"frostNova": 22,
"garrison": 2,
"garrisonGrant": 1,
"healField": 2,
"levitate": 84,
"levitate": 78,
"overclock": 2,
"palsy": 3,
"pull": 2,
"revive": 2,
"riposte": 4,
"rockslide": 24,
"riposte": 10,
"rockslide": 26,
"shieldBreak": 17,
"slash": 25,
"slash": 23,
"slippery": 4,
"splash": 26,
"splash": 25,
"summon": 1,
"swordStorm": 1,
"telegraph": 3,
"telegraph": 2,
"thorns": 3,
"tornado": 3,
"tornadoPulse": 10,
"truesilver": 2,
"zone": 69
},
"snaps": "f45385f9 5ac40548 e3c9fba2 a82a7220 bdd7b9c2 fd0e860d bd3391dd c3d19232 b9b4d320 431a7210 d812e1fc 0fe7927f 4534a341 0235a8bb e47e6fe7 b8963aa3 9089360f 388e3ca0"
"snaps": "c0395dfa 57f0c65b dd894131 ed91373b 34231a57 1b35b13e fe159d38 ca7769d5 2fa91817 9d9936db fe9fdd5a ba850298 b65e03a5 3a2fd6e9 a6cd2062 be05fd78 23784858"
},
"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",
@@ -1381,50 +1384,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",
@@ -1444,15 +1447,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],
@@ -1466,17 +1469,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",
@@ -1506,7 +1509,7 @@
["p1",6,"op","chess_char_2_11_a",0,null,281.02,1,8,0,1310,3159,73,4,1],
["p1",7,"op","chess_char_2_13_a",0,null,2472.85,1,9,0,3396,0,0,9,1],
["p1",8,"op","chess_char_2_16_a",0,null,2453.67,1,6,0,0,0,0,0,0],
["p1",9,"op","chess_char_2_12_a",0,null,1516.59,1,10,0,0,0,0,0,0],
["p1",9,"op","chess_char_2_12_a",0,null,1516.59,1,10,0,0,0,0,0,1],
["p1",10,"op","chess_char_2_06_a",1,null,1972.8,1,4,0,0,0,9205,29,2],
["p1",11,"op","chess_char_2_10_a",0,null,1476.86,1,5,0,6017,0,0,37,1],
["p1",12,"op","chess_char_2_14_a",0,null,1290.53,1,13,0,0,3218,7324,17,2],
@@ -1527,10 +1530,10 @@
"enemy_1439_dslntf_2": [1,0,0,1653,8876],
"enemy_2048_smgrd": [1,0,0,9276,5313]
},
"hooks": [1,52,3300,365,365,817,928,2024,35,35,20,18,0,10481,58,45,35,0,1,35,0,3,0],
"events": {"atk":486,"bounty":2,"deploy":14,"die":35,"dmg":832,"fx":58,"heal":115,"skill":38,"spawn":50,"status":245},
"hooks": [1,52,3300,365,365,817,928,2024,35,35,21,19,0,10481,58,45,35,0,1,35,0,3,0],
"events": {"atk":486,"bounty":2,"deploy":14,"die":35,"dmg":832,"fx":58,"heal":115,"skill":40,"spawn":50,"status":245},
"fx": {"beam":44,"buff":3,"burst":3,"explode":1,"takeoff":1,"telegraph":6},
"snaps": "79233706 fbbde954 ee6e5b34 69de2ff9 2fbb6124 855e06ef 2443be8d f2e935bb c6381f34 ef3ebf31 6ce61ad9"
"snaps": "dd24c30b 998fef35 24ef9a9d 15101150 c7878eb9 fbcb6d46 11141ecc 2e44951a fc393125 6a69d388 cafe9828"
},
"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",
@@ -1556,7 +1559,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],
@@ -1581,10 +1584,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",
@@ -1638,7 +1641,7 @@
"enemy_2021_syfish": [1,1,0,7,4608]
},
"hooks": [1,81,4200,900,900,1329,1334,5635,65,65,135,134,0,8882,236,90,66,0,1,122,1,0,0],
"events": {"atk":960,"bounty":3,"deploy":12,"die":65,"dmg":1334,"fx":166,"heal":276,"layer":1,"skill":269,"spawn":81,"status":231},
"events": {"atk":960,"bounty":3,"deploy":12,"die":65,"dmg":1334,"fx":166,"heal":276,"layer":1,"skill":271,"spawn":81,"status":231},
"fx": {"buff":1,"coin":2,"ember":57,"garrison":1,"sp":91,"summon":1,"takeoff":1,"zone":12},
"snaps": "82b4e917 5672134c 05d92f11 b2c7e239 c06462e0 d3ca46fc 31c8426f 343c2dd7 26111ac7 b16080df 5722eb2b ce7baf95 7afa6396 451c3e89"
},
@@ -1718,7 +1721,7 @@
"rngDraws": 136,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":27,"total":53,"perfect":false,"leaks":26,"leaksUncounted":0,"coins":4,"dmg":369449,"bossDmg":0,"heal":17637,"deaths":9,"layerGains":{"raidShip":18}}
"p1": {"killed":27,"total":53,"perfect":false,"leaks":26,"leaksUncounted":0,"coins":4,"dmg":369672,"bossDmg":0,"heal":17637,"deaths":9,"layerGains":{"raidShip":18}}
},
"units": [
["p1",1,"op","chess_char_3_06_b",0,"none",0,0,2,0,1546,6162,0,9,1],
@@ -1731,7 +1734,7 @@
["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",523.13,1,1,3,80456,2725,0,23,4],
["p1",10,"op","chess_char_3_09_b",0,"none",0,0,3,3,11118,1534,0,14,4],
["p1",11,"op","chess_char_3_10_b",0,"none",0,0,5,2,50229,2120,0,23,7],
["p1",11,"op","chess_char_3_10_b",0,"none",0,0,5,2,50452,2120,0,23,7],
["p1",12,"op","chess_char_3_11_b",0,"none",0,0,7,6,53591,1610,0,19,10]
],
"enemies": {
@@ -1740,17 +1743,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,2116],
"enemy_1267_nhpbr_2": [24,14,8,11693,116615],
"enemy_1267_nhpbr_2": [24,14,8,11693,116338],
"enemy_1270_nhstlk_2": [9,4,5,10813,102658],
"enemy_1283_sgkill_2": [1,0,1,10377,7305],
"enemy_1425_lrcmra": [1,0,1,1414,11658],
"enemy_2008_flking": [1,0,1,7628,3574],
"enemy_2008_flking": [1,0,1,7628,4074],
"enemy_9006_actoxi": [12,5,5,5143,137719]
},
"hooks": [1,67,5100,542,542,1917,1917,30,36,60,109,110,0,7620,40,31,53,22,1,38,3,0,0],
"events": {"atk":737,"bounty":2,"deploy":14,"die":38,"dmg":1846,"fx":107,"heal":30,"layer":3,"leak":22,"skill":217,"spawn":65,"status":185},
"events": {"atk":737,"bounty":2,"deploy":14,"die":38,"dmg":1847,"fx":107,"heal":30,"layer":3,"leak":22,"skill":221,"spawn":65,"status":185},
"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 5d33fca3 778ce26f 3e05a037 632c144a 3ebbae7b 6b441959 3eb8ce65 aea6bfc5 ac090656 6255d971 7ca2ea90 891023ba 61b88830 61ed7aee"
"snaps": "757da7ba 23c3e301 44393780 5d33fca3 778ce26f 3e05a037 632c144a 3ebbae7b 227e6b37 c9fd5a40 c5d6aa3b a09e8b70 615646db 6a5ca35e d6831070 397bb5be 94e757e4"
},
"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",
@@ -1770,39 +1773,39 @@
"bossHpLeft": null,
"rngDraws": 84,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":3,"total":62,"perfect":false,"leaks":59,"leaksUncounted":0,"coins":0,"dmg":153754,"bossDmg":0,"heal":105,"deaths":26,"layerGains":{}}},
"players": {"p1":{"killed":3,"total":62,"perfect":false,"leaks":59,"leaksUncounted":0,"coins":0,"dmg":155355,"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,91,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,88,1,23224,4151,0,27,1],
["p1",6,"op","chess_char_3_14_b",0,"none",0,0,88,1,24314,4151,0,28,1],
["p1",7,"op","chess_char_3_17_b",0,"none",0,0,82,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,61,0,17805,4603,0,22,8],
["p1",11,"op","chess_char_4_02_a",0,null,0,0,66,0,9310,5869,0,7,0],
["p1",12,"op","chess_char_4_06_a",0,null,0,0,74,0,8278,3414,0,17,1],
["p1",13,"token","token_10028_vigil_wolf",null,null,0,0,39,0,5293,4499,0,13,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,7757,16859],
"enemy_1092_mdgint": [1,0,1,1026,391],
"enemy_1019_jshoot_2": [18,0,18,7757,17842],
"enemy_1092_mdgint": [1,0,1,1026,765],
"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,35491,15020],
"enemy_1509_mousek": [1,0,1,1686,0],
"enemy_9006_actoxi": [1,0,1,554,712]
},
"hooks": [1,90,5100,401,401,504,520,30,31,87,28,28,0,4426,16,27,62,56,1,34,0,0,18],
"events": {"atk":426,"deploy":28,"die":31,"dmg":496,"fx":88,"leak":56,"skill":56,"spawn":75,"status":79},
"hooks": [1,90,5100,403,403,507,523,30,31,87,28,28,0,4426,16,27,62,56,1,34,0,0,18],
"events": {"atk":429,"deploy":28,"die":31,"dmg":499,"fx":88,"leak":56,"skill":56,"spawn":75,"status":79},
"fx": {"camouflage":1,"crit":6,"dodge":18,"dp":4,"phase":1,"pulse":17,"revive":1,"sentry":9,"shadowWeave":9,"shield":4,"shieldBreak":2,"summon":3,"telegraph":1,"volley":6,"wolfShadowLost":5,"zone":1},
"snaps": "7c092213 c094fdd0 61afa7ba 7ab796f5 513e5d4e 4a180eed f5d75fff 20004d5b d4cf3e4e e860333f 2580eac0 7b4a044a 27c913a4 dd5637ac f12e3851 a9e14dc9 76318156"
"snaps": "7c092213 7eeed0b1 ac8f2ed3 3858c149 5c4c9042 6e0b6dfc ecb20138 7bcdcc30 87e0ab2d d6d96690 40c393bf c32baa7d e3c84877 885dfe2b 0445be42 4f286152 de0ce50d"
},
"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",
@@ -1832,7 +1835,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],
@@ -1855,10 +1858,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",
@@ -1931,20 +1934,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],
@@ -1958,15 +1961,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",
@@ -2407,7 +2410,7 @@
"time": 170,
"ticks": 5100,
"reason": "timeout",
"killed": 45,
"killed": 46,
"total": 52,
"unspawned": 0,
"bossHpLeft": null,
@@ -2415,51 +2418,51 @@
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 45,
"killed": 46,
"total": 52,
"perfect": false,
"leaks": 7,
"leaks": 6,
"leaksUncounted": 1,
"coins": 6,
"dmg": 424185,
"dmg": 434676,
"bossDmg": 0,
"heal": 6811,
"deaths": 1,
"layerGains": {"miraShip":2,"raidShip":30}
"heal": 12994,
"deaths": 2,
"layerGains": {"miraShip":3,"raidShip":24}
}
},
"units": [
["p1",1,"op","chess_char_3_12_a",1,null,4551.3,1,29,6,54926,1408,1412,222,9],
["p1",2,"op","chess_char_3_12_b",1,"none",5725.2,1,31,5,56773,1695,1695,156,8],
["p1",3,"op","chess_char_3_18_a",1,null,1481,1,10,0,5032,0,0,0,7],
["p1",4,"op","chess_char_3_18_b",1,"none",0,0,13,2,23994,1845,0,34,3],
["p1",5,"op","chess_char_3_04_a",2,null,1864,0,9,4,20729,85,85,35,2],
["p1",6,"op","chess_char_3_05_a",1,null,8667.1,1,44,5,69844,3792,3046,59,0],
["p1",7,"op","chess_char_3_04_b",2,"uniequip_004_swire2",2588.3,0,76,4,35924,271,271,49,3],
["p1",8,"op","chess_char_3_05_b",1,"none",6928.35,1,48,5,60847,302,290,29,0],
["p1",9,"op","chess_char_3_01_a",1,null,1471.8,1,1,1,3189,0,0,5,1],
["p1",10,"op","chess_char_3_19_a",1,null,1439,1,4,2,19951,0,0,69,2],
["p1",11,"op","chess_char_3_21_a",1,null,1364,1,7,11,66406,0,0,185,18],
["p1",12,"op","chess_char_3_01_b",1,"none",1770.9,1,2,0,5157,101,0,5,1],
["p1",13,"token","token_10028_vigil_wolf",null,null,880,1,5,0,1414,13,13,1,null]
["p1",1,"op","chess_char_3_12_a",1,null,4411.26,1,29,7,54875,1553,1557,222,9],
["p1",2,"op","chess_char_3_12_b",1,"none",5549.04,1,31,6,62074,1585,1585,156,8],
["p1",3,"op","chess_char_3_18_a",1,null,1481,1,10,0,6559,0,0,0,7],
["p1",4,"op","chess_char_3_18_b",1,"none",1845,1,59,5,64813,4695,2850,133,9],
["p1",5,"op","chess_char_3_04_a",2,null,1864,1,71,3,25310,47,47,73,3],
["p1",6,"op","chess_char_3_05_a",1,null,5876,1,11,0,20410,5937,5937,88,1],
["p1",7,"op","chess_char_3_04_b",2,"uniequip_004_swire2",2588.3,1,72,4,35869,271,271,48,3],
["p1",8,"op","chess_char_3_05_b",1,"none",6715.17,1,34,1,38309,723,723,31,2],
["p1",9,"op","chess_char_3_01_a",1,null,1471.8,1,1,4,15614,0,0,73,2],
["p1",10,"op","chess_char_3_19_a",1,null,1145.5,1,4,5,37409,294,0,120,4],
["p1",11,"op","chess_char_3_21_a",1,null,0,0,7,7,50328,1364,0,158,15],
["p1",12,"op","chess_char_3_01_b",1,"none",1770.9,1,2,3,18936,101,0,73,3],
["p1",13,"token","token_10028_vigil_wolf",null,null,880,1,5,1,4170,25,25,7,null]
],
"enemies": {
"enemy_10030_vtwand": [1,0,0,0,0],
"enemy_1010_demon": [1,1,0,1229,8100],
"enemy_1061_zomshd": [1,1,0,842,22794],
"enemy_1061_zomshd": [1,1,0,907,24354],
"enemy_1169_duphlx": [1,1,0,13,4320],
"enemy_1208_msfji_2": [1,1,0,0,30],
"enemy_1302_ymtro_2": [1,1,0,0,32400],
"enemy_1381_winman": [21,20,0,1188,127714],
"enemy_1381_winman_2": [12,12,0,1634,101088],
"enemy_1387_winshd": [12,7,0,2734,105434],
"enemy_1438_dspred": [1,1,0,1699,14256],
"enemy_2034_sythef": [1,0,1,173,8049]
"enemy_1381_winman": [21,20,0,1140,126962],
"enemy_1381_winman_2": [12,12,0,1018,101088],
"enemy_1387_winshd": [12,7,0,11118,114526],
"enemy_1438_dspred": [1,1,0,1030,14256],
"enemy_2034_sythef": [1,1,0,139,8640]
},
"hooks": [1,75,5100,1045,1045,1351,1351,2787,46,58,54,55,0,10953,86,57,53,1,1,48,10,0,0],
"events": {"atk":1045,"bounty":3,"deploy":22,"die":57,"dmg":1351,"fx":121,"heal":9,"layer":10,"leak":1,"skill":106,"spawn":66,"status":131},
"fx": {"aoe":27,"blink":5,"bondMilestone":1,"buff":4,"coin":38,"displace":4,"dp":12,"garrison":10,"shield":1,"volley":18,"wolfShadow":1},
"snaps": "ed245fce 888d207d b41cc6de 0615a243 d56e7aa5 bc9a636b ed13556c 9a525052 d9996f54 545630b5 97649c7e 19865724 4cdfb441 c8bc6704 1d2d7c18 66121418 db51567c"
"hooks": [1,74,5100,1436,1436,1954,1954,4047,48,55,66,63,0,16495,108,53,53,0,1,51,7,0,0],
"events": {"atk":1436,"bounty":3,"deploy":21,"die":55,"dmg":1953,"fx":139,"heal":9,"layer":7,"skill":129,"spawn":66,"status":163},
"fx": {"aoe":33,"blink":3,"bondMilestone":1,"buff":6,"camouflage":2,"coin":45,"displace":3,"dp":21,"garrison":7,"shield":1,"shieldBreak":1,"volley":15,"wolfShadow":1},
"snaps": "abe7527f 54d37f93 efbcd554 1b821656 ac9c33a5 269cae9f 3f080e3a ac1ef736 a3e7f2b4 6db02653 6f34cc83 4cb3d2a7 22ba9134 7bd4222a 99114c8f cc0a8a71 3af49395"
},
"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",
@@ -2473,23 +2476,23 @@
"time": 170,
"ticks": 5100,
"reason": "timeout",
"killed": 23,
"killed": 25,
"total": 65,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 226,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":23,"total":65,"perfect":false,"leaks":42,"leaksUncounted":0,"coins":2,"dmg":398198,"bossDmg":0,"heal":339,"deaths":25,"layerGains":{}}},
"players": {"p1":{"killed":25,"total":65,"perfect":false,"leaks":40,"leaksUncounted":0,"coins":2,"dmg":405931,"bossDmg":0,"heal":339,"deaths":25,"layerGains":{}}},
"units": [
["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,14528,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,22077,3847,339,27,1],
["p1",5,"op","chess_char_4_04_a",2,null,1623.38,1,96,2,14512,7177,0,15,0],
["p1",5,"op","chess_char_4_04_a",2,null,1623.38,1,96,2,14512,7177,0,15,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,56048,7476,0,11,0],
["p1",7,"op","chess_char_4_16_a",1,null,0,0,94,2,56048,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,1],
["p1",9,"op","chess_char_3_19_b",1,"none",0,0,5,5,51724,1648,0,66,1],
["p1",10,"op","chess_char_3_21_b",1,"uniequip_004_archet",0,0,95,10,148810,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],
@@ -2498,22 +2501,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,44006],
"enemy_1005_yokai_2": [27,9,16,8465,76599],
"enemy_1005_yokai_3": [6,1,5,17931,168377],
"enemy_1005_yokai": [24,11,13,0,47446],
"enemy_1005_yokai_2": [27,10,15,7994,80198],
"enemy_1005_yokai_3": [6,1,5,18348,169071],
"enemy_10065_ftzlc": [1,1,0,461,16339],
"enemy_10162_mnctpt": [1,0,1,8345,20600],
"enemy_10162_mnctpt": [1,0,1,8402,20600],
"enemy_1119_vofsd": [1,0,1,91,45544],
"enemy_1196_msfyin_2": [1,1,0,0,3],
"enemy_1254_lypa_2": [1,0,1,8723,22211],
"enemy_1254_lypa_2": [1,0,1,8720,22211],
"enemy_1367_dseed": [1,0,1,0,1701],
"enemy_1517_xi": [1,0,1,5597,2809],
"enemy_9016_acstmr": [1,1,0,0,8]
},
"hooks": [1,95,5100,533,533,815,851,518,50,90,17,28,16,7820,316,21,65,40,1,60,0,1,0],
"events": {"atk":606,"bounty":1,"deploy":28,"die":50,"dmg":845,"fx":94,"leak":40,"skill":34,"spawn":80,"status":171},
"hooks": [1,95,5100,540,540,822,868,503,52,90,29,28,16,7806,316,21,65,38,1,62,0,1,0],
"events": {"atk":613,"bounty":1,"deploy":28,"die":52,"dmg":862,"fx":94,"leak":38,"skill":57,"spawn":80,"status":159},
"fx": {"beam":5,"block":3,"buff":2,"burst":1,"coldWind":1,"displace":15,"dp":1,"iceSpike":1,"liberate":1,"overclock":1,"overload":1,"palsy":3,"revive":3,"rockfall":1,"sentry":6,"shadowWeave":11,"shield":7,"shieldBreak":6,"stone":1,"swordStorm":7,"telegraph":2,"volley":9,"wake":1,"wolfShadow":1,"wolfShadowLost":3,"zone":1},
"snaps": "de3ca31c 182b136c 5c27b035 f8f15ac1 250e93d3 6fde9171 1273cfdd f1c0ebf4 8e50442c 8277b17d 3a6591ca 5a144122 2014c207 a4445fd0 3d27fbdc 8d62308c d9b05d02"
"snaps": "3eace5b8 61afe6da d60244f3 faf22b5a 4881cb88 a672b7d1 5494d9f4 f46efc11 dbe6624d 0c5503e3 15f7a0aa 6dfaa49b e212841f ac453bb9 08d4fce3 ddc0d417 bce1ca77"
},
"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",
@@ -2542,7 +2545,7 @@
["p1",3,"op","chess_char_4_19_a",1,null,1246.08,1,65,0,1103,4894,0,25,2],
["p1",4,"op","chess_char_4_22_a",1,null,2022,1,7,0,2567,2035,1345,66,1],
["p1",5,"op","chess_char_4_12_b",1,"none",4562.3,1,11,1,35031,22559,14453,82,5],
["p1",6,"op","chess_char_4_04_b",2,"uniequip_002_ines",2149,1,14,7,116578,7728,1513,130,0],
["p1",6,"op","chess_char_4_04_b",2,"uniequip_002_ines",2149,1,14,7,116578,7728,1513,130,1],
["p1",7,"op","chess_char_4_07_b",2,"none",0,0,52,2,32885,4466,0,49,2],
["p1",8,"op","chess_char_4_09_b",2,"uniequip_004_mizuki",1756,1,1,1,12149,527,311,5,1],
["p1",9,"op","chess_char_4_20_a",1,null,1217,1,2,3,31008,0,0,78,3],
@@ -2563,10 +2566,10 @@
"enemy_1427_lrnazg": [1,1,0,1121,12000],
"enemy_2010_csdcr": [1,1,0,2800,9600]
},
"hooks": [1,65,5700,1112,1112,1391,1406,26919,46,48,28,23,74,26265,88,62,47,1,1,47,2,0,5],
"events": {"atk":1300,"bounty":4,"deploy":18,"die":47,"dmg":1362,"fx":173,"heal":54,"layer":2,"leak":1,"skill":50,"spawn":60,"status":296},
"hooks": [1,65,5700,1112,1112,1391,1406,26919,46,48,29,23,74,26284,88,62,47,1,1,47,2,0,5],
"events": {"atk":1300,"bounty":4,"deploy":18,"die":47,"dmg":1362,"fx":173,"heal":54,"layer":2,"leak":1,"skill":52,"spawn":60,"status":296},
"fx": {"aoe":2,"beam":1,"buff":2,"devour":4,"displace":9,"dodge":5,"ember":1,"garrison":2,"garrisonGrant":1,"link":3,"mote":98,"overload":3,"reweave":3,"riposte":1,"ripple":1,"sentry":2,"shadowWeave":25,"shield":1,"snow":1,"summon":1,"wake":5,"zone":2},
"snaps": "87882f34 f1ef51b0 5ea73197 12103e8c 818a5853 d502dfd9 a4648470 50fd3af4 3ee10a1b f5bde5aa 48390c43 c00ef057 c6197b86 312f318b d8927db2 7363ebf5 5661afc9 ce4c9d4f b9b4e6aa"
"snaps": "af06579c 3fd6674e fd823b49 3720d672 741d61e5 50c92e6f 03053fa6 2b115dd6 cee39675 eb727d90 c7a11465 7ba12c0d 15b1683c 0cb8d4c1 f3758ac4 4e5bee43 b1945ebf 6735e46d eb8d8138"
},
"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",
@@ -2608,7 +2611,7 @@
["p1",4,"op","chess_char_4_19_b",1,"none",2525.9,1,10,1,8997,1531,0,0,4],
["p1",5,"op","chess_char_4_22_b",1,"uniequip_002_svrash",3339.7,1,11,3,40105,9401,4764,46,1],
["p1",6,"op","chess_char_5_01_a",2,null,1722.46,1,40,4,78323,9642,6250,66,1],
["p1",7,"op","chess_char_4_16_b",1,"none",507.1,1,71,7,97453,7002,1741,95,0],
["p1",7,"op","chess_char_4_16_b",1,"none",507.1,1,71,7,97453,7002,1741,95,6],
["p1",8,"op","chess_char_4_13_b",2,"uniequip_004_gnosis",2553.2,1,8,4,31086,0,0,39,3],
["p1",9,"op","chess_char_4_20_b",1,"none",1844.7,1,9,21,131494,1023,0,64,3],
["p1",10,"op","chess_char_4_24_b",2,"none",2571.4,1,12,10,154430,6521,646,37,3],
@@ -2630,10 +2633,10 @@
"enemy_1512_mcmstr": [1,1,0,5044,92160],
"enemy_9008_acbunn": [1,1,0,1373,13824]
},
"hooks": [1,76,4847,498,498,1282,1296,2030,65,62,24,23,16,17319,1994,40,59,0,1,70,51,1,7],
"events": {"atk":626,"bounty":4,"deploy":15,"die":62,"dmg":1130,"fx":198,"heal":161,"layer":51,"skill":45,"spawn":73,"status":282},
"hooks": [1,76,4847,498,498,1282,1296,2030,65,62,30,27,16,17319,1994,40,59,0,1,70,51,1,7],
"events": {"atk":626,"bounty":4,"deploy":15,"die":62,"dmg":1130,"fx":198,"heal":161,"layer":51,"skill":57,"spawn":73,"status":274},
"fx": {"aoe":4,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":3,"dodge":7,"explode":26,"extraAttack":15,"garrison":27,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":34,"overload":2,"revive":1,"reweave":3,"rockfall":1,"shieldBreak":2,"sleepGuard":3,"snow":1,"stone":1,"swordStorm":6,"telegraph":2,"zone":39},
"snaps": "df8e82b1 ae59b91a b9e69aee 54684398 674617d0 34b43454 c92732bf 0e99eaf5 de2182e7 7cc37ebc a20561f1 d0906e76 6055c6d4 23f54f14 b6a0a55d ba7187de 068d47fc"
"snaps": "875d082b 2d4fe188 7fcebf0f 0527d505 bb63a101 48180fae 265505ed 9e4dc1bc 9ba5f9b2 78ac7755 8a05d44c 142de75f 53a7aa5d 12ee3255 c9643b48 a3e02d33 beb312d1"
},
"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",
@@ -2850,7 +2853,7 @@
["p1",1,"op","chess_char_6_03_b",1,"uniequip_002_yu",0,0,3,1,20785,8650,761,4,1],
["p1",2,"op","chess_char_6_15_a",1,null,0,0,72,0,4648,4541,127,7,0],
["p1",3,"op","chess_char_6_19_a",1,null,0,0,4,0,10791,2168,0,10,0],
["p1",4,"op","chess_char_6_17_a",1,null,2698,1,165,0,36644,8769,0,16,0],
["p1",4,"op","chess_char_6_17_a",1,null,2698,1,165,0,36644,8769,0,16,5],
["p1",5,"op","chess_char_6_14_a",1,null,0,0,5,0,0,2865,1728,4,0],
["p1",6,"op","chess_char_6_16_a",1,null,2272,1,164,0,5434,7969,551,10,1],
["p1",7,"op","chess_char_6_18_a",1,null,0,0,6,0,18432,1142,0,10,1],
@@ -2874,10 +2877,10 @@
"enemy_1439_dslntf": [1,0,1,397,581],
"enemy_2034_sythef_2": [1,0,1,0,0]
},
"hooks": [1,164,6688,224,224,492,501,1455,25,162,5,9,5,3835,304,16,134,133,1,26,1,1,0],
"events": {"atk":326,"bounty":1,"deploy":30,"die":29,"dmg":492,"fx":159,"heal":28,"layer":1,"leak":133,"skill":10,"spawn":148,"status":429},
"hooks": [1,164,6688,224,224,492,501,1455,25,162,10,9,5,3835,304,16,134,133,1,26,1,1,0],
"events": {"atk":326,"bounty":1,"deploy":30,"die":29,"dmg":492,"fx":159,"heal":28,"layer":1,"leak":133,"skill":19,"spawn":148,"status":420},
"fx": {"blessing":1,"blink":2,"burst":1,"camouflage":2,"displace":1,"explode":67,"garrison":1,"garrisonGrant":2,"link":1,"snow":7,"sunBurst":3,"talent":2,"telegraph":2,"zone":67},
"snaps": "7be27b91 c943410d 64b03089 6ae97088 01b7e919 f0606621 a8a7d3d2 326e93c5 c8b7806d 72213934 6c59c2e8 46031492 b007d572 b6487f9d 74643c5e 88d8de57 f5ed6b09 65885b91 511362fc cfcba33c 7ec69a72 b1b313f2 323f1f9a"
"snaps": "17f09123 c0131332 66f26d68 86af2dc5 8f4f279c 70a99c1c 96e8fe43 5999d0e0 bcc09d6c 6faf7e7a 036f63ea cdeb2d14 4dd070b4 a6114bb3 6c6dd9e4 35189b57 6e47c809 aeb15891 d23efffc 22ade03c 3f937772 67ad029b 0f4893b1"
},
"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",
@@ -2902,7 +2905,7 @@
["p1",1,"op","chess_char_6_06_b",1,"none",0,0,148,4,43979,5278,0,22,1],
["p1",2,"op","chess_char_6_15_b",1,"none",0,0,145,9,45050,4608,0,39,2],
["p1",3,"op","chess_char_6_07_b",1,"none",0,0,114,0,12364,6774,0,10,0],
["p1",4,"op","chess_char_6_17_b",1,"none",0,0,150,4,47715,7965,0,11,0],
["p1",4,"op","chess_char_6_17_b",1,"none",0,0,150,4,47715,7965,0,11,3],
["p1",5,"op","chess_char_6_08_b",1,"none",1508,1,178,4,19587,4249,1355,16,1],
["p1",6,"op","chess_char_6_09_b",2,"none",0,0,170,0,3589,3336,0,8,0],
["p1",7,"op","chess_char_6_11_b",1,"none",0,0,161,0,2881,6379,0,8,0],
@@ -2933,10 +2936,10 @@
"enemy_1389_winbab_2": [48,15,33,58301,120873],
"enemy_1525_blkswb": [1,0,1,5492,26880]
},
"hooks": [1,177,6357,742,734,878,985,131,84,173,14,17,90,10740,766,44,142,85,1,88,0,0,0],
"events": {"atk":743,"bounty":3,"deploy":32,"die":88,"dmg":893,"fx":65,"heal":32,"leak":85,"skill":28,"spawn":163,"status":198},
"hooks": [1,177,6357,742,734,878,985,131,84,173,17,17,90,10740,766,44,142,85,1,88,0,0,0],
"events": {"atk":743,"bounty":3,"deploy":32,"die":88,"dmg":893,"fx":65,"heal":32,"leak":85,"skill":34,"spawn":163,"status":192},
"fx": {"airstrike":30,"anchor":1,"appear":1,"blink":1,"copy":1,"disappear":1,"ember":1,"explode":2,"garrisonGrant":2,"heal":4,"link":1,"revive":4,"strike":6,"summon":2,"summonStun":2,"sunBurst":2,"sword":2,"telegraph":1,"zone":1},
"snaps": "96a9ade8 95bd34d3 22cde02f bfdf7c9f b704e2ff 533e4e6d f9839161 c0d87763 dd31d583 2f154e7c e95c70e8 b12e977e 28445154 84981e67 77911a36 ae832f02 26eb2103 559b0319 38c71138 f5f4ec23 b50e1813 7485763f"
"snaps": "019a26cc 5b05950a 0a971315 475e085c 336f2641 ef129a76 9765e8db 5eb77486 8001f9e6 69e956dd a08f5b49 0b8a729f 3d4648e1 6778b522 fd888cce feb01eca a6d8e49b 4973cd01 55d8f4c0 0f7b2c7b aaf03cab e8ccca57"
},
"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",
@@ -3039,7 +3042,7 @@
"enemy_9009_acfort": [1,0,1,7851,20555]
},
"hooks": [1,71,5700,366,366,1839,1839,0,31,65,30,31,0,3894,792,3,64,34,1,33,0,0,0],
"events": {"atk":423,"bounty":3,"deploy":7,"die":31,"dmg":1838,"fx":45,"leak":34,"skill":60,"spawn":69,"status":304},
"events": {"atk":423,"bounty":3,"deploy":7,"die":31,"dmg":1838,"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": "3f2254f6 76b714eb 68b544c1 a849f2de 6daa55dd 94390a52 78fcc75a dbb3a5df fe0212ec 22d4a949 3536f432 9397e521 c2a1e9bf 3bdcfcc4 480c60b0 1b202712 7d195566 3149a793 c6f3c943"
}
+45 -33
View File
@@ -46,7 +46,7 @@
["p1",2,"op","chess_char_4_09_a",0,null,0,0,1,2,32698,2984,0,17,1],
["p1",3,"op","chess_char_3_05_a",2,null,2505,1,74,2,29899,11886,0,45,2],
["p1",4,"op","chess_char_3_18_a",2,null,0,0,7,1,14915,3668,0,36,1],
["p1",5,"op","chess_char_4_04_a",1,null,497.45,1,91,2,12814,6576,0,82,0],
["p1",5,"op","chess_char_4_04_a",1,null,497.45,1,91,2,12814,6576,0,82,10],
["p1",6,"op","chess_char_4_07_a",1,null,0,0,75,1,18472,13600,0,43,1],
["p1",7,"op","chess_char_4_16_a",1,null,0,0,88,0,3482,9543,0,103,0],
["p1",8,"op","chess_char_3_01_a",2,null,1271,1,5,0,19777,0,0,141,2],
@@ -68,10 +68,10 @@
"enemy_1119_vofsd": [12,2,8,13284,62929],
"enemy_1121_lifbos_2": [9,1,7,11244,38577]
},
"hooks": [1,91,5100,870,870,1441,1438,3866,41,82,15,33,0,18850,26,37,50,30,1,42,5,0,6],
"events": {"atk":1013,"bounty":2,"deploy":41,"die":52,"dmg":1438,"fx":62,"heal":35,"layer":5,"leak":30,"skill":28,"spawn":62,"status":126},
"hooks": [1,91,5100,870,870,1441,1438,3866,41,82,25,33,0,18850,26,37,50,30,1,42,5,0,6],
"events": {"atk":1013,"bounty":2,"deploy":41,"die":52,"dmg":1438,"fx":62,"heal":35,"layer":5,"leak":30,"skill":47,"spawn":62,"status":126},
"fx": {"beam":4,"blink":11,"dodge":6,"dp":5,"garrison":5,"liberate":14,"slash":16,"telegraph":1},
"snaps": "c7e5e922 eab4c908 ad04ab78 0424dbc9 6eff6ee3 2b3bded9 3edb2c2b 02ffd9b9 6202ecfa f7f93735 4f3faeff 59df56c7 3b5b29d0 e6bceddc 4d139a38 ea4edd9b b54828d9"
"snaps": "dec536bc cbfb0e19 608df77d 4bc1be00 31528b18 50c15974 172a64b1 80ed52af 452e6c6e 2cbbaf73 d2893b65 71598545 0ff0a232 8497b8ca 56857b5a 512860f1 25c11ae9"
},
"standin-02": {
"about": "normal band 5 as stand-ins: chess_char_4_17_a→char_610_acfend chess_char_4_18_a→char_602_cdfend chess_char_5_05_a→char_602_cdfend chess_char_4_19_a→char_608_acpion chess_char_5_01_a→char_600_cpione chess_char_4_22_a→char_609_acguad chess_char_4_23_a→char_609_acguad chess_char_5_06_a→char_609_acguad chess_char_4_20_a→char_603_csnipe chess_char_4_24_a→char_604_ccast chess_char_5_02_a→char_604_ccast chess_char_5_03_a→char_612_accast",
@@ -173,7 +173,7 @@
"enemy_9011_acrefr": [12,2,3,23060,125251]
},
"hooks": [1,93,5700,1154,1154,1775,1770,135,58,77,25,27,0,28761,3090,58,74,19,1,58,0,0,8],
"events": {"atk":1247,"bounty":4,"deploy":19,"die":58,"dmg":1767,"fx":170,"heal":124,"leak":19,"skill":50,"spawn":86,"status":614},
"events": {"atk":1247,"bounty":4,"deploy":19,"die":58,"dmg":1767,"fx":170,"heal":124,"leak":19,"skill":56,"spawn":86,"status":614},
"fx": {"aoe":60,"displace":1,"dodge":18,"dp":30,"phase":1,"slash":48,"sp":2,"spGain":10},
"snaps": "dbcb5cec 56a8a8f6 e8d97a5d d2594a66 b6f753a4 78a2f35e 80349f15 2cd292dd d7240cae 4f120b33 69d90058 45d5da9a ae0b244b 17b17251 4df0cf30 f2c7c569 baa94919 07ba89e9 e4a68e4d"
},
@@ -290,7 +290,7 @@
"enemy_1387_winshd": [30,1,29,15960,140112]
},
"hooks": [1,134,6430,620,620,988,988,6,29,128,8,24,0,14868,2437,28,104,97,1,29,1,0,0],
"events": {"atk":651,"deploy":27,"die":31,"dmg":901,"fx":58,"heal":3,"layer":1,"leak":97,"skill":16,"spawn":114,"status":514},
"events": {"atk":651,"deploy":27,"die":31,"dmg":901,"fx":58,"heal":3,"layer":1,"leak":97,"skill":47,"spawn":114,"status":514},
"fx": {"aoe":5,"blink":2,"camouflage":1,"displace":22,"dodge":3,"dp":2,"explode":6,"garrison":1,"garrisonGrant":2,"slash":8,"zone":6},
"snaps": "8d7b3223 b6dafa64 3940bc8e fd79b9e2 e5e85618 6fcfc479 90958489 71bfbb91 d12c224c cd2c9c7f 4a9fff33 68e48a86 4b97e027 5f8e8dad da62239a 8601c0dc 73717b6d 9e4f784f 818e7ed1 c1f2a855 33118eab 9bafc0bc"
},
@@ -303,31 +303,43 @@
"round": 10,
"seed": 1442657168,
"waveId": "act1autochess_h03",
"time": 182.77,
"ticks": 5483,
"time": 182.73,
"ticks": 5482,
"reason": "cleared",
"killed": 42,
"killed": 46,
"total": 98,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 309,
"rngDraws": 295,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":42,"total":98,"perfect":false,"leaks":56,"leaksUncounted":0,"coins":8,"dmg":577212,"bossDmg":0,"heal":19789,"deaths":32,"layerGains":{"raidShip":26}}
"p1": {
"killed": 46,
"total": 98,
"perfect": false,
"leaks": 52,
"leaksUncounted": 0,
"coins": 8,
"dmg": 621924,
"bossDmg": 0,
"heal": 27685,
"deaths": 32,
"layerGains": {"raidShip":26,"visiShip":4}
}
},
"units": [
["p1",1,"op","chess_char_3_12_b",2,"none",1904,1,142,10,142532,5468,880,20,3],
["p1",2,"op","chess_char_4_09_b",0,"none",0,0,6,7,58726,3510,0,13,1],
["p1",3,"op","chess_char_3_05_b",2,"none",0,0,93,2,26911,9128,0,18,2],
["p1",4,"op","chess_char_3_18_b",2,"none",0,0,92,3,34127,9062,2680,27,2],
["p1",5,"op","chess_char_4_04_b",1,"none",0,0,141,0,16294,10620,0,30,0],
["p1",6,"op","chess_char_4_07_b",1,"none",0,0,96,0,28260,10676,0,25,1],
["p1",7,"op","chess_char_4_16_b",1,"none",0,0,139,4,24443,16559,0,44,0],
["p1",8,"op","chess_char_3_01_b",2,"none",0,0,132,1,21262,3020,0,27,1],
["p1",9,"op","chess_char_3_04_b",2,"none",0,0,4,0,0,6998,16229,17,2],
["p1",10,"op","chess_char_3_21_b",2,"none",0,0,114,12,159580,7911,0,50,1],
["p1",11,"op","chess_char_4_02_b",1,"none",0,0,2,3,47003,1483,0,29,1],
["p1",12,"op","chess_char_4_13_b",1,"none",0,0,99,0,18075,3262,0,24,2]
["p1",1,"op","chess_char_3_12_b",2,"none",1904,1,138,10,143498,5544,956,21,3],
["p1",2,"op","chess_char_4_09_b",0,"none",0,0,6,9,109862,3510,0,21,1],
["p1",3,"op","chess_char_3_05_b",2,"none",0,0,93,2,26416,9128,0,18,2],
["p1",4,"op","chess_char_3_18_b",2,"none",0,0,92,3,33920,9190,2808,27,2],
["p1",5,"op","chess_char_4_04_b",1,"none",0,0,137,0,13321,10188,0,31,8],
["p1",6,"op","chess_char_4_07_b",1,"none",0,0,96,1,23318,10676,0,23,1],
["p1",7,"op","chess_char_4_16_b",1,"none",0,0,136,3,27487,13878,0,36,0],
["p1",8,"op","chess_char_3_01_b",2,"none",0,0,132,2,23235,3020,0,28,1],
["p1",9,"op","chess_char_3_04_b",2,"none",0,0,4,0,0,13615,23921,23,2],
["p1",10,"op","chess_char_3_21_b",2,"none",0,0,117,11,155638,7911,0,47,1],
["p1",11,"op","chess_char_4_02_b",1,"none",0,0,2,5,48494,1483,0,31,1],
["p1",12,"op","chess_char_4_13_b",1,"none",0,0,99,0,16734,3262,0,23,2]
],
"enemies": {
"enemy_10162_mnctpt": [1,1,0,7990,39813],
@@ -337,15 +349,15 @@
"enemy_1023_jmage": [1,1,0,2086,5474],
"enemy_1025_reveng": [1,1,0,0,12442],
"enemy_1026_aghost": [1,1,0,0,9456],
"enemy_1043_zomsbr": [1,0,1,0,0],
"enemy_1251_lysyta_2": [57,26,31,15933,233652],
"enemy_1252_lysytb_2": [21,8,13,23530,207750],
"enemy_1329_cbshld_2": [12,1,11,32779,51307]
"enemy_1043_zomsbr": [1,1,0,0,6287],
"enemy_1251_lysyta_2": [57,29,28,15958,256380],
"enemy_1252_lysytb_2": [21,8,13,24284,223998],
"enemy_1329_cbshld_2": [12,1,11,35708,50755]
},
"hooks": [1,142,5483,525,525,773,799,61,74,141,16,40,0,6475,34,57,98,56,1,82,4,0,7],
"events": {"atk":605,"bounty":4,"deploy":44,"die":85,"dmg":799,"fx":65,"heal":57,"layer":4,"leak":56,"skill":32,"spawn":110,"status":114},
"fx": {"blink":11,"camouflage":1,"dodge":11,"dp":13,"explode":1,"garrison":4,"slash":24},
"snaps": "2e7a8cb5 d50cd79b d7949cae 1b9749a6 6e3d202d f87f73b6 b1e5a323 226ab559 3962f99c b1192e86 1eb20f4b be395d5b 618f2a05 d9a8eb7c b36cfafe 0c3a2649 cb88aa80 0533b183 5054650e"
"hooks": [1,138,5482,523,523,776,802,93,78,137,24,36,0,6341,31,56,98,52,1,86,5,0,4],
"events": {"atk":611,"bounty":4,"deploy":40,"die":85,"dmg":802,"fx":59,"heal":66,"layer":5,"leak":52,"skill":48,"spawn":110,"status":107},
"fx": {"blink":7,"camouflage":1,"dodge":8,"dp":13,"explode":1,"garrison":5,"slash":24},
"snaps": "32c77df2 e0a686fc 511e3625 2a2c9595 4d454cd6 ba68fe2a 509a2a40 0f18d74e 26c72bef 4d6d5cce 834f8ce9 462b0854 0fec1e43 fd1bd922 ab1c6896 53173cd5 dbdfb42d 6f942256 4207d16e"
},
"standin-07": {
"about": "elite band 6 as stand-ins: chess_char_4_17_b→char_610_acfend chess_char_4_18_b→char_602_cdfend chess_char_5_05_b→char_602_cdfend chess_char_4_19_b→char_608_acpion chess_char_5_01_b→char_600_cpione chess_char_4_22_b→char_609_acguad chess_char_4_23_b→char_609_acguad chess_char_5_06_b→char_609_acguad chess_char_4_20_b→char_603_csnipe chess_char_4_24_b→char_604_ccast chess_char_5_02_b→char_604_ccast chess_char_5_03_b→char_612_accast",
@@ -464,7 +476,7 @@
"enemy_1254_lypa_2": [48,1,47,74560,442348]
},
"hooks": [1,205,6900,1764,1764,3493,3533,147,56,189,40,44,0,26478,16569,72,179,133,1,59,0,0,10],
"events": {"atk":2013,"bounty":1,"deploy":23,"die":56,"dmg":3508,"fx":308,"heal":130,"leak":133,"skill":80,"spawn":194,"status":1297},
"events": {"atk":2013,"bounty":1,"deploy":23,"die":56,"dmg":3508,"fx":308,"heal":130,"leak":133,"skill":90,"spawn":194,"status":1297},
"fx": {"aoe":90,"displace":12,"dodge":31,"dp":24,"shieldBreak":59,"slash":80,"sp":1,"spGain":11},
"snaps": "4d4dc5ab 00efc642 4dc3aa32 13e9533b a3617bdf 587927d0 9fe3b7b8 0b39dceb 2981edf9 2ba1ce8c ef825d5b 85fd9608 a83f0a12 9e6b1271 7c5bf7ee f6ad8d8e c76286f6 037365de bf979497 771a38f7 b74aaf11 518326a3 19e5e338"
},
@@ -570,7 +582,7 @@
"enemy_1402_tgshd_2": [36,2,34,29224,197345]
},
"hooks": [1,165,6486,549,549,865,877,6809,50,159,17,36,0,8308,1125,49,131,106,1,50,1,1,0],
"events": {"atk":554,"deploy":32,"die":53,"dmg":802,"fx":530,"heal":21,"layer":1,"leak":106,"skill":34,"spawn":140,"status":378},
"events": {"atk":554,"deploy":32,"die":53,"dmg":802,"fx":530,"heal":21,"layer":1,"leak":106,"skill":74,"spawn":140,"status":378},
"fx": {"aoe":4,"blink":3,"burst":1,"displace":17,"dodge":3,"dp":17,"expose":414,"garrison":1,"garrisonGrant":3,"slash":67},
"snaps": "80709a69 1b180600 2040e410 16970933 7ec28fab 061d6583 09f2da3e 98333fee 741e4051 c5f8165a 55db9474 b9b2bf46 d72aea5d 20d591d8 3077f062 ab32900d eb4540a2 37c492a6 3e3cd8f7 20a4cb8e 22ada53e b9ce62f2"
}
+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);
});
}
+175
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,33 @@ 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 deploy-timed skill runs its duration (PR #109)', () => {
// 琳琅诗怀雅 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 deploy-timed skill (缄默德克萨斯 S2 阵雨连绵, 8 s) is a real duration skill since PR #109: on at the deployment
// (its own skill event, no 0.5 s window), off when the duration runs out, not ready again in that deployment
const t = makeBattle({ units: [{ chessId: 'chess_char_4_16_a', row: 10, col: 6, skillIndex: 1 }], autoFinish: false, timeLimit: 30 });
t.run(0.6);
const tx = t.unit('chess_char_4_16_a');
assert.equal(tx.skill.kind, 'duration');
assert.equal(tx.skill.active, true);
assert.deepEqual(t.eventsOf('skill').map((e) => e[2]), [1], 'on at the deployment');
t.run(12);
assert.equal(tx.skill.active, false, 'the duration ran out');
assert.deepEqual(t.eventsOf('skill').map((e) => e[2]), [1, 0], 'and the skill ended');
assert.equal(tx.skill.ready, false, 'one charge per deployment');
});
+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
@@ -32,6 +32,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',
@@ -535,11 +543,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 = {};