- Stock: fixed trait gifts (缪尔赛思 / 变形者集群 变形同构体, 耶拉 不融冰, 蜜蜡 / 卡涅利安 迅捷作战粮, 诗怀雅 盟约之币) still occupy stock but are granted past the cap; an operator card already shown in the shop or a reward can be taken after the pool runs out and overdraws (GitHub #498, the owner's decision); item buys and item rewards keep the stock check. 洛洛 / 焰尾 sub-pools use the measured weights (PR #497 by @2321Robin; the occupancy idea of PR #496 by @2321Robin re-implemented differently). - Boss pairs are drawn at random once per match and fixed to battle start; the Hidden Core reuses them; a leaver does not split a full pair (the owner's decision). - 阿戈尔 in 联防 no longer re-applies the opening devour HP loss or a false revive; Touch's team medic stays for the match after its holder leaves (owner-approved, official lifetime unverified). - Auto-skill ranges: 司霆惊蛰 S2, 斩业星熊 S3 against air, 予愿安洁莉娜 S3; 自在's post-rebirth invulnerability is also untargetable (#510); Horn / Clementia / catapult / 忍冬 first hit (PR #502 by @Xinglan233); 不融冰 / tear gas trigger on real damage. - Bond layer-up sound in round start / draft / prep / battle (#482): one channel, a new cue replaces the old, no extra cooldown; a prep update that adds layers and activates a bond plays one cue. - Client: multi-swing fix (#506), refresh single-flight (PR #507 by @robll-v1), LP tower keeps the settled value with a −N tick (PR #504 by @robll-v1), export with only 潜能 / 练度 changes (PR #503 by @Xinglan233), spectator rosters list DIY members (PR #505 by @Xinglan233), banned members beside bond tooltips (PR #494 by @YuriRestia), recovery action above the away overlay (PR #511 by @RainoutOvO), the /dev/sim-check.html consistency page (PR #492 by @Cloudnyco), Korean (PR #490 by @D-Roote) and English Victoria wording (PR #499 by @YuriRestia). - golden: 45 of 287 scenarios move against 0.2.4 (roster 17, bonds 3, fields 6, matches 17, standins 2), each attributed in docs/history/0.2.5.md §30.9 / §30.16.
38 lines
2.3 KiB
JavaScript
38 lines
2.3 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
|
|
import * as enemiesMod from '../../server/sim/content/enemies.js';
|
|
import { canTargetEnemy } from '../../server/sim/targeting.js';
|
|
|
|
const grid = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]];
|
|
for (const rule of ['DEFAULT', 'ACTIVE_RANGE', 'SKILL_RANGE', 'SP_FULL']) test(`Xi post-rebirth invincibility prevents attacks; ${rule} retains its own trigger semantics`, () => {
|
|
const h = makeBattle({ content: 'generic', extraContent: [enemiesMod], autoFinish: false, captureNoisy: true,
|
|
defs: { chess: { probe: chessRec({ id: 'probe', profession: 'SNIPER', projectile: 'none',
|
|
stats: { atk: 10, maxHp: 1e7, bat: 1, blockCnt: 0 }, rangeGrid: grid,
|
|
skill: { spCost: 2, initSp: 0, duration: 0, trigger: { rule, grid } } }) } },
|
|
kits: { probe: () => ({ skill: { duration: 0, spCost: 2, initSp: 0, trigger: { rule, grid }, onStart() {} } }) },
|
|
units: [{ chessId: 'probe', row: 10, col: 4 }], enemies: [{ key: 'enemy_1517_xi', pos: [10, 7], mods: { speedMul: 0 } }],
|
|
});
|
|
h.step(); const u = h.unit('probe'), e = h.enemy('enemy_1517_xi');
|
|
h.b.kill(e, null); h.run(4.8);
|
|
let arrival = null;
|
|
h.b.addProjectile({ from: u, target: e, speed: 3, onHit: ({ battle, target }) => {
|
|
arrival = battle.dealDamage(u, target, { type: 'true', amount: 1000 });
|
|
} });
|
|
h.run(0.4);
|
|
assert.equal(e.form, 'form2'); assert.ok(e.s.flags.invulnerable);
|
|
assert.equal(canTargetEnemy(u, e, u.profile), false);
|
|
const attacks = u.stats.attacks, casts = u.skill.activations, hp = e.hp;
|
|
h.run(3);
|
|
assert.equal(u.stats.attacks, attacks);
|
|
if (rule === 'DEFAULT' || rule === 'ACTIVE_RANGE') assert.equal(u.skill.activations, casts);
|
|
else assert.ok(u.skill.activations > casts, 'presence-only and SP-only skills are not forcibly suppressed');
|
|
assert.equal(arrival, 0, 'an already flying projectile still does no damage');
|
|
assert.equal(h.b.dealDamage(u, e, { type: 'arts', amount: 1000 }), 0);
|
|
assert.equal(h.b.dealDamage(u, e, { type: 'element', element: 'neural', amount: 1000 }), 0);
|
|
assert.equal(e.hp, hp);
|
|
h.run(7.2);
|
|
assert.equal(canTargetEnemy(u, e, u.profile), true); assert.ok(u.stats.attacks > attacks);
|
|
checkInvariants(h.b); assert.deepEqual(h.b.errors, []);
|
|
});
|