feedback4: 缇缇 S2's sleep pulses each count as a new 沉睡 entry for her trait (GitHub #162; the owner's decision of 2026-10-06)

- The S2 ward ("期间持续使自身与该目标周围的敌人陷入沉睡") re-applies a 0.5 s sleep every 0.25 s, so an enemy held asleep
  was one entry for garrison_125 (「每当范围内有敌人或干员进入沉睡或晕眩时」 萨尔贡 / 精准 +1, elite +2, at most 24 / 48 per
  battle). The reporter's first-hand memory of the official mode: 「攻击范围内有陷入沉睡就开始迅速增加直至上限」.
- The pulse now passes applyStatus `reenter` (a new opt-in: statusApplied reports `entered`; the buff refreshes as
  before) — once per enemy and pulse, not while a longer sleep from elsewhere holds it. With one enemy in her range the
  trait reaches its cap 5.63 s into S2; an enemy outside her range still adds nothing; a refresh elsewhere is still no
  entry (the garrison rule is unchanged). No enemy wakes between pulses. The pulse timing is [ASSUMED] (not in the data).
- Tests: garrisons_battle — new 缇缇 125 + S2 test (normal and elite: an enemy at her back adds nothing over 8 s of
  pulses; an enemy in range beside both wards, one entry per pulse, takes the trait to 24 / 48 within 6.5 s).
- Docs: DESIGN §24.8 (and the §24 intro), the Battle.applyStatus / garrison / kit comments.
- Golden: roster-042 (缇缇 S2): her trait's 24 layers come within her first S2 (26–37 s) instead of over four casts up
  to 177 s; same totals (capped), and the earlier layers end the fight at 166.97 s instead of 182.03 s, 5 knock-outs
  instead of 7.
This commit is contained in:
sganggs committed 2026-10-06 14:40:33 +08:00
1 parent 5bc2669407
commit c236273cf2
6 files changed
+136 -34

No files matched your search

+53
View File
@@ -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