feedback7 R22-RENDER: an attack seen late plays its whole clip from the wind-up when the rhythm leaves room for it — 重犯's iron ball lifts before the slam (#246 by @TsangAsuna, accepted by the owner); golden: unchanged

SpineActor.attack() starts an attack that was not wound up (no look-ahead: a batch that arrived late) on its strike
frame — right for a quick rhythm, but for a slow clip-per-attack enemy the wind-up was cut out: 重犯 (Attack_grey
2.333 s, strike at 1.0 s, attacks every 3 s) showed the slam without the ball ever rising, and only 1.33 s of the clip.
The owner's decision of 2026-10-07 on #246: re-implement on the current spine.js so that, when the attack interval
leaves room for the whole clip, it plays once completely from the start. A clip-per-attack actor (enemies) with
interval ≥ the clip now plays it from 0 at its own speed for the whole clip, the strike landing a little late; a
quicker rhythm keeps the strike-frame start, and operators' looping attacks are unchanged (the PR's condition had it
the other way round: it changed the looping attacks and left out the clip-per-attack enemies it was written for).

Tests: test/render/feedback2-enemy-attack-clip.test.js (+1: 重犯 plays the whole clip from its wind-up, a quicker rhythm
and an operator start on the strike frame) 5/5; render eyemask, unitview, texas skill clip, skill idle 51/51.
This commit is contained in:
sganggs committed 2026-10-08 19:20:55 +08:00
1 parent 1e78648ad4
commit 6326dd4d0b
2 files changed
+28 -3

No files matched your search

+7 -3
View File
@@ -367,9 +367,13 @@ export class SpineActor {
this.windUntil = null;
this.wound = false;
if (!wasAttacking) {
// not wound up (no look-ahead, e.g. a batch that arrived late): the sim already resolved the hit, so show
// the strike frame now
this._play(clip.loop, !single, { timeScale: ts, start: hit, mix: 0.06 });
// not wound up (no look-ahead, e.g. a batch that arrived late): the sim already resolved the hit, so show the
// strike frame now — unless a clip-per-attack actor's rhythm leaves room for the whole clip (interval ≥ clip):
// then the clip plays complete from its wind-up at its own speed, the strike a little late (重犯's iron ball
// lifts before the slam; #246 by @TsangAsuna, accepted by the owner on 2026-10-07)
const whole = per && this.interval >= loopDur;
if (whole) this.attackUntil = this.clock + loopDur / ts;
this._play(clip.loop, !single, { timeScale: ts, start: whole ? 0 : hit, mix: 0.06 });
} else if (single) {
const e = this.spine.state.tracks[0];
if (e) e.timeScale = ts;
@@ -71,6 +71,27 @@ test('an enemy attacking quicker than its clip plays it faster, one clip per att
assert.equal(a.current, 'Move', 'the rest of the clip at 2×: 0.23 s');
});
// #246 (by @TsangAsuna, accepted by the owner on 2026-10-07): seen late, an attack whose rhythm leaves room for the whole
// clip plays it complete from the wind-up — 重犯's iron ball lifts before the slam instead of the slam appearing alone
test('an attack seen late plays the whole clip from its wind-up when the rhythm leaves room for it (重犯), else from the strike frame', () => {
const LIFBOS = 'enemy_1121_lifbos'; // 重犯: Attack_grey 2.333 s, strike at 1.0 s; attacks every 3 s
const a = actor(LIFBOS, true);
a.attack(3);
assert.equal(a.current, 'Attack_grey');
assert.equal(track(a).trackTime, 0, 'from the wind-up (the ball lifts), not the 1.0 s strike frame');
assert.equal(track(a).timeScale, 1, 'at its own speed');
run(a, 2.25);
assert.equal(a.current, 'Attack_grey', 'the whole 2.333 s clip plays (it used to end 1.333 s after the strike frame)');
run(a, 0.15);
assert.equal(a.current, 'Move_grey', 'then the resting clip');
const quick = actor(LIFBOS, true);
quick.attack(2); // quicker than the clip: no room for the wind-up
assert.ok(Math.abs(track(quick).trackTime - 1) < 1e-9, 'from the strike frame');
const op = actor(LIFBOS, false);
op.attack(3); // an operator's looping attack is unchanged
assert.ok(Math.abs(track(op).trackTime - 1) < 1e-9);
});
test('an operator keeps its attack loop stretched over the attack rhythm (unchanged)', () => {
const a = actor(JSHOOT, false);
a.attack(2.7);