Files
ark-stronghold-protocol/test/sim/displace-fx.test.js
T
xcdoge 2669b6ab57 feedback7 R22-RENDER: a pushed / pulled enemy slides to where the sim put it, a dying flyer keeps its lift through the Die clip, a long Die clip plays whole (PR #380 by @xcdoge); golden: unchanged
The sim displaces at once (battle/displacement.js), so the snapshots only show the destination: a ≤ 2.5-tile push was
lerped over one snapshot interval (3 frames, 0.58 tiles in one frame — frame-loop repro over a real battle), a longer
pull snapped. render/app.js now starts UnitView.slideTo as soon as the interval it samples ends on the snapshot that
carries the destination (a `displace` fx still queued up to SnapshotBuffer.nextSnapT); the view holds where it stood
until that snapshot is shown, then eases into the sampled position under a constant deceleration (v0 = 2D/T, a = v0/T,
T = 0.14·√D in 0.12–0.45 s — ours, [ASSUMED]: no source gives the official timing; docs/research/13 has the official
impulse-and-friction classes). A unit that dies on the way finishes the slide and lies where the push put it.
Death: DIE_CLIP_MAX 1.6 → 8 s (data/assets.json: 234 enemy Die clips, the longest 7.97 s, 25 over 1.6 s), and a
flyer holds FLY_HOVER until the fade tail (DIE_FADE_TAIL 0.55 s).

Substantially @xcdoge's code (PR #380: the slide, the death timing, research 13 / 14, most tests), authored as theirs.
Changed on review: the corpse no longer goes back to where it stood (the PR's test asserted that; the sim's body is at
the destination); the PR's pre-pass scanned (renderT − 0.5, renderT] after processEvents had taken those events, so
it never fired and the slide started a frame before landing — it now looks ahead to the next snapshot and holds the
view until then; the slide eases into the live sample instead of the fixed destination (no jump back onto an enemy
that walked on); each step uses its mean velocity (the end-of-step speed ran 9 % short, then jumped); the constants'
comments say the timing is ours; research 14 no longer cites a sweep script and a data guard that were not in the PR.

Golden: unchanged (client only).

Tests: test/render/unitview.test.js 35/35 (slide: deceleration, idempotence, facing, snapshots in flight, friction,
√D, corpse lies where pushed, hold-then-ease without a landing jump, a never-shown slide handed back; the app.js frame
order over a real battle: a 1.7-tile push over ≥ 8 frames with every step < 0.35 tiles, a push-kill's body staying at
the destination; death: a dying flyer's lift, every Die clip within DIE_CLIP_MAX and a 7.97 s clip scheduled whole);
test/sim/displace-fx.test.js 1/1; render/interp 16/16; docs tests pass.
2026-10-08 19:08:27 +08:00

34 lines
1.9 KiB
JavaScript

// The contract between server/sim/battle/displacement.js and render/app.js: a displacement puts a
// ['fx', 'displace', x, y, { id }] tuple into the stream the client renders. The client must read the destination
// from the tuple's own slots — e[4] carries the id only, and reading x/y from it silently produced NaN, which made
// slideTo a no-op (player report: 有停顿但没有中间帧).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
const walker = (o = {}) => enemyRec({ key: 'enemy_walker', hp: 1e6, speed: 1, ...o });
test('a displacement puts a `displace` fx into the event stream the client receives', () => {
const h = makeBattle({
defs: { enemies: { enemy_walker: walker({ speed: 0.01 }) } },
enemies: [{ key: 'enemy_walker', pos: [10, 6] }],
content: 'none', autoFinish: false,
});
h.step();
const e = h.enemy('enemy_walker');
h.b.drainEvents(); // clear the spawn/etc. batch
const moved = h.b.displace(e, { x: 1, y: 0 }, 2, { force: 1 });
assert.ok(moved > 1.8, 'the enemy moved: ' + moved);
const evs = h.b.drainEvents();
const fx = (evs || []).filter((t) => Array.isArray(t) && t[0] === 'fx');
const disp = fx.find((t) => t[1] === 'displace');
assert.ok(disp, 'the stream carries a displace fx: ' + JSON.stringify(fx));
// the fx rounds its coordinates to 2 decimals (events.js fx), the unit keeps full precision
assert.ok(Math.abs(disp[2] - e.x) <= 0.01, 'e[2] is the destination x: ' + disp[2] + ' vs ' + e.x);
assert.ok(Math.abs(disp[3] - e.y) <= 0.01, 'e[3] is the destination y: ' + disp[3] + ' vs ' + e.y);
assert.equal(disp[4] && disp[4].id, e.id, 'e[4].id is the displaced unit');
assert.equal(disp[4].x, undefined, 'e[4] carries no x — reading it gave NaN, so no slide ever ran');
assert.equal(disp[4].y, undefined, 'e[4] carries no y either');
});