feedback5 WJ: 伺夜's 狼群 takes its official 战术点形态 and S1 领袖的呼唤 acts by the pack's state (PRTS 狼群领袖 / S1 备注)

The fatal hit on the last 狼影, or a 撤退 while 伺夜 stands, no longer kills the pack. It enters its 战术点形态, the
remake's knocked-out piece: off the fight (no block, no attack, not targetable), kept, its tile reserved, 0 狼影. After
the 狼影 recovery time it comes back on its tile at full HP with 1 狼影 and a fresh 狼影 cycle. That time is the token
talent's interval, read from the data: 25 s on both chess. Until now it came back after the token's 10 s redeploy time
with 2 狼影.

Both packs share this (tokens.js installWolfTacticalPoint): the prep-placed 狼群 piece and the 伺夜 kit's own pack.
- A return timer carries the form's seq and a return cancels it, so a stale timer never revives the pack early.
- When 伺夜 leaves, a standing pack still leaves with him ('expired', no form). A pack in its form ends it with no
  return; his redeploy brings a fresh pack.

S1 casts when ready while the pack is on the field in either form, and always pays +cost DP:
① in its 战术点形态 the pack comes back at once with 1 狼影 and a fresh cycle;
② below the cap, +1 狼影, the pack's own cycle untouched;
③ at the cap, the pack's HP is reset to max.
S2 still needs a standing pack: its 备注 says nothing of the form [ASSUMED]. A held S2 gift stays on the pack through
the form [ASSUMED].

fx: the kit's pack keeps 'summon' / 'revive', the piece keeps 'wolfShadow' / 'wolfShadowLost'. Entering the form shows
'wolfShadowLost' with count 0. No new kinds, no client change. Data unchanged.

Docs: DESIGN §5.6 (SP_FULL note), PLAYING (作战中, 自动触发), SIM (token pieces), the tokens.js / kit comments.

Tests:
- kits_alt_t3, new: 3_19 战术点形态 (both chess × both packs), S1 ①②③ with the stale-timer guard, 撤退 / 伺夜 leaving.
- kits_alt_t3, rewritten: the S1 no-pack test, and the S2 auto-cast test (no cast in the form, a cast once the pack is
  back).
- kits_t3 3_19 wolf pack and tokens_devices 狼群 board piece: the form, then a return with 1 狼影.

Golden (each fields 伺夜 with his pack knocked out at its last 狼影 while he stands: 25 s out and 1 狼影 instead of
10 s and 2):
- roster-007 and roster-009: the fights change.
- roster-028 (elite S1): S1 brings the pack back at once.
- roster-026 and roster-040: 伺夜 falls before the old return; only the new 'wolfShadowLost' fx moves.
- bond-siracusaShip-high.
- matches solo-NORMAL-1, coop2-NORMAL-3, coop3-ABYSS-5, coop2-NORMAL-8-serverrun: the bots field 伺夜.
This commit is contained in:
sganggs committed 2026-10-05 19:47:12 +08:00
1 parent a98b02477d
commit 4304eb057c
11 files changed
+577 -222

No files matched your search

