// test/content/op_judge.test.js — the 自选 operator kit of 斥罪 (char_4065_judge, 6★ 不屈者; kit // server/sim/content/kits/ops/op-judge.js), fielded the production way (a DIY slot + its `diy` pick, simdata getDiy) in // every form: tiers 5 / 6, normal (E2 Lv1, skill rank 4, no module) and elite (E2 Lv60, rank 7) with no module, UNY-X // “通往未来的荆棘之路” or UNY-Y “无罪” at stage 1 (tier 5) / 3 (tier 6). Every number is read back from data/backups.json // (the form of that slot status); the fidelity checklist of kits/README.md item by item. // Run: node --test test/content/op_judge.test.js import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { makeBattle, enemyRec, chessRec, checkInvariants } from '../helpers/battleHarness.js'; import { KITTED_CHARS, OPERATOR_KITS, KITS } from '../../server/sim/content/kits/index.js'; import { diyPool, validateDiyPicks } from '../../shared/diy.js'; const load = (f) => JSON.parse(readFileSync(new URL(`../../data/${f}.json`, import.meta.url), 'utf8')); const CHESS = load('chess'); const BACKUPS = load('backups'); const JUDGE = 'char_4065_judge'; const FORMS = BACKUPS.units[JUDGE].forms; const SLOT = { 5: 'chess_char_5_diy1_a', 6: 'chess_char_6_diy1_a' }; const UNYX = 'uniequip_002_judge', UNYY = 'uniequip_003_judge'; const S1 = 'skchr_judge_1', S2 = 'skchr_judge_2', S3 = 'skchr_judge_3'; const BARRIER = 'talent:judge:barrier'; const formOf = (tier, elite) => FORMS[elite ? (tier === 5 ? '2/60/7/1' : '2/60/7/3') : '2/1/4/0']; const skillOf = (tier, elite, id) => formOf(tier, elite).skills.find((s) => s.skillId === id); const modOf = (tier, mod) => (mod ? formOf(tier, true).modules.find((m) => m.uniEquipId === mod) : null); const approx = (a, b, msg, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg}: ${a} vs ${b}`); const dummy = (key, o = {}) => enemyRec({ key, hp: 1e9, speed: 0, mass: 0, ...o }); const ENEMIES = { enemy_dummy: dummy('enemy_dummy'), enemy_fly: dummy('enemy_fly', { motion: 'FLY' }), enemy_frail: dummy('enemy_frail', { hp: 10 }), }; const ALLIES = { t_mate: chessRec({ id: 't_mate', skill: null, stats: { maxHp: 10000, atk: 0, def: 0, blockCnt: 0 } }), t_medic: chessRec({ id: 't_medic', profession: 'MEDIC', skill: null, stats: { maxHp: 10000, atk: 500, def: 0, blockCnt: 0 } }) }; /** Every 自选 form: [tier, elite, module]. */ const FORMS_ALL = [[5, false, null], [6, false, null], ...[5, 6].flatMap((t) => [null, UNYX, UNYY].map((m) => [t, true, m]))]; const label = ([tier, elite, mod]) => `T${tier} ${elite ? 'elite' : 'normal'} ${mod ?? 'none'}`; /** A battle with 斥罪 as uid 1 at (row, col) facing RIGHT, plus `others`. */ function field({ tier = 5, elite = false, mod = null, skill = 2, row = 10, col = 5, others = [], seed = 5, setup = null } = {}) { const h = makeBattle({ defs: { enemies: ENEMIES, chess: ALLIES }, timeLimit: 900, autoFinish: false, seed, setup, flags: { dpPerSec: 0, dpMax: 999 }, hooks: ['damaged', 'heal', 'skillStart', 'skillEnd', 'statusApplied', 'kill'], captureNoisy: true, units: [{ uid: 1, diy: { slot: SLOT[tier], charId: JUDGE, skillIndex: skill, uniEquipId: mod }, elite, row, col }, ...others], }); h.step(); return { h, u: h.unit(1) }; } const barrier = (u) => u.findBuff(BARRIER)?.shield ?? 0; const t0Of = (u) => u.def.raw.talents.find((t) => t.index === 0).bb; const t1Of = (u) => u.def.raw.talents.find((t) => t.index === 1).bb; const thorns = (h, u, target = null) => h.hooksOf('damaged').filter((c) => c.source === u && (c.dmg?.tags ?? []).includes('judge:thorns') && (!target || c.target === target)); /** Knock an enemy out with damage credited to her. */ function killOne(h, u) { const e = h.spawn('enemy_frail', { pos: [12, 9] }); h.b.dealDamage(u, e, { amount: 1e6, type: 'true' }); assert.ok(!e.alive, 'knocked out'); } function done(h) { checkInvariants(h.b); assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); } test('斥罪 in every 自选 form: her operator kit (all three skills authored), the form\'s stats + module attributes, 1-1 range, blocks 3, melee ground-only, 叙拉古, no 特质; 不屈者: no heal from others; triggers from the data', () => { assert.equal(OPERATOR_KITS[JUDGE], KITS[JUDGE]); for (const f of FORMS_ALL) { const [tier, elite, mod] = f; for (const skill of [0, 1, 2]) { const { h, u } = field({ tier, elite, mod, skill, others: [{ uid: 2, chessId: 't_medic', row: 11, col: 5 }] }); const form = formOf(tier, elite), m = elite ? modOf(tier, mod) : null; assert.deepEqual([u.def.charId, u.def.diyFor, u.skill.id, !!u.kit.generic, u.kit.skillSource], [JUDGE, SLOT[tier], form.skills[skill].skillId, false, 'skills'], label(f)); assert.deepEqual([u.base.maxHp, u.base.atk, u.base.def], [form.stats.maxHp + (m?.attr.maxHp ?? 0), form.stats.atk + (m?.attr.atk ?? 0), form.stats.def + (m?.attr.def ?? 0)], `${label(f)}: stats`); assert.deepEqual([u.s.blockCnt, u.profile.attack, u.profile.dmgType, u.profile.canHitFly, u.profile.noHeal, u.base.bat], [3, 'melee', 'phys', false, true, 1.6], `${label(f)}: 不屈者`); assert.deepEqual(u.liveRangeGrid, form.rangeGrid, `${label(f)}: 1-1`); assert.deepEqual([u.def.bonds, u.def.raw.garrisonIds], [['siracusaShip'], []], `${label(f)}: bonds / 特质`); assert.ok(!u.s.flags.liftoff && !u.s.flags.camou && !u.s.flags.stealth, `${label(f)}: ground enemies target her`); const sk = form.skills[skill]; if (skill === 0) assert.deepEqual([u.skill.rule, sk.trigger.rawRule, sk.skillType, u.skill.maxCharges], ['DEFAULT', 'DEFAULT', 'AUTO', 2], `${label(f)}: S1 AUTO`); else assert.deepEqual([u.skill.rule, sk.trigger.rawRule, sk.skillType], ['DEFAULT', 'TAKE_DAMAGE', 'MANUAL'], `${label(f)}: S${skill + 1} trigger (the 重装 exception)`); assert.equal(u.skill.spType, skill === 2 ? 'hurt' : 'time', `${label(f)}: SP type`); // 无法被友方角色治疗: a heal from another unit does nothing u.hp = u.s.maxHp * 0.5; assert.equal(h.b.heal(h.unit(2), u, 500), 0, `${label(f)}: no heal from others`); done(h); } } // zh_CN numbers (full potential): E2 Lv1 2960 / 711 / 474, E2 Lv60 3686 / 839 / 568; UNY-X +150/+35/+15 → +300/+50/+33, UNY-Y +60/+48 → +85/+70 assert.deepEqual([FORMS['2/1/4/0'].stats.maxHp, FORMS['2/1/4/0'].stats.atk, FORMS['2/60/7/1'].stats.maxHp, FORMS['2/60/7/1'].stats.atk], [2960, 711, 3686, 839]); assert.deepEqual([modOf(5, UNYX).attr, modOf(6, UNYX).attr, modOf(5, UNYY).attr, modOf(6, UNYY).attr], [{ maxHp: 150, atk: 35, def: 15 }, { maxHp: 300, atk: 50, def: 33 }, { atk: 60, def: 48 }, { atk: 85, def: 70 }]); }); test('a 自选 pick: 斥罪 is offered at tiers 5 and 6 (she has a kit) and a roster with her passes validateDiyPicks', () => { const data = { chess: CHESS, backups: BACKUPS }; assert.ok(KITTED_CHARS.includes(JUDGE)); for (const t of [5, 6]) assert.ok(diyPool(t, { data, kitted: KITTED_CHARS }).includes(JUDGE), `tier ${t}`); assert.deepEqual(validateDiyPicks({ [SLOT[5]]: { charId: JUDGE, skillIndex: 0, uniEquipId: UNYY } }, { data, kitted: KITTED_CHARS }), { ok: true, picks: { [SLOT[5]]: { charId: JUDGE, skillIndex: 0, uniEquipId: UNYY } } }); }); test('T1 律法卫士: barrier 55 % max HP at every deployment (75 % UNY-X stage 3), +10 % per enemy she knocks out (12 %), never past 300 % of the current max HP; it absorbs before HP', () => { for (const f of FORMS_ALL) { const [tier, elite, mod] = f; const { h, u } = field({ tier, elite, mod, skill: 2 }); const t0 = t0Of(u); const x3 = elite && mod === UNYX && tier === 6; assert.deepEqual([t0.born_hp_ratio, t0.kill_hp_ratio, t0.max_hp_ratio], x3 ? [0.75, 0.12, 3] : [0.55, 0.1, 3], label(f)); approx(barrier(u), u.s.maxHp * t0.born_hp_ratio, `${label(f)}: at the deployment`); killOne(h, u); approx(barrier(u), u.s.maxHp * (t0.born_hp_ratio + t0.kill_hp_ratio), `${label(f)}: +1 kill`); // it absorbs: HP untouched until it runs out const hp0 = u.hp, e = h.spawn('enemy_dummy', { pos: [12, 9] }); h.b.dealDamage(e, u, { amount: 300, type: 'true' }); assert.equal(u.hp, hp0, `${label(f)}: absorbed`); approx(barrier(u), u.s.maxHp * (t0.born_hp_ratio + t0.kill_hp_ratio) - 300, `${label(f)}: −300`); // the cap: 300 % of the current max HP u.findBuff(BARRIER).shield = u.s.maxHp * 2.95; killOne(h, u); approx(barrier(u), u.s.maxHp * 3, `${label(f)}: capped`); killOne(h, u); approx(barrier(u), u.s.maxHp * 3, `${label(f)}: no gain at the cap`); // a max-HP change keeps the barrier; the cap follows the current max HP h.b.addBuff(u, { key: 'test:hp', mods: { hpPct: -0.5 } }); const kept = barrier(u); killOne(h, u); assert.equal(barrier(u), kept, `${label(f)}: above the new cap: no gain, no cut`); h.b.removeBuff(u, 'test:hp'); // a new deployment: a new barrier h.b.retreat(u); h.b.redeploy(u); h.step(); approx(barrier(u), u.s.maxHp * t0.born_hp_ratio, `${label(f)}: again at the redeployment`); done(h); } }); test('T2 荆棘环身: while she holds barrier, every damage from an enemy deals 53 % ATK arts back (61 % UNY-Y stage 3) — the hit that breaks it too; none without barrier, for a 流失, a 无来源 hit or an ally', () => { for (const f of FORMS_ALL) { const [tier, elite, mod] = f; const { h, u } = field({ tier, elite, mod, skill: 2, others: [{ uid: 2, chessId: 't_mate', row: 12, col: 3 }] }); const t1 = t1Of(u); assert.equal(t1.atk_scale, elite && mod === UNYY && tier === 6 ? 0.61 : 0.53, label(f)); const e = h.spawn('enemy_dummy', { pos: [12, 9] }); h.b.dealDamage(e, u, { amount: 100, type: 'phys' }); const t = thorns(h, u, e); assert.equal(t.length, 1, `${label(f)}: one reflection`); approx(t[0].amount, u.s.atk * t1.atk_scale, `${label(f)}: ${t1.atk_scale * 100} % ATK`); assert.deepEqual([t[0].type, t[0].dmg.isAttack, t[0].dmg.canDodge], ['arts', false, false], `${label(f)}: arts, not an attack, undodgeable`); h.b.loseHp(u, 50, { source: e }); h.b.dealDamage(e, u, { amount: 50, type: 'true', sourceless: true }); h.b.dealDamage(h.unit(2), u, { amount: 50, type: 'true' }); assert.equal(thorns(h, u).length, 1, `${label(f)}: no reflection for a 流失, a 无来源 hit or an ally`); // the breaking hit still reflects; then none u.findBuff(BARRIER).shield = 10; h.b.dealDamage(e, u, { amount: 200, type: 'true' }); assert.equal(barrier(u), 0, `${label(f)}: broken`); assert.equal(thorns(h, u).length, 2, `${label(f)}: the breaking hit reflects`); h.b.dealDamage(e, u, { amount: 50, type: 'true' }); assert.equal(thorns(h, u).length, 2, `${label(f)}: no barrier, no thorns`); done(h); } }); test('S1 一锤定音 (AUTO, data DEFAULT): the next attack adds 160 % / 180 % ATK arts; every cast spends all the SP; 蓄力 (twice the cost) also stuns 3 s and raises all her damage ×1.8 (thorns included), and reaching it restarts her attack cycle', () => { for (const [tier, elite] of [[5, false], [6, true]]) { const sk = skillOf(tier, elite, S1); let probe = null; const { h, u } = field({ tier, elite, skill: 0, setup: (b) => { // during her 蓄力 attack an enemy hits her: the reflection carries the ×1.8 b.on('hit', (c) => { if (!probe || probe.done || c.source !== probe.u || !c.dmg?.isAttack) return; probe.done = true; b.dealDamage(probe.e, probe.u, { amount: 10, type: 'true' }); }); } }); assert.deepEqual([sk.spCost, sk.initSp, sk.maxChargeTime, sk.bb.atk_scale_2, sk.bb['judge_s_1_enhance_checker.atk_scale'], sk.bb.stun], elite ? [4, 0, 2, 1.8, 1.8, 3] : [5, 0, 2, 1.6, 1.8, 3], `T${tier}`); // one charge: a plain cast u.skill.gainSp(sk.spCost, 'init'); assert.equal(u.skill.charges, 1); const e = h.spawn('enemy_dummy', { pos: [u.tileR, u.tileC + 1] }); assert.ok(h.runUntil(() => u.skill.activations === 1, 3), `T${tier}: cast at her attack`); assert.deepEqual([u.skill.charges, u.skill.sp], [0, 0], `T${tier}: all SP spent`); const hits = () => h.hooksOf('damaged').filter((c) => c.source === u && c.target === e); h.step(2); const gavel = hits().filter((c) => (c.dmg?.tags ?? []).includes('judge:gavel')); assert.equal(gavel.length, 1, `T${tier}: one arts rider`); approx(gavel[0].amount, u.s.atk * sk.bb.atk_scale_2, `T${tier}: ${sk.bb.atk_scale_2 * 100} % ATK arts`); assert.deepEqual([gavel[0].type, gavel[0].dmg.isSkill], ['arts', true]); assert.ok(!h.hooksOf('statusApplied').some((c) => c.target === e && c.status === 'stun'), `T${tier}: no stun without 蓄力`); // 蓄力: the SP runs on to twice the cost; the attack cycle restarts the moment it gets there h.b.kill(e, u); h.step(2); u.atkCd = 1.2; u.skill.setSpTotal(2 * sk.spCost - 0.02); assert.equal(u.skill.charges, 1); assert.ok(h.runUntil(() => u.skill.charges === 2, 1), `T${tier}: the stack fills`); h.step(); assert.equal(u.atkCd, 0, `T${tier}: attack cycle restarted`); const e2 = h.spawn('enemy_dummy', { pos: [u.tileR, u.tileC + 1] }); probe = { u, e: e2, done: false }; assert.ok(h.runUntil(() => u.skill.activations === 2, 1), `T${tier}: 蓄力 cast`); assert.deepEqual([u.skill.charges, u.skill.sp], [0, 0], `T${tier}: all SP spent`); h.step(2); const all = h.hooksOf('damaged').filter((c) => c.source === u && c.target === e2); const phys = all.find((c) => c.dmg?.isAttack); const rider = all.find((c) => (c.dmg?.tags ?? []).includes('judge:gavel')); approx(phys.amount, u.s.atk * 1.8, `T${tier}: the attack ×1.8`); approx(rider.amount, u.s.atk * sk.bb.atk_scale_2 * 1.8, `T${tier}: the rider ×1.8`); const th = thorns(h, u, e2); assert.ok(th.length >= 1, `T${tier}: a reflection during the attack`); approx(th[0].amount, u.s.atk * t1Of(u).atk_scale * 1.8, `T${tier}: thorns ×1.8`); const st = h.hooksOf('statusApplied').find((c) => c.target === e2 && c.status === 'stun' && c.source === u); assert.ok(st, `T${tier}: stun`); approx(st.duration, sk.bb.stun, `T${tier}: ${sk.bb.stun} s`); assert.equal(u.s.atkScaleMul, 1, `T${tier}: ×1.8 gone after the attack`); done(h); } }); test('S2 坚心苦修 (data DEFAULT): 20 s without attacks, 40 % / 50 % 庇护, 90 % / 110 % ATK arts to every ground enemy on her x-4 at 0.9 s and every second after (20 pulses), barrier gains ×1.6 / ×1.8 meanwhile', () => { for (const [tier, elite] of [[5, false], [6, true]]) { const sk = skillOf(tier, elite, S2); const { h, u } = field({ tier, elite, skill: 1 }); assert.deepEqual([sk.duration, sk.bb.damage_resistance, sk.bb.atk_scale, sk.bb.shield_scale, sk.rangeId], elite ? [20, 0.5, 1.1, 0.8, 'x-4'] : [20, 0.4, 0.9, 0.6, 'x-4'], `T${tier}`); u.skill.gainSp(999, 'init'); const inside = [h.spawn('enemy_dummy', { pos: [10, 6] }), h.spawn('enemy_dummy', { pos: [11, 4] }), h.spawn('enemy_dummy', { pos: [9, 5] })]; const fly = h.spawn('enemy_fly', { pos: [9, 6] }), out = h.spawn('enemy_dummy', { pos: [10, 7] }); assert.ok(h.runUntil(() => u.skill.active, 3), `T${tier}: cast with an enemy in range`); const t0 = h.hooksOf('skillStart').find((c) => c.unit === u).t; const pb = u.findBuff('protect'); approx(pb.data.value, sk.bb.damage_resistance, `T${tier}: 庇护`); const n0 = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isAttack).length; // the barrier gain of a kill ×(1 + shield_scale) const b0 = barrier(u); killOne(h, u); approx(barrier(u) - b0, u.s.maxHp * t0Of(u).kill_hp_ratio * (1 + sk.bb.shield_scale), `T${tier}: kill barrier ×${1 + sk.bb.shield_scale}`); assert.ok(h.runUntil(() => !u.skill.active, sk.duration + 1), `T${tier}: ends`); const pulses = h.hooksOf('damaged').filter((c) => c.source === u && (c.dmg?.tags ?? []).includes('judge:atonement')); const times = [...new Set(pulses.map((c) => Math.round((c.t - t0) * 100) / 100))]; assert.equal(times.length, 20, `T${tier}: 20 pulses`); approx(times[0], 0.9, `T${tier}: first at 0.9 s`, 0.05); approx(times[1] - times[0], 1, `T${tier}: then every second`, 0.05); for (const e of inside) assert.equal(pulses.filter((c) => c.target === e).length, 20, `T${tier}: ${e.tileR},${e.tileC} hit 20 times`); for (const c of pulses.slice(0, 3)) { approx(c.amount, u.s.atk * sk.bb.atk_scale, `T${tier}: ${sk.bb.atk_scale * 100} % ATK`); assert.deepEqual([c.type, c.dmg.isSkill, c.dmg.isAttack], ['arts', true, false]); } assert.ok(!pulses.some((c) => c.target === fly || c.target === out), `T${tier}: no flyer, nothing outside x-4`); assert.equal(h.hooksOf('damaged').filter((c) => c.source === u && c.dmg?.isAttack && c.t > t0 + 0.01 && c.t < t0 + sk.duration - 0.01).length - n0, 0, `T${tier}: no attack while it runs`); assert.equal(u.findBuff('protect'), null, `T${tier}: 庇护 gone`); done(h); } }); test('S3 披荆斩棘 (data DEFAULT, 受击回复 27 / 24 SP — absorbed hits count): barrier +70 % / +100 % max HP (to the cap), 30 s ATK +260 % / +320 %, interval 1.6 + 0.9 s, taunt +1', () => { for (const [tier, elite] of [[5, false], [6, true]]) { const sk = skillOf(tier, elite, S3); const { h, u } = field({ tier, elite, skill: 2 }); assert.deepEqual([sk.duration, sk.bb.hp_ratio, sk.bb.atk, sk.bb.base_attack_time, sk.bb.taunt_level, sk.spCost, sk.initSp, sk.spType], elite ? [30, 1, 3.2, 0.9, 1, 24, 0, 'INCREASE_WHEN_TAKEN_DAMAGE'] : [30, 0.7, 2.6, 0.9, 1, 27, 0, 'INCREASE_WHEN_TAKEN_DAMAGE'], `T${tier}`); const src = h.spawn('enemy_dummy', { pos: [12, 9] }); for (let i = 0; i < sk.spCost - 1; i++) h.b.dealDamage(src, u, { amount: 1, type: 'true' }); assert.equal(u.skill.sp, sk.spCost - 1, `T${tier}: one SP per hit, the barrier absorbing`); assert.equal(u.hp, u.s.maxHp, `T${tier}: HP untouched`); h.b.dealDamage(src, u, { amount: 1, type: 'true' }); assert.ok(u.skill.ready, `T${tier}: ready`); h.run(1); assert.equal(u.skill.activations, 0, `T${tier}: no enemy in range, no cast`); const b0 = barrier(u); h.spawn('enemy_dummy', { pos: [10, 6] }); assert.ok(h.runUntil(() => u.skill.active, 3), `T${tier}: cast with an enemy in range`); approx(barrier(u), Math.min(u.s.maxHp * 3, b0 + u.s.maxHp * sk.bb.hp_ratio), `T${tier}: +${sk.bb.hp_ratio * 100} % max HP`, 1e-3); approx(u.s.atk, u.base.atk * (1 + sk.bb.atk), `T${tier}: ATK`); approx(u.s.interval, 2.5, `T${tier}: 1.6 + 0.9 s`); assert.equal(u.s.taunt, 1, `T${tier}: taunt +1`); // the cap holds for the skill's barrier too h.b.retreat(u); h.b.redeploy(u); h.step(); u.findBuff(BARRIER).shield = u.s.maxHp * 2.5; u.skill.gainSp(999, 'init'); assert.ok(h.runUntil(() => u.skill.active, 5)); approx(barrier(u), u.s.maxHp * 3, `T${tier}: capped at 300 %`); assert.ok(h.runUntil(() => !u.skill.active, sk.duration + 1)); approx(u.s.interval, 1.6, `T${tier}: interval back`); assert.equal(u.s.taunt, 0, `T${tier}: taunt back`); done(h); } }); test('UNY-X “通往未来的荆棘之路”: damage from an enemy she blocks ×0.85 (stages 1 and 3); UNY-Y “无罪”: ATK / DEF +8 % with no allied operator on the 8 tiles around her (a summon does not count)', () => { for (const f of FORMS_ALL) { const [tier, elite, mod] = f; const others = [{ uid: 2, chessId: 't_mate', row: 12, col: 8 }, { uid: 3, chessId: 'chess_char_3_19_a', row: 12, col: 3 }, { uid: 4, kind: 'token', tokenId: 'token_10028_vigil_wolf', ownerUid: 3, row: 11, col: 6 }]; const { h, u } = field({ tier, elite, mod, skill: 2, others }); const x = elite && mod === UNYX, y = elite && mod === UNYY; if (x) assert.equal(u.def.raw.trait.bb.damage_scale, 0.85, label(f)); if (y) assert.deepEqual(u.def.raw.trait.bb, { atk: 0.08, def: 0.08 }, label(f)); u.findBuff(BARRIER).shield = 0; const blocked = h.spawn('enemy_dummy', { pos: [u.tileR, u.tileC] }); assert.ok(h.runUntil(() => blocked.blockedBy === u, 1)); const free = h.spawn('enemy_dummy', { pos: [12, 9] }); const take = (src) => { u.hp = u.s.maxHp; return h.b.dealDamage(src, u, { amount: 500, type: 'true' }); }; approx(take(blocked), x ? 425 : 500, `${label(f)}: from the blocked enemy`); approx(take(free), 500, `${label(f)}: from another`); // the wolf (a summon) is on the 8 around her: still alone h.step(2); assert.equal(!!u.findBuff('trait:judge:alone'), y, `${label(f)}: alone (a summon next to her)`); if (y) approx(u.s.atk, u.base.atk * 1.08, `${label(f)}: ATK +8 %`); h.b.relocate(h.unit(2), 9, 4); // an operator on her diagonal h.step(2); assert.equal(u.findBuff('trait:judge:alone'), null, `${label(f)}: not alone`); done(h); } });