feedback5 WM: merge feedback4 (896dd5d, the coming 0.1.4) — PRs #150 #66 #146 #74 #72 #103 #149 #109 #160 ported into the split layout
Ported by hand: - #109 kit hunks → kits/ops (the tier files stay re-export shims): 宴 chess_char_1_18-utage.js, 野鬃 chess_char_1_19-wildmn.js, 砾 chess_char_2_12-gravel.js, 斯卡蒂 chess_char_3_05-skadi.js, 伊内丝 chess_char_4_04-ines.js, 缄默德克萨斯 chess_char_4_16-texas2.js, 耀骑士临光 chess_char_6_17-nearl2.js. The +/- lines are identical to #109's tier1/2/3/4/6 diffs. - #109 generic.js against W5D's own-duration passives (一击即退): the deployment window is computed where #109 computes it, with W5D's case included. Both forms run #109's duration lifecycle (activateOnDeploy, spType 'none', the skill's own mods), and W5D's 冲锋号令 DP start effect is kept. - #103 room.js through i18n phase 1: t('{name}邀请你加入卫戍协议:盟约【{difficulty}】') with the translated difficulty name (the Chinese text is the PR's), plus its English line in public/i18n/en.json. - data/assets.json: feedback5's manifest plus #72's audio.bgm.combatAlts (after boss, the plan order). files +3 and bytes +5 037 595 as measured on feedback4; the hash is recomputed with the generator's contentHash. No other data/ file changed. - docs: DESIGN §5.6 (#109's timed deployment paragraph before feedback5's trigger rules; "Permanent passive skills are always on"), SIM §7.4 (both generic passive cases). - Auto-merged and checked: #149 bond-bar fold (still in screens/game.js), #160 skills.js / spine.js, #109 skills.js / snapshot.js, #66 addon/battle.js (restTile / isDown live in sim/battle/tiles.js), #150 units.js, #72 audio.js / plan.mjs, #74 plan.mjs / docs. Where the PRs met feedback5 work: - 突袭 (W5L, GitHub #49): raidPoll counts a skill that needs no SP while it is on. That is a passive, as before, or a #109 deploy-timed skill while its window runs. Once the window has ended only the idle trigger is left. Without this change 缄默德克萨斯 / 宴 lost #49. research 02 §3.18 says so. - Tests changed to #109's representation: bonds_addon #49 test (kind 'duration', activateOnDeploy) plus a new test (window over → idle trigger only), and standin.test.js's 预备干员 PASSIVE branch (the skill's mods instead of the generic:passive buff). Golden regenerated on the merged code. Attribution: each PR's gameplay change was reverted alone in a scratch copy, and all three together. With all three reverted the digests equal feedback5's exactly. Every moved scenario was replayed and fields the unit that explains it. - #160 only (0.5 s skill events of deploy passives; events.skill only): roster-001/006/025/049, bond-arcaneShip-low, bond-emptyShip-high, bond-investShip-high, bond-kjeragShip-low, bond-siracusaShip-low/high, bond-victoriaShip-low, bond-yanShip-high, boss-boss_2/4/6-solo, hidden-boss_8/10-solo, standin-03/05/08/10, diy-005/006 (普罗旺斯 S1, 琳琅诗怀雅 S1/S2, 星熊 S2, 凯瑟琳 S1, Misery S3 as stand-in / 自选 prototype). - #109 only (deploy-timed skill state and lifecycle; 突袭 技能就绪 limited to the window; generic 一击即退): roster-003/004/005/021/023/024/039/041/042/046/047, bond-egirShip-high, bond-indomShip-high, bond-kazimierzShip-low/high, hidden-boss_8-pair, standin-01, coop4-FUNNY-6, coop2-NORMAL-8-serverrun. - #66 only (灵巧's aura stays only for a killed member, from its body tile): roster-028, coop2-NORMAL-3, coop3-ABYSS-5, coop4-ABYSS-7, solo-HARD-9-boosted. - #109 + #160: roster-002, roster-029, bond-investShip-low, bond-raidShip-low/high, bond-suntShip-low, bond-visiShip-high. - #66 + #160: roster-008, roster-027. - #66 + #109: roster-022, roster-040, bond-indomShip-low, boss-boss_2/4/6-pair, hidden-boss_10-pair, standin-06, coop2-HARD-4, coop2-NORMAL-14-standins. - all three: roster-010, roster-012, roster-031. 69 scenarios. Every other digest is unchanged.
This commit is contained in:
commit
a0a7660287
55 files changed
+2140
-736
No files matched your search
@@ -0,0 +1,121 @@
|
||||
// test/render/feedback4-texas-skill-clip.test.js — player report (after 0.1.3): 德克萨斯 S2「剑雨」释放时不播放技能动画.
|
||||
//
|
||||
// Her Front model's skill roles are { begin: null, loop: 'Skill', end: null } — the 2.17 s 剑雨 clip — and both her
|
||||
// skills are instant (server/sim/skills.js fires 'skill' 1 and 0 inside one tick and holds only the anim code SKILL for
|
||||
// 0.5 s, snapshot.js animOf). spine.js setSkill(true) had a branch for a Begin clip and otherwise just re-played the
|
||||
// base clip, so the skill clip never played: the actor showed its idle through the whole cast. The same clip shape
|
||||
// (no Begin, no own Idle) carries 58 instant skills across the manifest.
|
||||
//
|
||||
// Real manifest entries, headless fake PIXI (test/render/fakepixi.js), like feedback3-skill-idle.test.js.
|
||||
|
||||
import { test, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { installFakePixi } from './fakepixi.js';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const assets = JSON.parse(readFileSync(path.join(ROOT, 'data/assets.json'), 'utf8'));
|
||||
|
||||
let fake, SpineActor;
|
||||
before(async () => {
|
||||
fake = installFakePixi();
|
||||
({ SpineActor } = await import('../../public/js/render/spine.js'));
|
||||
});
|
||||
after(() => fake.restore());
|
||||
|
||||
/** An actor of a real operator's Front model with its equipped skill, recording every clip set / queued. */
|
||||
function actor(charId, skillIndex) {
|
||||
const entry = assets.chars[charId].spine.front;
|
||||
const a = new SpineActor({ animations: Object.keys(entry.animations).map((name) => ({ name })) }, entry);
|
||||
a.setSkillIndex(skillIndex);
|
||||
a.log = [];
|
||||
const st = a.spine.state;
|
||||
const set = st.setAnimation, add = st.addAnimation;
|
||||
st.setAnimation = (i, name, loop) => { a.log.push(['set', name, !!loop]); return set(i, name, loop); };
|
||||
st.addAnimation = (i, name, loop) => { a.log.push(['queue', name, !!loop]); return add(i, name, loop); };
|
||||
return a;
|
||||
}
|
||||
const run = (a, s, dt = 1 / 60) => { for (let t = 0; t < s - 1e-9; t += dt) a.update(dt); };
|
||||
const names = (a) => a.log.map(([, n]) => n);
|
||||
|
||||
test('德克萨斯 S2 剑雨: an instant skill with no Begin clip plays its Skill clip and keeps it for the clip length', () => {
|
||||
const a = actor('char_102_texas', 1);
|
||||
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.end, a.roles.skill.idle], [null, 'Skill', null, null], 'the manifest roles');
|
||||
assert.equal(a.dur('Skill'), 2.167, 'the 剑雨 clip');
|
||||
a.setSkill(true);
|
||||
assert.deepEqual(a.log, [['set', 'Skill', false]], 'the skill clip itself, played once');
|
||||
a.setSkill(false); // the sim fires 'skill' 1 and 0 in the same tick: the flag is already off
|
||||
run(a, 1);
|
||||
assert.equal(a.mode, 'skillBegin');
|
||||
assert.equal(a.current, 'Skill', 'still on screen halfway through the cast');
|
||||
run(a, 1.3);
|
||||
assert.equal(a.mode, 'base');
|
||||
assert.equal(a.current, 'Idle', 'back to the idle once the clip has run');
|
||||
assert.deepEqual(names(a), ['Skill', 'Idle'], 'no end clip, no base flash before it');
|
||||
});
|
||||
|
||||
test('the other instant skills with the same clip shape play their clip too (char_140_whitew S1 日晷, char_206_gnosis S2 零度爆发, 德克萨斯 S1)', () => {
|
||||
for (const [charId, idx, clip] of [['char_140_whitew', 0, 'Skill'], ['char_206_gnosis', 1, 'Skill_2'], ['char_102_texas', 0, 'Skill']]) {
|
||||
const a = actor(charId, idx);
|
||||
assert.equal(a.roles.skill.begin, null, `${charId} S${idx + 1}: no Begin clip`);
|
||||
assert.equal(a.roles.skill.idle, null, `${charId} S${idx + 1}: no own Idle`);
|
||||
a.setSkill(true);
|
||||
assert.equal(a.current, clip, `${charId} S${idx + 1} plays ${clip}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('a skill that stays on (银灰 S3 真银斩: same clip shape, 1.33 s clip) plays it once and then rests — no freeze on its last frame', () => {
|
||||
const a = actor('char_172_svrash', 2);
|
||||
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.idle], [null, 'Skill', null]);
|
||||
a.setSkill(true);
|
||||
assert.equal(a.current, 'Skill');
|
||||
run(a, 2);
|
||||
assert.equal(a.mode, 'base', 'the one-shot window is over');
|
||||
assert.equal(a.current, 'Idle', 'rests on the idle while the skill is still running');
|
||||
a.log.length = 0;
|
||||
a.attack(1.2);
|
||||
assert.equal(a.current, 'Skill', 'attacks still use the skill loop, as before');
|
||||
a.setSkill(false);
|
||||
assert.equal(a.current, 'Idle', 'no End clip: straight back to the idle');
|
||||
});
|
||||
|
||||
test('unchanged: a Begin clip still runs into its queued next clip, a skill with its own Idle still idles, and End still closes the skill', () => {
|
||||
const b = actor('char_4207_branch', 1); // 折桠 S2: Skill_2_Begin → Skill_2_Idle (community report #23)
|
||||
b.setSkill(true);
|
||||
assert.deepEqual(b.log, [['set', 'Skill_2_Begin', false], ['queue', 'Skill_2_Idle', true]]);
|
||||
const n = actor('char_1014_nearl2', 2); // 耀骑士临光 S3: no Begin, own Idle — plays that idle at once
|
||||
n.setSkill(true);
|
||||
assert.deepEqual(n.log, [['set', 'Skill_3_Idle', true]]);
|
||||
const u = actor('char_337_utage', 1); // 宴: Begin → loop, unchanged
|
||||
u.setSkill(true);
|
||||
assert.deepEqual(u.log, [['set', 'Skill_Start', false], ['queue', 'Skill_Loop', true]]);
|
||||
});
|
||||
|
||||
test('the deploy-time passives the sim now opens a window for play their clip (琳琅诗怀雅 S1/S2: kind passive, no duration)', () => {
|
||||
// server/sim/skills.js `_startPassive` fires ['skill', id, 1] at the deployment and 0 after SKILL_ANIM_WINDOW (0.5 s),
|
||||
// which is shorter than her 1 s clips: the actor holds the clip to its own end and then idles.
|
||||
for (const [idx, clip] of [[0, 'Skill_1'], [1, 'Skill_2']]) {
|
||||
const a = actor('char_1033_swire2', idx);
|
||||
assert.deepEqual([a.roles.skill.begin, a.roles.skill.loop, a.roles.skill.idle], [null, clip, null]);
|
||||
assert.equal(a.dur(clip), 1);
|
||||
a.setSkill(true);
|
||||
assert.equal(a.current, clip, `S${idx + 1} plays ${clip} at the deployment`);
|
||||
run(a, 0.5);
|
||||
a.setSkill(false); // the sim closes the window at 0.5 s …
|
||||
run(a, 0.4);
|
||||
assert.equal(a.current, clip, '… the clip is still running (it is 1 s long)');
|
||||
run(a, 0.2);
|
||||
assert.equal(a.mode, 'base');
|
||||
assert.equal(a.current, 'Idle', 'and then back to the idle');
|
||||
}
|
||||
});
|
||||
|
||||
test('a skill clip that IS the normal attack clip is not played as a skill animation (no attack twitch at the deployment)', () => {
|
||||
const a = actor('char_1028_texas2', 0); // 缄默德克萨斯 S1: anims.skill.loop === anims.attack.loop ('Attack')
|
||||
assert.equal(a.roles.skill.loop, a.roles.attack.loop);
|
||||
a.setSkill(true);
|
||||
assert.deepEqual(a.log, [['set', 'Idle', true]], 'falls through to the base clip, as before');
|
||||
assert.equal(a.current, 'Idle');
|
||||
});
|
||||
Reference in new issue
Block a user