+184 -20
View File
@@ -8,6 +8,7 @@ import { getDefaultSource } from '../../server/sim/simdata.js';
import { skillSpecSource } from '../../server/sim/content/index.js';
import { effectiveProfile } from '../../server/sim/ai.js';
import { kitCoverage } from '../../tools/kit-coverage.mjs';
import { wolfShadows, wolfTacticalPoint } from '../../server/sim/content/tokens.js';
import KITS from '../../server/sim/content/kits/tier3.js';
const ds = getDefaultSource();
@@ -786,28 +787,186 @@ test('3_19 伺夜 S1 领袖的呼唤 (自动触发, the pack on the field): +DP
done(g);
});
test('3_19 伺夜 S1 领袖的呼唤: PRTS 备注 「仅场上存在狼群时可触发技能」 — no pack, no cast (the SP waits full, no DP); cast once the pack stands again', () => {
test('3_19 伺夜 S1 领袖的呼唤: PRTS 备注 「仅场上存在狼群时可触发技能」 — no pack on the field in either form, no cast (the SP waits full, no DP)', () => {
for (const id of BOTH('chess_char_3_19_a')) {
const b = SB(id, 'skchr_vigil_1');
const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 60, flags: { dpPerSec: 0 }, units: [U(id, 'skchr_vigil_1', 10, 3)] });
const u = h.unit(id), p = h.b.getPlayer('p1');
h.run(1);
const w = u.trait.reinforcement;
assert.ok(w && w.alive, 'the pack stands');
for (let i = 0; i < 10 && w.alive; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); // a wolf is lost per KO
assert.ok(!u.trait.reinforcement?.alive, 'no pack on the field');
// withdrawn by a rule ('expired', as when it leaves with 伺夜): gone, not in its 战术点形态
h.b.retreat(w, { reason: 'expired', permanent: true });
assert.equal(w.removed, true);
assert.equal(wolfTacticalPoint(w), null, 'no 战术点形态');
const dp0 = p.dp;
fill(u);
h.run(2);
assert.equal(u.skill.activations, 0, 'no pack, no cast');
assert.ok(u.skill.ready, 'the SP waits full');
approx(p.dp, dp0, 1e-9, 'no DP without the pack');
// the knocked-out pack comes back after its respawn time: cast at once — +DP and one more 狼影
assert.ok(h.runUntil(() => u.skill.activations === 1, 15), 'cast once the pack stands again');
done(h);
}
});
// 狼群 战术点形态 (PRTS 伺夜 天赋 狼群领袖 备注, 狼群 召唤物信息 备注) — both packs: the 伺夜 kit's own pack (no board piece) and
// the 狼群 piece placed in the prep phase (content/tokens.js wolfPack, adopted by the kit)
const WOLF = 'token_10028_vigil_wolf';
/** The 狼影 recovery time from the data: the token's 狼群领袖 talent interval. */
const WOLF_IV = (id) => ds.getToken(WOLF, id).talents.find((t) => t.bb['vigil_wolf_t_1_enhance[trigger].interval'] != null).bb['vigil_wolf_t_1_enhance[trigger].interval'];
/** 伺夜 `id` (skill `skillId`, default S3 when null) with the kit's pack, or with a prep-placed 狼群 piece (uid 2). */
const vigilUnits = (id, skillId, piece) => {
const sk = skillId ? { skillIndex: IDX(id, skillId) } : {};
return piece
? [{ chessId: id, row: 12, col: 3, uid: 1, ...sk }, { kind: 'token', tokenId: WOLF, ownerUid: 1, row: 11, col: 5, uid: 2 }]
: [{ chessId: id, row: 10, col: 3, uid: 1, ...sk }];
};
const close = (a, b, tol, msg) => assert.ok(Math.abs(a - b) <= tol, `${msg}: ${a} ≈ ${b}`);
const silence = (h, u, on) => (on ? h.b.addBuff(u, { key: 'test:silence', flags: { silence: true } }) : h.b.removeBuff(u, 'test:silence'));
test('3_19 伺夜 狼群 战术点形态: the fatal hit on the last 狼影 — out of the fight for the 狼影 interval (data), then back on its tile at full HP with one 狼影 and a fresh cycle (kit pack and prep piece)', () => {
for (const id of BOTH('chess_char_3_19_a')) for (const piece of [false, true]) {
const iv = WOLF_IV(id), tag = `${id} ${piece ? 'piece' : 'kit pack'}`;
assert.equal(iv, 25, '25 s on both chess (data)');
const h = makeBattle({ defs: { enemies: { enemy_d: dummy('enemy_d', { atk: 0 }) }, chess: noGarrison(id) }, timeLimit: 120, hooks: ['attack'], flags: { dpPerSec: 0 },
units: vigilUnits(id, null, piece), enemies: [{ key: 'enemy_d', pos: piece ? [11, 5] : [9, 3], time: 0.5 }] });
h.run(2);
const u = h.unit(1), w = u.trait.reinforcement, e = h.enemy('enemy_d');
if (piece) assert.equal(w, h.unit(2), `${tag}: the piece is the pack`);
else assert.ok(w && w.defId === WOLF && w.uid == null, `${tag}: the kit's own pack`);
assert.equal(e.blockedBy, w, `${tag}: blocks`);
const tile = [w.tileR, w.tileC];
h.b.dealDamage(null, w, { amount: 1e9, type: 'true' });
assert.ok(w.alive && wolfShadows(w) === 1, `${tag}: one 狼影 lost, still standing`);
h.b.dealDamage(null, w, { amount: 1e9, type: 'true' });
const t0 = h.b.time;
assert.equal(w.alive, false, `${tag}: out of the fight`);
assert.ok(wolfTacticalPoint(w), `${tag}: 战术点形态`);
assert.equal(w.removed, false, `${tag}: kept, not removed`);
assert.equal(wolfShadows(w), 0, `${tag}: 狼影 0`);
assert.equal(u.trait.reinforcement, w, `${tag}: still his 援军`);
assert.ok(h.b.isReservedTile(tile[0], tile[1]), `${tag}: its tile stays taken`);
assert.deepEqual(w.blocking, [], `${tag}: no block`);
assert.notEqual(e.blockedBy, w);
const bites0 = h.hooksOf('attack').filter((c) => c.attacker === w).length;
h.run(iv - 0.5);
assert.equal(w.alive, false, `${tag}: still in its 战术点形态 (not back after the token's 10 s redeploy time)`);
assert.equal(h.hooksOf('attack').filter((c) => c.attacker === w).length, bites0, `${tag}: no attack`);
assert.notEqual(e.blockedBy, w, `${tag}: blocks nothing`);
assert.ok(h.runUntil(() => w.alive, 1), `${tag}: back`);
close(h.b.time - t0, iv, 0.1, `${tag}: after the 狼影 interval`);
assert.equal(wolfTacticalPoint(w), null);
assert.deepEqual([w.tileR, w.tileC], tile, `${tag}: on its tile`);
assert.equal(w.hp, w.s.maxHp, `${tag}: full HP`);
assert.equal(wolfShadows(w), 1, `${tag}: one 狼影`);
assert.equal(w.s.blockCnt, 1);
assert.equal(u.trait.reinforcement, w, `${tag}: the same pack`);
// a fresh 狼影 cycle: the next one an interval after the return
const t1 = h.b.time;
h.run(iv - 0.5);
assert.equal(wolfShadows(w), 1, `${tag}: no 狼影 before a full interval`);
assert.ok(h.runUntil(() => wolfShadows(w) === 2, 1), `${tag}: the next 狼影`);
close(h.b.time - t1, iv, 0.1, `${tag}: one interval after the return`);
done(h);
}
});
test('3_19 伺夜 S1 领袖的呼唤 by the pack\'s state (PRTS 备注 ①②③): +cost DP each cast; ② +1 狼影 (own cycle untouched), ③ at the maximum HP to max, ① from the 战术点形态 back at once with one 狼影 and a fresh cycle — a stale return timer never revives it (kit pack and prep piece)', () => {
for (const id of BOTH('chess_char_3_19_a')) for (const piece of [false, true]) {
const b = SB(id, 'skchr_vigil_1'), iv = WOLF_IV(id), tag = `${id} ${piece ? 'piece' : 'kit pack'}`;
const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 120, flags: { dpPerSec: 0 }, units: vigilUnits(id, 'skchr_vigil_1', piece) });
const u = h.unit(1), p = h.b.getPlayer('p1');
h.step();
const w = u.trait.reinforcement;
assert.ok(w && w.alive && (!piece || w === h.unit(2)), tag);
silence(h, u, true); // casts only when the test asks (the SP refills in 24–27 s)
const cast = () => {
const n0 = u.skill.activations, dp0 = p.dp;
silence(h, u, false);
fill(u);
h.step();
silence(h, u, true);
assert.equal(u.skill.activations, n0 + 1, `${tag}: cast`);
approx(p.dp, dp0 + b.cost, 1e-9, `${tag}: +cost DP`);
};
// ② below the maximum, 5 s into the pack's cycle: one more 狼影; its own next one still comes at deploy + interval
assert.equal(wolfShadows(w), 2);
h.run(5);
cast();
assert.equal(wolfShadows(w), 3, `${tag} ②: +1 狼影`);
assert.equal(w.s.blockCnt, 3);
h.b.dealDamage(null, w, { amount: 1e9, type: 'true' });
assert.equal(wolfShadows(w), 2);
assert.ok(h.runUntil(() => wolfShadows(w) === 3, iv), `${tag} ②: the pack's own 狼影`);
close(h.b.time - w.deployedAt, iv, 0.1, `${tag} ②: on the cycle from its deployment, not from the cast`);
// ③ at the maximum: the pack's HP back to max, the count unchanged
w.hp = w.s.maxHp * 0.3;
cast();
assert.equal(wolfShadows(w), 3, `${tag} ③: still at the maximum`);
assert.equal(w.hp, w.s.maxHp, `${tag} ③: HP to max`);
// ① in its 战术点形态: back at once, one 狼影, full HP, a fresh cycle
for (let i = 0; i < 3; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' });
assert.ok(!w.alive && wolfTacticalPoint(w), `${tag}: 战术点形态`);
h.run(3);
assert.equal(w.alive, false, `${tag}: waits in its 战术点形态 without a cast`);
cast();
assert.ok(w.alive, `${tag} ①: back at once`);
assert.equal(wolfTacticalPoint(w), null);
assert.equal(wolfShadows(w), 1, `${tag} ①: one 狼影`);
assert.equal(w.hp, w.s.maxHp, `${tag} ①: full HP`);
assert.equal(u.trait.reinforcement, w, `${tag} ①: the same pack`);
const tUp = h.b.time;
assert.ok(h.runUntil(() => wolfShadows(w) === 2, iv + 1), `${tag} ①: the next 狼影`);
close(h.b.time - tUp, iv, 0.1, `${tag} ①: a fresh cycle from the return`);
// a stale timer: down, back by S1 3 s later, down again 2 s after that — the first form's return time passes
// without reviving it; it comes back an interval after the second knock-out
for (let i = 0; i < 2; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' });
assert.ok(wolfTacticalPoint(w));
const tDown = h.b.time;
h.run(3);
cast();
assert.ok(w.alive);
h.run(2);
h.b.dealDamage(null, w, { amount: 1e9, type: 'true' });
assert.ok(wolfTacticalPoint(w), `${tag}: 战术点形态 again`);
const tDown2 = h.b.time;
h.run(tDown + iv + 0.5 - h.b.time);
assert.equal(w.alive, false, `${tag}: the cancelled return does not revive it`);
assert.ok(h.runUntil(() => w.alive, 6), `${tag}: back`);
close(h.b.time - tDown2, iv, 0.1, `${tag}: an interval after the second knock-out`);
done(h);
}
});
test('3_19 伺夜 狼群: a 撤退 also ends in the 战术点形态 (狼影 0); 伺夜 leaving ends the form without a return — his redeploy brings a fresh pack; a standing pack leaves with him, no 战术点形态 (kit pack and prep piece)', () => {
const id = 'chess_char_3_19_a', iv = WOLF_IV(id);
for (const piece of [false, true]) {
const tag = piece ? 'piece' : 'kit pack';
const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 120, flags: { dpPerSec: 0 }, units: vigilUnits(id, null, piece) });
h.step();
const u = h.unit(1), w = u.trait.reinforcement;
assert.equal(wolfShadows(w), 2);
// a manual 撤退 (battle.retreat's default reason; no sim path withdraws the pack so today)
h.b.retreat(w);
assert.ok(!w.alive && wolfTacticalPoint(w), `${tag}: 撤退 ⇒ 战术点形态`);
assert.equal(wolfShadows(w), 0, `${tag}: 狼影 0`);
// 伺夜 leaves: the form ends there, no return
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
assert.equal(u.alive, false);
assert.equal(wolfTacticalPoint(w), null, `${tag}: the form ends with 伺夜`);
h.run(iv + 2);
assert.equal(w.alive, false, `${tag}: no return without 伺夜`);
// his redeploy: a fresh pack with the initial 狼影
assert.ok(h.b.redeploy(u, { free: true }));
h.step();
const w2 = u.trait.reinforcement;
assert.ok(w2 && w2.alive, 'the pack is back');
approx(p.dp, dp0 + b.cost, 1e-9, '+cost DP');
assert.equal(w2.mem.wolves, 3, 'one more 狼影 on the returned pack');
assert.ok(w2 && w2.alive, `${tag}: a pack with him`);
assert.equal(wolfShadows(w2), 2, `${tag}: the initial 狼影`);
assert.equal(h.b.allyUnits.filter((t) => t.alive && t.defId === WOLF).length, 1, `${tag}: one pack`);
// a standing pack leaves with him ('expired'): gone, no 战术点形态
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
assert.equal(w2.alive, false);
assert.equal(wolfTacticalPoint(w2), null, `${tag}: no 战术点形态 when it leaves with 伺夜`);
assert.equal(w2.removed, true);
done(h);
}
});
@@ -857,28 +1016,33 @@ test('3_19 伺夜 S2 领袖的馈赠: +DP, the pack recovers HP, its next attack
}
});
test('3_19 伺夜 S2 领袖的馈赠 (自动触发): no enemy needed — cast at full SP while the pack stands and holds no unused gift (PRTS 备注), never without the pack', () => {
for (const id of BOTH('chess_char_3_19_a')) {
const b = SB(id, 'skchr_vigil_2');
const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 60, flags: { dpPerSec: 0 }, units: [U(id, 'skchr_vigil_2', 10, 3)] });
const u = h.unit(id), p = h.b.getPlayer('p1');
test('3_19 伺夜 S2 领袖的馈赠 (自动触发): no enemy needed — cast at full SP while the pack stands and holds no unused gift (PRTS 备注), never while it is in its 战术点形态 [ASSUMED] — cast once it is back (kit pack and prep piece)', () => {
for (const id of BOTH('chess_char_3_19_a')) for (const piece of [false, true]) {
const b = SB(id, 'skchr_vigil_2'), tag = `${id} ${piece ? 'piece' : 'kit pack'}`;
const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 60, flags: { dpPerSec: 0 }, units: vigilUnits(id, 'skchr_vigil_2', piece) });
const u = h.unit(1), p = h.b.getPlayer('p1');
h.run(1);
const w = u.trait.reinforcement;
assert.ok(w && w.alive, 'the pack stands');
assert.ok(w && w.alive, `${tag}: the pack stands`);
const dp0 = p.dp;
fill(u);
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), 'cast with nobody on the field');
assert.ok(h.runUntil(() => u.skill.activations === 1, 1), `${tag}: cast with nobody on the field`);
approx(p.dp, dp0 + b.cost, 1e-6, '+cost DP at once');
assert.ok(w.mem.vigilGift, 'the pack holds the gift for its next attack');
fill(u);
h.run(3);
assert.equal(u.skill.activations, 1, '"狼群未获得此技能的充能时可触发": no second cast while the gift is unused');
// the gift spent (as by a bite), the pack gone: still no cast until a pack stands again
// the gift spent (as by a bite), the pack in its 战术点形态: no cast (the 备注 does not count that form for S2 —
// [ASSUMED]); once it is back, the waiting cast fires
w.mem.vigilGift = null;
for (let i = 0; i < 10 && w.alive; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); // a wolf is lost per KO
assert.ok(!u.trait.reinforcement?.alive, 'no pack on the field');
h.step(2);
assert.equal(u.skill.activations, 1, '"仅场上存在狼群…时可触发": no pack, no cast');
assert.ok(wolfTacticalPoint(w), `${tag}: 战术点形态`);
h.run(5);
assert.equal(u.skill.activations, 1, `${tag}: no cast while the pack is in its 战术点形态`);
assert.ok(u.skill.ready, 'the SP waits full');
assert.ok(h.runUntil(() => w.alive, WOLF_IV(id)), `${tag}: the pack is back`);
assert.ok(h.runUntil(() => u.skill.activations === 2, 0.5), `${tag}: cast once it stands again`);
assert.ok(w.mem.vigilGift, 'the returned pack holds the gift');
done(h);
}
});
+7 -1
View File
@@ -727,11 +727,17 @@ test('3_19 伺夜: wolf pack (2 → 3 wolves, block/bites, lose a wolf instead o
assert.ok(g.runUntil(() => v.skill.active, 5));
g.runUntil(() => !v.skill.active, 20);
approx(gp.dp, gdp + BB(gid).value, 1e-6, '精锐 DP over the full duration');
// the last wolf falls: 战术点形态 (PRTS 狼群领袖 备注) for the 狼影 interval, then the same pack back with one wolf —
// not a new pack after the token's redeploy time (until 0.2.0)
const iv = ds.getToken('token_10028_vigil_wolf', gid).talents[0].bb.interval;
g.b.dealDamage(null, gw, { type: 'true', amount: 1e7 });
g.b.dealDamage(null, gw, { type: 'true', amount: 1e7 });
assert.equal(gw.alive, false);
g.run(gw.base.respawnTime + 0.5);
assert.ok(v.trait.reinforcement !== gw && v.trait.reinforcement?.alive, 'pack re-summoned after its respawn time');
assert.equal(gw.alive, false, 'not back after the token’s redeploy time');
assert.ok(g.runUntil(() => gw.alive, iv), 'back after the 狼影 interval');
assert.equal(v.trait.reinforcement, gw, 'the same pack');
assert.equal(gw.mem.wolves, 1, 'with one wolf');
done(g);
});
+14 -6
View File
@@ -5,7 +5,7 @@ import assert from 'node:assert/strict';
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { hasGeneratedData, getDefaultSource } from '../../server/sim/simdata.js';
import { genericKit } from '../../server/sim/content/generic.js';
import { spawnYanyou, spawnMapChar, TOKEN_IDS, wolfShadows, tileFree, findSummonTile, summonToken } from '../../server/sim/content/tokens.js';
import { spawnYanyou, spawnMapChar, TOKEN_IDS, wolfShadows, wolfShadowInterval, wolfTacticalPoint, tileFree, findSummonTile, summonToken } from '../../server/sim/content/tokens.js';
import { startColdWind, kjeragColdWind, activateTurrets, terrainAt, deviceOverridesOf } from '../../server/sim/content/devices.js';
import { HUSK_REBIRTH } from '../../server/sim/content/enemies.js';
@@ -202,7 +202,7 @@ test('纸偶: appear burst = its ATK × damage_scale arts on the 8 surrounding t
checkInvariants(h.b);
});
test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits), fatal sheds a shadow, DEF ignore vs blocked, respawn', REAL, () => {
test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits), fatal sheds a shadow, DEF ignore vs blocked, 战术点形态 after the last one', REAL, () => {
let wolfHits = 0;
const h = makeBattle({
defs: { enemies: { enemy_walker: walker({ def: 200, atk: 0 }) } },
@@ -238,11 +238,19 @@ test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits),
assert.equal(wolf.hp, wolf.s.maxHp);
h.b.dealDamage(null, wolf, { amount: 1e6, type: 'true' });
h.b.dealDamage(null, wolf, { amount: 1e6, type: 'true' });
// the last shadow falls: 战术点形态 (PRTS 狼群 备注) — off the field, kept, 0 狼影, for the 狼影 interval (data), then
// back on its tile with one 狼影 (no longer a full pack after the token's redeploy time)
assert.equal(wolf.alive, false, 'last shadow falls');
const died = h.b.time;
assert.ok(h.runUntil(() => wolf.alive, 20), 'respawns');
approx(h.b.time - died, wolf.base.respawnTime, 0.3);
assert.equal(wolfShadows(wolf), 2, 'fresh pack');
assert.ok(wolfTacticalPoint(wolf), '战术点形态');
assert.equal(wolf.removed, false);
assert.equal(wolfShadows(wolf), 0);
const died = h.b.time, iv = wolfShadowInterval(wolf.def);
assert.equal(iv, tokDef(TOKEN_IDS.wolfPack, 'chess_char_3_19_b').talents[0].bb.interval, 'the talent interval');
assert.ok(h.runUntil(() => wolf.alive, iv + 1), 'back');
approx(h.b.time - died, iv, 0.004);
assert.deepEqual([wolf.tileR, wolf.tileC], [9, 6], 'on its tile');
assert.equal(wolfShadows(wolf), 1, 'one 狼影');
assert.equal(wolf.s.blockCnt, 1);
checkInvariants(h.b);
});