Files
ark-stronghold-protocol/test/content/feedback5-protect.test.js
T
sganggs cd180bc752 feedback5 WE1: 庇护 is one effect per unit — 宴, 余, 缪尔赛思, 赫拉格, 左乐 and 赫德雷 join the shared key (follow-up 18)
gamedata_const ba.protect / PRTS 术语 庇护 "受到的物理和法术伤害降低相应比例(同名效果取最高)": the strongest source holds; the
自选 kits already used battle.applyStrongest('protect'), these six multiplied private cuts with it.

- kits/shared/tier1.js: PROTECT, protectMods, PROTECT_TICK_HOLD and holdProtect (applyStrongest on the shared key).
- 宴 / 赫拉格 / 左乐 (below their HP threshold): refreshed every tick and at each hit on them; 余 (blocking) and 缪尔赛思's
  melee copies at their old cadence; 赫德雷's 庇护 joins the key, his CRU-X 物理伤害 + becomes its own instance. 左乐 keeps the
  PRTS S2 ordering (his own 庇护 newer than his barrier cuts behind it); 行险 drops his own held 庇护 for its 0.1 s
  re-judgement [ASSUMED: another source's returns with its next refresh]. 遥's bubbles stay separate (the client's
  damage_resistance[bonus] multiplies with the common damage_resistance[inf]).

Golden: no scenario moved — no unit of the corpus holds two 庇护 at once, and one source alone cuts exactly as before.

Tests: test/content/feedback5-protect.test.js (new, 2); kits_t6 (余), kits_alt_t6 (缪尔赛思), op_hodrer updated to the
shared key; kits_t1t2, op_helage, op_zuole pass. Docs: SIM.md §3, kits/README.md checklist 9.
2026-10-06 15:43:24 +08:00

82 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// test/content/feedback5-protect.test.js — 庇护 is one effect per unit whoever grants it (gamedata_const ba.protect / PRTS 术语
// 庇护 "受到的物理和法术伤害降低相应比例(同名效果取最高)"): the older kits — 宴, 余, 缪尔赛思 (chess), 赫拉格, 左乐, 赫德雷 (自选) —
// hold it through the shared key the other 自选 kits use (kits/shared/tier1.js PROTECT / holdProtect → battle.applyStrongest),
// so the strongest value applies instead of a product of every source. 遥's bubbles keep their own key (the client's
// damage_resistance[bonus], which multiplies with the common damage_resistance[inf]).
// Run: node --test test/content/feedback5-protect.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { PROTECT, holdProtect } from '../../server/sim/content/kits/shared/tier1.js';
import { getDefaultSource } from '../../server/sim/simdata.js';
const ds = getDefaultSource();
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 done = (h) => { checkInvariants(h.b); assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); };
/** 宴 (elite: 庇护 below hp_ratio) at 20 % HP with nothing to hit her. */
function utage() {
const h = makeBattle({ units: [{ chessId: 'chess_char_1_18_b', row: 9, col: 5 }], autoFinish: false, timeLimit: 60, seed: 3 });
h.step();
const u = h.unit('chess_char_1_18_b');
u.hp = u.s.maxHp * 0.2;
h.step();
return { h, u, dr: ds.rawChess('chess_char_1_18_b').talents.filter((t) => t.index !== -1)[0].bb.damage_resistance };
}
/** Physical damage she takes from a 1000-point hit (her DEF taken off). */
const physTaken = (h, u) => { const hp = u.hp; h.b.dealDamage(null, u, { amount: 1000, type: 'phys', canDodge: false }); const d = hp - u.hp; u.hp = hp; return d; };
test('宴 below her threshold holds the shared 庇护; a stronger one from another source replaces it (no product), a weaker one waits under it', () => {
const { h, u, dr } = utage();
assert.ok(dr > 0 && dr < 0.5);
const b = u.findBuff(PROTECT);
assert.ok(b && b.source === u && b.data.value === dr, 'her own 庇护, on the shared key');
const base = 1000 - u.s.def;
approx(physTaken(h, u), base * (1 - dr), 'hers alone');
// a stronger 庇护 from another source: the highest counts
holdProtect(h.b, u, 0.5, 10, { id: 'other' });
h.step();
approx(physTaken(h, u), base * (1 - 0.5), 'the stronger one only — not ×(1 − dr) as well');
assert.equal(u.buffs.filter((x) => x.key === PROTECT).length, 1, 'one 庇护 effect');
done(h);
// a weaker one: hers holds
const w = utage();
holdProtect(w.h.b, w.u, 0.1, 10, { id: 'other' });
w.h.step();
approx(physTaken(w.h, w.u), (1000 - w.u.s.def) * (1 - w.dr), 'hers — the weaker 庇护 waits');
// back above the threshold her own lapses within a tick; the weaker one resumes for its remaining time
w.u.hp = w.u.s.maxHp;
w.h.step(3);
const after = w.u.findBuff(PROTECT);
assert.ok(after && after.data.value === 0.1, 'the weaker 庇护 resumed');
done(w.h);
});
test('余 (blocking), 缪尔赛思\'s melee copies, 赫拉格 / 左乐 (SBL-X, below 50 %) and 赫德雷 (余火之氅) all hold 庇护 on the shared key', () => {
const src = (f) => readFileSync(new URL(`../../server/sim/content/kits/ops/${f}`, import.meta.url), 'utf8');
for (const f of ['chess_char_1_18-utage.js', 'chess_char_6_03-yu.js', 'chess_char_6_11-mlyss.js', 'op-helage.js', 'op-zuole.js', 'op-hodrer.js']) {
assert.match(src(f), /holdProtect\(/, `${f} uses the shared 庇护`);
assert.doesNotMatch(src(f), /physTakenMul: 1 - dr|dmg\.mul \*= 1 - (dr|protectCut|num\(t\.damage_resistance\))/, `${f}: no private 庇护 multiplier left`);
}
// 遥's bubble stays its own effect (damage_resistance[bonus])
assert.doesNotMatch(src('op-haruka.js'), /holdProtect\(/);
// 赫拉格 (SBL-X, tier 5) at 40 % HP: his 庇护 on the shared key
const h = makeBattle({
defs: { enemies: { e: enemyRec({ key: 'e', hp: 1e9, speed: 0 }) } },
units: [{ diy: { slot: 5, charId: 'char_188_helage', skillIndex: 0, uniEquipId: 'uniequip_002_helage' }, elite: true, row: 10, col: 5 }],
autoFinish: false, timeLimit: 30, seed: 3,
});
h.step();
const u = h.b.allyUnits.find((x) => x.def?.charId === 'char_188_helage');
u.hp = u.s.maxHp * 0.4;
h.step();
const b = u.findBuff(PROTECT);
assert.ok(b && b.source === u && b.data.value === 0.25, 'his SBL-X 庇护');
u.hp = u.s.maxHp;
h.step(3);
assert.equal(u.findBuff(PROTECT), null, 'gone above 50 %');
done(h);
});