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.
34 lines
1.9 KiB
JavaScript
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');
|
|
});
|
|
|