diff --git a/docs/DESIGN.md b/docs/DESIGN.md index d61b534c..1e0ec2ac 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -2142,7 +2142,7 @@ Decisions of the owner, 2026-10-04: a 联防 devour counts members who enter alr ## 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; 高台 for every melee chess whose trait reads 「可以放置于远程位」 (GitHub #153, PR #69) → §24.2; the 5-阿戈尔 revives go to the first 3 members knocked out (GitHub #105, #140) → §24.3; 联防 阿戈尔 devours the operator in front whoever owns it (GitHub #140) → §24.4; a special terrain tile explains itself on a tap (GitHub #184, PR #185) → §24.5; 活性源石 was two different materials on the two boards (GitHub #184, PR #185) → §24.6; 阿戈尔's devoured base ATK is a 最终加算 and a fallen marker's marks still resolve (GitHub #165, PR #176) → §24.7. Merged pull requests keep their own notes where they changed the rules: deploy-timed skills as duration skills (PR #109, §5 skill contract), the teammate's hand / temp / equipment in prep scouting (PR #129), 灵巧's knocked-out aura and the hidden-layer bonds (PR #66, research 02). +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; 高台 for every melee chess whose trait reads 「可以放置于远程位」 (GitHub #153, PR #69) → §24.2; the 5-阿戈尔 revives go to the first 3 members knocked out (GitHub #105, #140) → §24.3; 联防 阿戈尔 devours the operator in front whoever owns it (GitHub #140) → §24.4; a special terrain tile explains itself on a tap (GitHub #184, PR #185) → §24.5; 活性源石 was two different materials on the two boards (GitHub #184, PR #185) → §24.6; 阿戈尔's devoured base ATK is a 最终加算 and a fallen marker's marks still resolve (GitHub #165, PR #176) → §24.7; 缇缇 S2's sleep pulses count for her trait (GitHub #162) → §24.8. Merged pull requests keep their own notes where they changed the rules: deploy-timed skills as duration skills (PR #109, §5 skill contract), the teammate's hand / temp / equipment in prep scouting (PR #129), 灵巧's knocked-out aura and the hidden-layer bonds (PR #66, research 02). ### 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`) @@ -2379,3 +2379,34 @@ Reports after the 0.1.3 release. Each was checked against the official data and the kill is B's); all three fail on 0.1.3's code. `feedback3-egir-down`, `feedback4-egir-unite` and `feedback3-ulpia-move` read `atkFinal` instead of `atkFlat`; `test/match/bosshp.test.js` recomputes ATK with the 最终加算. `facing`, `feedback2-doll`, `sim/core` pass unchanged. + +### 24.8 缇缇 S2 封护: every sleep pulse of the ward is a new 沉睡 entry for her trait (GitHub #162; the owner's decision of 2026-10-06) — `kits/tier5.js` (缇缇 `skchr_titi_2` `onTick`), `Battle.applyStatus` (`reenter`) + +- **Seen**: GitHub #162: the reporter remembers first-hand that in the official mode 缇缇's trait (garrison_125: 萨尔贡 / + 精准 +1 per entry, elite +2, at most 24 / 48 per battle) climbs fast while S2 runs (「攻击范围内有陷入沉睡就开始迅速增加直至 + 上限」); in the remake an enemy held asleep by S2 counted once. +- **Official**: the trait 「<战斗中>每当范围内有敌人或干员进入沉睡或晕眩时,使已激活的【萨尔贡】【精准】层数+1(每场战斗至多24层)」 + (elite +2, 48); S2 「…技能开启时使自身与攻击范围内生命比例最低的干员陷入沉睡直到技能结束,期间持续使自身与该目标周围的敌人陷入 + 沉睡…」 (12 s / elite 15 s). Neither the data nor PRTS gives the pulse's interval or sleep length. +- **Decision**: the owner's decision of 2026-10-06, following the report: each pulse counts as an entry. +- **Cause**: the pulse re-applies a 0.5 s sleep every 0.25 s (`AURA_DUR` / `AURA_IV`), so an enemy beside a ward never + leaves 沉睡 and `statusApplied.entered` (the target did not carry the status) is true at the first pulse only; the + garrison event counts new entries only. +- **Now**: the pulse calls `applyStatus(enemy, 'sleep', { duration: 0.5, source, reenter })`, a new opt-in that reports + `entered` anyway (the sleep buff refreshes as before): once per enemy and pulse — an enemy beside both wards re-enters + once — and not while a longer sleep from elsewhere holds it (more than 0.5 s left). The garrison rule is untouched: a + refresh elsewhere is still no entry (the 125 test's stun refresh), and the sleeper must be in 缇缇's attack range + (`S.inRange`) — an enemy at her back is slept by every pulse and adds nothing. One enemy in range: one entry per pulse + (the 0.25 s timer runs in 1/30 s ticks: every 8 ticks, 0.27 s), the cap (24 / 48, with the ward's own two operators) + 5.63 s into S2. No enemy wakes between pulses (the 0.5 s outlasts the pulse) and no status event is added, so the + fight changes only through the earlier layers. `entered` is read by the two garrison events only (冻结 for 初雪 / + 银灰; 沉睡 / 晕眩 by garrison_125_a / _b alone). +- [ASSUMED]: the pulse timing (0.25 s / 0.5 s, unchanged) and that each pulse is an entry (the reporter's memory; no + source gives the official interval); a longer sleep from another source is no re-entry. +- Golden: `roster-042` (缇缇 S2) — her trait's 24 layers now come within her first S2 (26.07–37.03 s) instead of over four + casts up to 176.73 s; the totals are the same (capped), and the earlier 萨尔贡 / 精准 layers end the fight at 166.97 s + instead of 182.03 s with 5 knock-outs instead of 7. +- Tests: `test/content/garrisons_battle.test.js` — new '缇缇 125 + S2 封护 (GitHub #162) …' (the real 缇缇, normal and + elite, S2 cast: an enemy at her back sleeps through 8 s of pulses and adds nothing beyond the ward's two operators; an + enemy in range beside both wards — one entry per pulse — takes the trait to 24 / 48 within 6.5 s; fails on 0.1.3's + code). The 125 test (a stun refresh is not an entry) and `kits_alt_t5`'s S2 test pass unchanged. diff --git a/server/sim/Battle.js b/server/sim/Battle.js index fc232337..67550290 100644 --- a/server/sim/Battle.js +++ b/server/sim/Battle.js @@ -1344,7 +1344,7 @@ export class Battle { } /** - * Apply a catalogue status. opts: { duration, source, value, force, refresh, point, resistApplied } — returns true + * Apply a catalogue status. opts: { duration, source, value, force, refresh, point, resistApplied, reenter } — returns true * when applied. Honours enemy immunities (stun/silence/sleep/frozen/levitate/feared) unless `force`. `beforeStatus` * handlers may cancel it or change `duration` / `value`. Official rules (buffs.js STATUS): 抵抗 (the `resist` status) * shortens the RESIST_STATUSES by its value (default half; applied after `beforeStatus`; `resistApplied` skips that @@ -1356,7 +1356,9 @@ export class Battle { * ([r, c] or {x, y}; default the source's tile — a new application moves the point); 恐惧 (`fear`) stamps where it * was applied and from where (fear.js stampFear: the fan of 恐惧可达地块 its movement uses). A stunned/sleeping operator * releases the enemies it blocks; a feared/levitated/unblockable/attracted enemy is released by its blocker. - * `statusApplied` reports the final duration and `entered` (the target did not carry the status before). + * `statusApplied` reports the final duration and `entered` (the target did not carry the status before) — or, with + * `reenter`, entered anyway: a pulse whose own short status the caller re-applies as a fresh one each time (缇缇 S2's + * sleep ward, DESIGN §24.8); the buff itself is refreshed as usual. * A unit that is 无敌 and 无法选中 at once (a 重生 in progress, a hovering or 永久无敌 leader part) takes no status from * the other side, `force` included — PRTS 无敌 "无法被不同阵营选中": so a status carried by the very hit that knocked an * enemy out does not land after its 重生's cleanse (DESIGN §21.4). A ground enemy's status never lands on an airborne @@ -1409,7 +1411,7 @@ export class Battle { } const source = opts.source ?? null; let entered = true; - for (const b of target.buffs) if ((b.status ?? b.key) === key) { entered = false; break; } + if (opts.reenter !== true) for (const b of target.buffs) if ((b.status ?? b.key) === key) { entered = false; break; } if (tpl.palsy) { this.addBuff(target, { ...palsyBuff(value ?? 1), duration, source }); } else if (tpl.valued != null && typeof tpl.mods === 'function' && opts.refresh == null) { diff --git a/server/sim/content/garrisons/battle.js b/server/sim/content/garrisons/battle.js index 4c533cb3..3883863c 100644 --- a/server/sim/content/garrisons/battle.js +++ b/server/sim/content/garrisons/battle.js @@ -14,7 +14,9 @@ // 'freeze', × prob) · act2autochess_gar_event_onstart (every deploy) · // act2autochess_gar_event_allyenemy_sleepstun_inrange (an enemy or operator in range ENTERS // sleep / stun). "进入…时": re-applying a running status (a refresh) is not a new entry -// (engine statusApplied ctx.entered). Targets: bond_by_id / bond_self (own active bonds) / bond_actived_maxstack; +// (engine statusApplied ctx.entered) — except a pulse that re-applies its own short status as a +// fresh one (applyStatus `reenter`: 缇缇 S2's sleep ward, DESIGN §24.8). +// Targets: bond_by_id / bond_self (own active bonds) / bond_actived_maxstack; // amounts: by_count / by_charcount_samerow / by_charlevel; conditions character_same_row / // character_same_col (≥ check_count incl. self). Gains go through support.gainLayers with // reason 'garrison', source = the trait's owner, cap = max_add_count_per_battle per (instance, bond). diff --git a/server/sim/content/kits/tier5.js b/server/sim/content/kits/tier5.js index 9116b5a0..2b13c50e 100644 --- a/server/sim/content/kits/tier5.js +++ b/server/sim/content/kits/tier5.js @@ -403,7 +403,8 @@ const KITS = { // enemies, sleeping enemies take 30 % ATK arts/s. T2 勇气的报偿: Sargon/Minos ops above 50 % HP get +20 ASPD. // S1 缓蚀 (duration): ATK +, attack@prob chance per attack to sleep the target attack@sleep s. // S2 封护 (duration): no attacks, ATK +; she and the lowest-HP-ratio operator in her range sleep until the skill ends - // (沉睡 = invulnerable), enemies around either of them keep falling asleep; T1 ×talent_scale. + // (沉睡 = invulnerable), enemies around either of them keep falling asleep — each 0.25 s pulse a new 沉睡 entry for + // her trait (GitHub #162, DESIGN §24.8); T1 ×talent_scale. chess_char_5_02_a: (bb, chess, def) => { const t0 = talent(chess, 0), t1 = talent(chess, 1); const sid = selectedId(chess, def); @@ -444,9 +445,22 @@ const KITS = { unit.mem.titiWardAcc += dt; if (unit.mem.titiWardAcc < AURA_IV - 1e-9) return; unit.mem.titiWardAcc = 0; + // "期间持续使自身与该目标周围的敌人陷入沉睡": every AURA_IV (0.25 s) the enemies around a ward sleep AURA_DUR (0.5 s), + // and each pulse is a new 沉睡 entry (`reenter`), so her trait garrison_125 「每当范围内有敌人或干员进入沉睡或晕眩时」 + // climbs on it to its per-battle cap (GitHub #162, the reporter's memory of the official mode: 「攻击范围内有陷入 + // 沉睡就开始迅速增加直至上限」; the owner's decision of 2026-10-06, DESIGN §24.8). [ASSUMED] the pulse timing (not in + // the data). Once per enemy and pulse (one beside both wards re-enters once); not while a longer sleep from + // elsewhere holds it. The 0.5 s sleep outlasts the 0.25 s, so the enemy never wakes between pulses — only the + // `entered` flag of the pulse changed (until 0.1.3 the first pulse alone was an entry). + const pulsed = new Set(); for (const a of unit.mem.titiWard || []) { if (!a.alive || !a.deployed || !a.findBuff('titi:ward')) continue; - for (const e of battle.foesInRadius(a.x, a.y, RING1)) if (e.alive) battle.applyStatus(e, 'sleep', { duration: AURA_DUR, source: unit }); + for (const e of battle.foesInRadius(a.x, a.y, RING1)) { + if (!e.alive) continue; + const reenter = !pulsed.has(e) && !e.buffs.some((b) => (b.status ?? b.key) === 'sleep' && b.timeLeft > AURA_DUR + 1e-9); + pulsed.add(e); + battle.applyStatus(e, 'sleep', { duration: AURA_DUR, source: unit, reenter }); + } } }, onEnd({ battle, unit }) { diff --git a/test/content/garrisons_battle.test.js b/test/content/garrisons_battle.test.js index f82fd61d..56bcdc93 100644 --- a/test/content/garrisons_battle.test.js +++ b/test/content/garrisons_battle.test.js @@ -302,6 +302,59 @@ test('allyenemy_sleepstun_inrange (缇缇 125): enemies or operators entering assert.equal(capOf('garrison_125_b'), 48); }); +test('缇缇 125 + S2 封护 (GitHub #162): every 0.25 s sleep pulse of the ward is a new entry — 8 s climb to the cap; a sleeper outside her range does not count', () => { + // the reporter's memory of the official mode: 「攻击范围内有陷入沉睡就开始迅速增加直至上限」; the pulse timing is [ASSUMED] + for (const id of ['chess_char_5_02_a', 'chess_char_5_02_b']) { + const gid = D.chess[id].garrisonIds.find((g) => GR(g).effectKey === 'act2autochess_gar_event_allyenemy_sleepstun_inrange'); + const per = num(GR(gid).bb.bond_add_count), cap = capOf(gid); + const s2 = D.chess[id].skills.find((x) => x.skillId === 'skchr_titi_2').index; + // 缇缇 (10,4) facing right: range 3-3 = rows 9–11, cols 4–7. The ward ally (10,6) is the only operator in it; the + // enemy (10,5) stands beside both wards (in her range), the one at (10,3) beside her back (out of her range) + const run = (enemies) => { + const h = makeBattle({ + seed: 3, autoFinish: false, timeLimit: 400, hooks: ['statusApplied'], + defs: { chess: { ward: rec('ward', []) }, enemies: { e_dummy: dummy() } }, + units: [{ chessId: id, skillIndex: s2, row: 10, col: 4 }, { chessId: 'ward', row: 10, col: 6 }], + bonds: { sargonShip: B(0), preciShip: B(0) }, enemies, + }); + h.step(1); + const tt = h.unit(id); + tt.skill.gainSp(1000); + assert.ok(tt.skill.activate('test'), `${id}: S2 cast`); + assert.equal(tt.skill.id, 'skchr_titi_2'); + return { h, tt }; + }; + // out of range only: the pulses sleep it (each one an entry for the engine) but her trait ignores it + { + const { h, tt } = run([{ key: 'e_dummy', pos: [10, 3] }]); + const back = h.enemies()[0]; + expectAll(h, ['sargonShip', 'preciShip'], 2 * per, `${id}: the ward's two operators fall asleep in her range`); + h.run(8); + assert.ok(tt.skill.active && back.s.flags.sleep, `${id}: the enemy at her back sleeps`); + const pulses = h.hooksOf('statusApplied').filter((c) => c.target === back && c.source === tt && c.entered); + assert.ok(pulses.length >= 30, `${id}: ${pulses.length} pulse entries in 8 s`); + expectAll(h, ['sargonShip', 'preciShip'], 2 * per, `${id}: an out-of-range sleeper does not count`); + checkInvariants(h.b); + } + // in range: one entry per pulse (beside both wards: still one), the cap within the 8 s + { + const { h, tt } = run([{ key: 'e_dummy', pos: [10, 5] }, { key: 'e_dummy', pos: [10, 3] }]); + const near = h.enemies().find((e) => Math.round(e.x) === 5); + let capAt = null; + for (let t = 0; t < 8 - 1e-9; t += 0.25) { + h.run(0.25); + if (capAt == null && (gains(h).sargonShip ?? 0) >= cap) capAt = t + 0.25; + } + assert.ok(tt.skill.active && near.s.flags.sleep, `${id}: still asleep after 8 s`); + const mine = h.hooksOf('statusApplied').filter((c) => c.target === near && c.source === tt && c.status === 'sleep'); + assert.equal(mine.filter((c) => c.entered).length * 2, mine.length, `${id}: two wards, one entry per pulse`); + expectAll(h, ['sargonShip', 'preciShip'], cap, `${id}: the cap (${cap})`); + assert.ok(capAt != null && capAt <= 6.5, `${id}: capped after ${capAt} s`); + checkInvariants(h.b); + } + } +}); + // --------------------------------------------------------------------------------------------------------------------- // ADD_BOND grants diff --git a/test/golden/roster.json b/test/golden/roster.json index ef4d8f24..d93d5cd0 100644 --- a/test/golden/roster.json +++ b/test/golden/roster.json @@ -2490,14 +2490,14 @@ "round": 10, "seed": 3609857679, "waveId": "act1autochess_h03", - "time": 182.03, - "ticks": 5461, + "time": 166.97, + "ticks": 5009, "reason": "cleared", "killed": 59, "total": 59, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 67, + "rngDraws": 60, "errors": {"count":0,"kinds":[]}, "players": { "p1": { @@ -2507,26 +2507,26 @@ "leaks": 0, "leaksUncounted": 0, "coins": 8, - "dmg": 703544, + "dmg": 706144, "bossDmg": 0, - "heal": 27086, - "deaths": 7, + "heal": 30645, + "deaths": 5, "layerGains": {"preciShip":80,"sargonShip":90} } }, "units": [ ["p1",1,"op","chess_char_4_17_b",2,"none",4566.9,1,3,0,0,0,0,0,0], - ["p1",2,"op","chess_char_4_18_b",2,"none",6783,1,5,0,4701,0,0,2,1], - ["p1",3,"op","chess_char_4_23_b",1,"uniequip_004_gvial2",3667.3,1,7,1,13592,289,0,6,1], - ["p1",4,"op","chess_char_4_19_b",1,"none",2525.9,1,10,0,19818,2156,0,18,5], - ["p1",5,"op","chess_char_4_22_b",1,"uniequip_002_svrash",3339.7,1,11,6,48019,10366,6083,60,1], - ["p1",6,"op","chess_char_5_01_a",2,null,0,0,76,4,34609,14118,3890,65,0], - ["p1",7,"op","chess_char_4_16_b",1,"none",1922.7,1,79,7,104704,7396,1628,92,7], - ["p1",8,"op","chess_char_4_13_b",2,"uniequip_004_gnosis",2553.2,1,8,6,36309,0,0,49,3], - ["p1",9,"op","chess_char_4_20_b",1,"none",1844.7,1,9,21,139236,1023,0,70,3], - ["p1",10,"op","chess_char_4_24_b",2,"none",2571.4,1,12,7,173659,7101,646,37,4], - ["p1",11,"op","chess_char_4_25_b",1,"uniequip_002_cetsyr",2172.3,1,4,0,19289,0,922,0,3], - ["p1",12,"op","chess_char_5_02_a",1,null,1570.4,1,6,7,109608,633,13917,34,4], + ["p1",2,"op","chess_char_4_18_b",2,"none",6783,1,5,1,1344,0,0,1,0], + ["p1",3,"op","chess_char_4_23_b",1,"uniequip_004_gvial2",3667.3,1,7,0,5552,649,0,2,1], + ["p1",4,"op","chess_char_4_19_b",1,"none",2165.9,1,10,1,22286,1891,0,3,4], + ["p1",5,"op","chess_char_4_22_b",1,"uniequip_002_svrash",3339.7,1,11,6,46148,13206,8486,53,1], + ["p1",6,"op","chess_char_5_01_a",2,null,0,0,73,0,39755,11316,3645,54,0], + ["p1",7,"op","chess_char_4_16_b",1,"none",1922.7,1,77,7,105393,6426,2581,116,6], + ["p1",8,"op","chess_char_4_13_b",2,"uniequip_004_gnosis",2553.2,1,8,6,29127,0,0,39,3], + ["p1",9,"op","chess_char_4_20_b",1,"none",1844.7,1,9,21,155751,1023,0,69,3], + ["p1",10,"op","chess_char_4_24_b",2,"none",2442.81,1,12,10,146453,7236,646,38,3], + ["p1",11,"op","chess_char_4_25_b",1,"uniequip_002_cetsyr",2172.3,1,4,0,9645,0,1552,0,3], + ["p1",12,"op","chess_char_5_02_a",1,null,1570.4,1,6,7,144690,903,13735,29,4], ["-","~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] ], @@ -2534,19 +2534,19 @@ "enemy_10054_cjhot": [1,1,0,675,17280], "enemy_10141_xdpeng_2": [1,1,0,19,11520], "enemy_1112_emppnt_2": [1,1,0,0,18432], - "enemy_1183_mlasrt": [1,1,0,694,7488], + "enemy_1183_mlasrt": [1,1,0,573,7488], "enemy_1249_lysdb_2": [1,1,0,0,6912], - "enemy_1267_nhpbr_2": [33,33,0,8239,209088], - "enemy_1270_nhstlk_2": [12,12,0,7629,221184], - "enemy_1272_nhtank_2": [6,6,0,13246,103680], - "enemy_1329_cbshld_2": [1,1,0,1316,14976], - "enemy_1512_mcmstr": [1,1,0,5981,92160], + "enemy_1267_nhpbr_2": [33,33,0,10082,209088], + "enemy_1270_nhstlk_2": [12,12,0,8739,221184], + "enemy_1272_nhtank_2": [6,6,0,10411,103680], + "enemy_1329_cbshld_2": [1,1,0,1824,14976], + "enemy_1512_mcmstr": [1,1,0,5044,92160], "enemy_9008_acbunn": [1,1,0,1338,13824] }, - "hooks": [1,79,5461,554,554,1344,1361,1606,69,66,32,28,0,17722,2148,41,59,0,1,76,50,1,6], - "events": {"atk":674,"bounty":4,"deploy":18,"die":66,"dmg":1181,"engage":10,"fx":205,"heal":174,"layer":50,"skill":60,"spawn":73,"status":314}, - "fx": {"aoe":5,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":4,"dodge":6,"explode":26,"extraAttack":12,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":40,"overload":1,"revive":1,"reweave":3,"riposte":2,"rockfall":1,"shieldBreak":2,"sleepGuard":4,"snow":1,"stone":1,"swordStorm":7,"telegraph":2,"zone":40}, - "snaps": "875d082b 85a4bc0b 9625d9f6 7e68be43 ddfb3f30 824ba052 277e00a7 05c18f12 c69c93d5 804b666f d57f06c7 1c059a92 2e2f05e5 8a408670 bcd9bab3 325c0f56 9eeb4763 8b918a67 86550ca0" + "hooks": [1,77,5009,530,530,1322,1337,2127,67,64,28,26,0,16734,2065,40,59,0,1,75,50,1,6], + "events": {"atk":637,"bounty":4,"deploy":16,"die":64,"dmg":1179,"engage":10,"fx":180,"heal":172,"layer":50,"skill":54,"spawn":73,"status":273}, + "fx": {"aoe":4,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":3,"dodge":6,"explode":26,"extraAttack":7,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":28,"overload":1,"revive":1,"reweave":3,"shieldBreak":1,"sleepGuard":4,"snow":1,"swordStorm":6,"telegraph":2,"zone":40}, + "snaps": "875d082b 85a4bc0b 9625d9f6 72f888b8 c305d6aa fde1882d 3b825ab8 9f744e02 8ef0fc59 54683ff7 87a57f66 4c384ffd 210d3aff 08bfd1d2 548b73fe bec1eec3 ae8f7277" }, "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",