feedback7 R22-RENDER: 赤刃明霄陈 S3's sword wave is drawn along its path — the sim sends its position as a chen3Wave fx every 0.12 s, the client draws a 'qi' blade (PR #382 by @Convey123, owner item 12); golden: diy-073 / diy-074 events.fx and fx.chen3Wave only
The 赤霄·天喟 wave is a kit-managed probe (it turns corners and lives as long as the skill), not a snapshot
projectile, and the sim emitted one `dash` at the cast and a `slash` per hit — nothing along the way, so the client
drew no wave. op-chen3.js now emits fx('chen3Wave', { x, y, fromX, fromY, dr, dc, id }) every WAVE_FX_EVERY (0.12 s;
4 ticks), the trail restarting at a corner; render/fx registers chen3Wave → 'qi' (pt: drawn at the event, never on
her) and draws a streak plus a crescent head turned the way it travels. The look is a procedural stand-in, not the
official particle prefab.
Substantially @Convey123's code (PR #382), authored as theirs. Changed on review: the head is projected after
streak() — streak() reuses the effect system's projection scratch, so the PR drew the crescent on the trail's start,
one event behind the wave — and the trail, head and heading share one height; an unused test variable dropped.
Golden: diy-073 (5_diy*=chen3 S3 in two slots) events.fx 508 → 1558, fx.chen3Wave → 1050; diy-074 (6_diy*=chen3 S3)
events.fx 272 → 872, fx.chen3Wave → 600. HP, kills, damage, hooks and snapshot hashes unchanged: battle.fx only
records an event.
Tests: test/content/op_chen3.test.js (+1: the events, their cadence, id, direction, trail start, silence after the
skill) 12/12; test/render/fxchen3wave.test.js (new, 6: the archetype, drawn at the wave not at her, the head on the
wave and turned with it, a sweep joining into one trail, junk input, nothing left after) 6/6; boardart 15/15.
This commit is contained in:
1 parent
ae22335cfe
commit
ffa538c39d
6 files changed
+219
-8
No files matched your search
@@ -15,6 +15,9 @@ export const FX_KINDS = Object.freeze({
|
||||
frostNova: { a: 'blast', c: 0x9fe6ff, smoke: 0x1c2630 }, sunBurst: { a: 'blast', c: 0xffe28a }, meltdown: { a: 'blast', c: 0xff5a2a, r: 1.5, heavy: true },
|
||||
iceSpike: { a: 'blast', c: 0xbfeeff, smoke: 0x1c2630 }, rockfall: { a: 'blast', c: 0xc8a878, smoke: 0x4a3f33 }, rockslide: { a: 'blast', c: 0xc8a878, smoke: 0x4a3f33 },
|
||||
finale: { a: 'blast', c: 0xffd45a, r: 1.5 }, swordStorm: { a: 'blast', c: 0xdfe8ff }, swordRain: { a: 'blast', c: 0xdfe8ff }, liberate: { a: 'blast', c: 0xffffff },
|
||||
// 赤刃明霄陈 S3 赤霄·天喟 的龙剑气: a travelling blade, drawn from the sim's per-interval `chen3Wave` events (its own
|
||||
// position + the previous point + direction); `pt` because it is not at the caster (it roams and passes by her tile)
|
||||
chen3Wave: { a: 'qi', c: 0xdfe8ff, pt: true },
|
||||
quadShot: { a: 'volley', c: 0xfff2d0 }, featherArrow: { a: 'counter', c: 0xfff2d0 },
|
||||
burst: { a: 'element', c: 0xd0a0ff },
|
||||
// areas
|
||||
|
||||
@@ -47,10 +47,11 @@ export class FxSim {
|
||||
/**
|
||||
* b.ev 'fx': every kind the sim / content emits has a visual (FX_KINDS archetypes: blast, shell, zone, telegraph,
|
||||
* heal, sp, shield, shatter, summon, vanish, blink, move, wave, mark, reticle, buff, lift, sleep, crit, dodge,
|
||||
* counter, dp, coin, crate, down, beam, bolt, strike, volley, pillar, lp, chill, element, flame), except kinds whose
|
||||
* counter, dp, coin, crate, down, beam, bolt, strike, volley, pillar, lp, chill, element, flame, qi), except kinds whose
|
||||
* archetype is 'none' (hitCap: a leader hit cancelled by 限伤 draws nothing); unknown kinds get a generic sparkle. `extra` keys used: id (anchor unit — or the shooter of a `pt` kind), r | radius, dur | duration,
|
||||
* t (shell flight, game s), src / from / to / target / targets (unit ids), fx, fy / fromX, fromY / tx, ty (positions),
|
||||
* element, n, scale, kind, tiles, hold ('lock': the reticle waits while the shooter's skill runs).
|
||||
* element, n, scale, kind, tiles, hold ('lock': the reticle waits while the shooter's skill runs), dr / dc (a
|
||||
* direction in tiles: 'qi' points its blade that way).
|
||||
*/
|
||||
simFx(kind, x, y, extra) {
|
||||
const ex = extra && typeof extra === 'object' ? extra : {};
|
||||
@@ -168,6 +169,27 @@ export class FxSim {
|
||||
}
|
||||
break;
|
||||
}
|
||||
// a travelling blade (赤刃明霄陈 S3's 龙剑气): a short trail from where it was to where it is, plus a crescent head
|
||||
// turned the way it moves — the sim sends one of these every 0.12 s, and the trail outlives that gap so the
|
||||
// events join into one sweeping wave. A procedural stand-in (the official effect is a particle prefab this client
|
||||
// does not extract). Trail, head and heading share one height; the head is projected after streak(), which
|
||||
// reuses this._p / this._q (projected before it, the head was drawn at the trail's start, one event behind)
|
||||
case 'qi': {
|
||||
const z = at.z + 0.35;
|
||||
const fx0 = num(ex.fromX, NaN), fy0 = num(ex.fromY, NaN);
|
||||
if (Number.isFinite(fx0) && Number.isFinite(fy0)) this.streak(fx0, fy0, at.x, at.y, z, col, 0.34);
|
||||
const h = cam.project(at.x, at.y, z, this._p), hx = h.x, hy = h.y, hs = h.s;
|
||||
const dr = num(ex.dr, NaN), dc = num(ex.dc, NaN);
|
||||
let rot = 0;
|
||||
if (Number.isFinite(dr) && Number.isFinite(dc) && (dr || dc)) {
|
||||
const q = cam.project(at.x + dc * 0.5, at.y + dr * 0.5, z, this._q);
|
||||
// the `slash` sprite's crescent bulges along its local −y, so this turns that edge onto the travel direction
|
||||
rot = Math.atan2(q.x - hx, -(q.y - hy));
|
||||
}
|
||||
this.particle('slash', hx, hy, { tint: col, life: 0.3, s0: hs / 128 * 1.3, s1: hs / 128 * 2.0, a0: 0.9, a1: 0, rot });
|
||||
this.particle('glow', hx, hy, { tint: col, life: 0.26, s0: hs / 128 * 0.8, s1: hs / 128 * 1.5, a0: 0.55, a1: 0 });
|
||||
break;
|
||||
}
|
||||
case 'wave': {
|
||||
const k = clamp(num(ex.scale, 1), 0.5, 4);
|
||||
const rr = Math.max(0.8, r * (ex.scale ? Math.min(2, k / 2) : 1));
|
||||
|
||||
@@ -46,7 +46,10 @@
|
||||
// field, the stage tiles beyond it are another player's half] —, an 侵入点 or a 保护目标 it turns 90° clockwise (four
|
||||
// turns at most per tick; boxed in on every side it waits) and forgets whom it hit; each enemy it can select within 1.3
|
||||
// (air too) is hit once per straight run for the larger of hp_ratio × its current HP (the unit's own HP, PRTS) and
|
||||
// projectile_min_atk_scale × her ATK at the hit, arts (RES applies — PRTS "非伤害保底或无视法术抗性"; 普通伤害). Her
|
||||
// projectile_min_atk_scale × her ATK at the hit, arts (RES applies — PRTS "非伤害保底或无视法术抗性"; 普通伤害).
|
||||
// The wave is a kit-managed probe, not a snapshot projectile, so it is shown with `fx('chen3Wave')` events every
|
||||
// WAVE_FX_EVERY s (its position, the previous point and its direction — render/fx 'qi': a procedural blade trail with
|
||||
// a crescent head, not the official particle prefab); before that the 龙剑气 had no visual at all (PR #382). Her
|
||||
// begin clips (S1 0.2 s, S3 0.433 s) are not modelled [ASSUMED: the engine starts skills at once].
|
||||
|
||||
import { num, talentBb, traitBb, skillRec, statBuff, toggleBuff, up } from '../shared/tier1.js';
|
||||
@@ -69,6 +72,14 @@ const RETARGET_RADIUS = 1.7;
|
||||
const WAVE_SPEED = 1.5;
|
||||
const WAVE_PROBE = 0.25;
|
||||
const WAVE_HIT_RADIUS = 1.3;
|
||||
/**
|
||||
* How often the wave's position is sent to the client as an `fx` (`chen3Wave`, carrying the previous point and its
|
||||
* direction). The wave is a kit-managed probe (it turns corners and lives as long as the skill), so it is not in the
|
||||
* snapshot's projectile list: without these events the client has nothing to draw and the 龙剑气 is invisible (the cast
|
||||
* emitted one `dash` puff and each hit a `slash` spark). The rate is the renderer's: each event draws a short blade
|
||||
* trail whose life outlasts the gap, so the events join into one continuous wave.
|
||||
*/
|
||||
const WAVE_FX_EVERY = 0.12;
|
||||
/** S1's early end: a silence of hers that ends within this of the skill's planned end ended with it. */
|
||||
const SILENCE_SLACK = 0.1;
|
||||
const AIR = Object.freeze({ canHitFly: true });
|
||||
@@ -215,7 +226,8 @@ export default {
|
||||
attack: { atkScale: num(b3['attack@atk_scale'], 1), hits: 3 }, // `_additionalTimes` 2
|
||||
onStart({ battle, unit, skill }) {
|
||||
const [dr, dc] = unit.fwd;
|
||||
unit.mem.chen3Wave = { x: unit.x, y: unit.y, dr, dc, hit: new Set(), act: skill.activations, seq: unit.deploySeq };
|
||||
unit.mem.chen3Wave = { x: unit.x, y: unit.y, dr, dc, hit: new Set(), act: skill.activations, seq: unit.deploySeq,
|
||||
fx: 0, fxX: unit.x, fxY: unit.y };
|
||||
battle.fx('dash', { x: unit.x, y: unit.y, id: unit.id, skill: 'chen3:wave' });
|
||||
},
|
||||
onEnd({ unit }) { unit.mem.chen3Wave = null; },
|
||||
@@ -285,8 +297,17 @@ export default {
|
||||
w.dr = 0 - w.dc; // (`0 - x`: never a negative zero)
|
||||
w.dc = dr;
|
||||
w.hit.clear();
|
||||
w.fxX = w.x; w.fxY = w.y; // the drawn trail starts again at the corner (no streak across the turn)
|
||||
}
|
||||
if (!waveBlocked(battle, w)) { w.x += w.dc * WAVE_SPEED * dt; w.y += w.dr * WAVE_SPEED * dt; }
|
||||
// the 龙剑气 itself: tell the client where it is now (and where it came from) so it can be drawn — while it
|
||||
// runs, while it is boxed in waiting, and after a turn (the direction comes along for the renderer)
|
||||
w.fx += dt;
|
||||
if (w.fx >= WAVE_FX_EVERY) {
|
||||
w.fx = 0;
|
||||
battle.fx('chen3Wave', { x: w.x, y: w.y, fromX: w.fxX, fromY: w.fxY, dr: w.dr, dc: w.dc, id: unit.id, skill: 'chen3:wave' });
|
||||
w.fxX = w.x; w.fxY = w.y;
|
||||
}
|
||||
for (const e of battle.foesInRadius(w.x, w.y, WAVE_HIT_RADIUS)) {
|
||||
if (w.hit.has(e) || !canTargetEnemy(unit, e, AIR)) continue;
|
||||
w.hit.add(e);
|
||||
|
||||
@@ -319,6 +319,39 @@ test('S3 赤霄·天喟 (MANUAL, data ACTIVE_RANGE on 3-12): range 3-12, each at
|
||||
}
|
||||
});
|
||||
|
||||
// 用户报告: 「自选干员赤刃明霄陈三技能的龙剑气看不到」. 波是 kit 自己管的探针(会拐弯、跟技能同寿),不在快照的 projectile
|
||||
// 列表里,所以客户端只能靠 fx 事件画它——原来只在起手发了一个 `dash`、每次命中发 `slash`,波本身**没有任何事件**。
|
||||
// 现在每 WAVE_FX_EVERY(0.12) 秒发一次 `chen3Wave`:位置 + 上一个点(拖尾)+ 方向(月牙朝向)。
|
||||
test('S3: the 龙剑气 streams its position to the client as `chen3Wave` fx (position, previous point, direction)', () => {
|
||||
const { h, u } = field({ tier: 6, elite: true, skill: 2 });
|
||||
u.skill.gainSp(999);
|
||||
h.spawn('enemy_dummy', { pos: [10, 6] }); // a target in range: the MANUAL skill casts
|
||||
const waveFx = () => h.events.filter((e) => e[0] === 'fx' && e[1] === 'chen3Wave');
|
||||
assert.ok(h.runUntil(() => u.skill.active, 2));
|
||||
h.run(1);
|
||||
const fx = waveFx();
|
||||
assert.ok(fx.length >= 6, `每 0.12 秒一次 ⇒ 1 秒约 8 次(实际 ${fx.length})`);
|
||||
for (const [, , x, y, ex] of fx) {
|
||||
assert.equal(ex.id, u.id, '带着她的 id(可定位到玩家/队伍)');
|
||||
assert.equal(ex.skill, 'chen3:wave');
|
||||
assert.ok(Math.abs(x - u.tileC) <= 12 && Math.abs(y - u.tileR) <= 12, '波在场上');
|
||||
}
|
||||
// 位置随时间前移、方向与朝向一致(row 10 向右),from 是上一个点(拖尾)
|
||||
const [, , x0, , ex0] = fx[0];
|
||||
const last = fx[fx.length - 1];
|
||||
assert.ok(last[2] > x0, `向前推进(${x0.toFixed(2)} → ${last[2].toFixed(2)})`);
|
||||
assert.deepEqual([last[4].dc, last[4].dr], [1, 0], '方向 = 她的朝向(向右)');
|
||||
assert.ok(Math.abs(ex0.fromX - u.tileC) < 0.5 && Math.abs(ex0.fromY - u.tileR) < 0.5, '第一个事件从上场点开始');
|
||||
assert.ok(last[4].fromX < last[2], 'from 是上一个事件的点(拖尾向前)');
|
||||
// 技能结束后不再发
|
||||
h.runUntil(() => !u.skill.active, 25);
|
||||
h.step();
|
||||
const n = waveFx().length;
|
||||
h.run(2);
|
||||
assert.equal(waveFx().length, n, '技能结束后不再有波的 fx');
|
||||
done(h);
|
||||
});
|
||||
|
||||
test('S3: the sword wave leaves her tile forward at 1.5 tiles/s, hits each enemy within 1.3 once per straight run (air too) for max(6 % of its HP, 530 % / 550 % ATK) arts, turns clockwise at the field\'s edge / high ground / gates and forgets whom it hit, and is gone with the skill', () => {
|
||||
for (const [tier, elite] of [[5, false], [6, true]]) {
|
||||
const sk = skillOf(tier, elite, S3);
|
||||
|
||||
@@ -3158,8 +3158,8 @@
|
||||
"enemy_9010_acpupp": [39,6,33,47997,613287]
|
||||
},
|
||||
"hooks": [1,188,6098,435,435,1218,1333,444,85,174,21,21,0,18383,8,44,156,89,1,91,0,0,4],
|
||||
"events": {"atk":476,"deploy":30,"die":85,"dmg":1333,"engage":11,"fx":508,"heal":7,"leak":89,"skill":42,"spawn":170,"status":9},
|
||||
"fx": {"blink":7,"dash":14,"dodge":4,"ember":6,"shield":138,"slash":308,"summon":21,"telegraph":6,"thorns":4},
|
||||
"events": {"atk":476,"deploy":30,"die":85,"dmg":1333,"engage":11,"fx":1558,"heal":7,"leak":89,"skill":42,"spawn":170,"status":9},
|
||||
"fx": {"blink":7,"chen3Wave":1050,"dash":14,"dodge":4,"ember":6,"shield":138,"slash":308,"summon":21,"telegraph":6,"thorns":4},
|
||||
"snaps": "f1faec58 cbf72c28 58e3f561 b8182f46 b1f5b0f5 1de38a22 bc7c2a2b f233c949 01d56108 560ee3fc 8ec7a4b9 b95b08ce 1943beb7 c593b60a e179c3a0 4a2ad117 171d3fbd 1e1c4411 efd84577 32339940 33fdecb2"
|
||||
},
|
||||
"diy-074": {
|
||||
@@ -3197,8 +3197,8 @@
|
||||
],
|
||||
"enemies": {"enemy_1229_darmy":[27,7,20,398,133429],"enemy_1229_darmy_2":[57,14,43,11905,308752],"enemy_1275_dwlock_2":[21,6,15,50953,369503]},
|
||||
"hooks": [1,133,6831,258,258,706,729,74,50,128,11,11,0,8921,19,29,105,78,1,50,0,2,24],
|
||||
"events": {"atk":301,"deploy":28,"die":50,"dmg":729,"engage":12,"fx":272,"heal":1,"leak":78,"skill":22,"spawn":117,"status":9},
|
||||
"fx": {"beam":46,"blink":4,"burst":2,"dash":8,"dodge":24,"shield":74,"slash":114},
|
||||
"events": {"atk":301,"deploy":28,"die":50,"dmg":729,"engage":12,"fx":872,"heal":1,"leak":78,"skill":22,"spawn":117,"status":9},
|
||||
"fx": {"beam":46,"blink":4,"burst":2,"chen3Wave":600,"dash":8,"dodge":24,"shield":74,"slash":114},
|
||||
"snaps": "498914af 0a79facb 2cda1fb6 0a030d4c 9ce66676 179c11c1 62b416c3 fe75747c 619a4d17 62a8fa9d 86e914bd b1b84020 f6408a48 d11bff81 68c04d21 052d976c 5b0d0f6d 7b80bb08 e10c9b3e 0befc79d 41d43704 1e0376b1 8d64e6cf"
|
||||
},
|
||||
"diy-075": {
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// test/render/fxchen3wave.test.js — 赤刃明霄陈 S3 赤霄·天喟 的「龙剑气」(用户报告: 三技能的龙剑气看不到).
|
||||
//
|
||||
// The wave is a kit-managed probe in the sim (server/sim/content/kits/ops/op-chen3.js): it leaves her tile forward at
|
||||
// 1.5 tiles/s, turns 90° at the field's edge / high ground / gates, and lives as long as the skill. It is not in the
|
||||
// snapshot's projectile list, so the client can only draw it from `fx` events — and before this change the sim sent
|
||||
// none for the wave itself (one `dash` puff at the cast, one `slash` spark per enemy hit): the 龙剑气 was invisible.
|
||||
// The sim now emits `fx('chen3Wave', { x, y, fromX, fromY, dr, dc, id, skill })` every 0.12 s — its position, the
|
||||
// previous point (the trail) and its direction (the crescent's heading) — and render/fx.js draws the 'qi' archetype.
|
||||
// Against the headless fake PIXI (test/render/fakepixi.js), like fxflame.test.js.
|
||||
|
||||
import { test, describe, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { installFakePixi, fakeViewCtx } from './fakepixi.js';
|
||||
import { presetCamera } from '../../public/js/render/projection.js';
|
||||
|
||||
let fake, FX;
|
||||
before(async () => {
|
||||
fake = installFakePixi();
|
||||
FX = await import('../../public/js/render/fx.js');
|
||||
});
|
||||
after(() => fake.restore());
|
||||
|
||||
const DT = 1 / 120;
|
||||
const cam = presetCamera('normal', { width: 1600, height: 900 });
|
||||
const unit = (id, x, y, o = {}) => ({ id, x, y, z: 0, hover: 0, _headTiles: 1.2, alive: true, destroyed: false, info: { defId: 'x' }, onHit() {}, ...o });
|
||||
|
||||
function makeFx(views, ts = 1) {
|
||||
const P = fake.P;
|
||||
const ctx = fakeViewCtx(P);
|
||||
const map = new Map(views.map((v) => [v.id, v]));
|
||||
const fx = new FX.FxSystem({
|
||||
P, layers: ctx.layers, cam: () => cam, heightAt: () => 0, settings: { quality: 'high', damageNumbers: true },
|
||||
timeScale: () => ts, loadLevel: () => 0, subProfOf: () => null, view: (id) => map.get(id) || null,
|
||||
screenSize: () => ({ width: 1600, height: 900 }), fieldTop: () => 120,
|
||||
});
|
||||
return { fx, map };
|
||||
}
|
||||
const run = (fx, seconds) => { for (let t = 0; t < seconds - 1e-9; t += DT) fx.update(DT); };
|
||||
/** One sim event of the wave: its position, the previous point and the direction (`dc` cols, `dr` rows). */
|
||||
const wave = (fx, id, x, y, fromX, fromY, dc = 1, dr = 0) => fx.simFx('chen3Wave', x, y, { x, y, fromX, fromY, dr, dc, id, skill: 'chen3:wave' });
|
||||
/** Screen box of the drawn trail/head: min/max of every live particle position. */
|
||||
const drawn = (fx) => {
|
||||
const list = fx.parts.filter((p) => p.sp && p.sp.visible !== false);
|
||||
if (!list.length) return null;
|
||||
const xs = list.map((p) => p.sp.position.x), ys = list.map((p) => p.sp.position.y);
|
||||
return { n: list.length, x0: Math.min(...xs), x1: Math.max(...xs), y0: Math.min(...ys), y1: Math.max(...ys) };
|
||||
};
|
||||
|
||||
describe('赤刃明霄陈 S3 龙剑气: 波自己会被画出来 (issue: 看不到)', () => {
|
||||
test('its own archetype — a travelling blade, not the generic sparkle or a dust puff', () => {
|
||||
assert.equal(FX.FX_KINDS.chen3Wave.a, 'qi');
|
||||
assert.equal(FX.fxSpec('chen3Wave').a, 'qi');
|
||||
assert.equal(FX.fxSpec('chen3Wave').pt, true, 'drawn at the wave (never anchored to the caster)');
|
||||
});
|
||||
|
||||
test('one event draws a trail and a crescent head at the wave, not at her', () => {
|
||||
const her = unit(1, 3, 10), waveAt = { x: 8, y: 10 };
|
||||
const { fx } = makeFx([her]);
|
||||
assert.equal(drawn(fx), null, 'nothing before the event');
|
||||
wave(fx, her.id, waveAt.x, waveAt.y, 7.8, 10);
|
||||
run(fx, 1 / 60);
|
||||
const box = drawn(fx);
|
||||
assert.ok(box && box.n >= 2, `the trail and the head are drawn (${box && box.n} pieces)`);
|
||||
const at = cam.project(waveAt.x, waveAt.y, 0.35, { x: 0, y: 0, s: 0, depth: 0 });
|
||||
const hers = cam.project(her.x, her.y, 0.5, { x: 0, y: 0, s: 0, depth: 0 });
|
||||
const near = (v, t) => Math.abs(v - t) <= 90;
|
||||
assert.ok(near(box.x1, at.x) || near(box.x0, at.x), `drawn around the wave (${Math.round(box.x0)}…${Math.round(box.x1)} vs ${Math.round(at.x)})`);
|
||||
assert.ok(Math.abs(box.x1 - hers.x) > 60, 'not at her tile');
|
||||
});
|
||||
|
||||
// streak() reuses the effect system's projection scratch: a head placed from it sat on the trail's start, one event
|
||||
// (~0.2 tile) behind the wave
|
||||
test('the crescent head sits on the wave itself (not on the trail\'s start) and points the way it travels', () => {
|
||||
const her = unit(1, 3, 10);
|
||||
const { fx } = makeFx([her]);
|
||||
const heads = () => fx.parts.filter((p) => p.sp.visible !== false && p.sp.texture === fx.tex.slash);
|
||||
wave(fx, her.id, 8, 10, 7.6, 10, 1, 0); // moving right (+col)
|
||||
const right = heads();
|
||||
assert.equal(right.length, 1, 'one crescent head');
|
||||
const at = cam.project(8, 10, 0.35, { x: 0, y: 0, s: 0, depth: 0 });
|
||||
assert.ok(Math.abs(right[0].x - at.x) < 0.5 && Math.abs(right[0].y - at.y) < 0.5,
|
||||
`head at the wave (${right[0].x.toFixed(1)}, ${right[0].y.toFixed(1)}) vs (${at.x.toFixed(1)}, ${at.y.toFixed(1)})`);
|
||||
assert.ok(Math.abs(right[0].sp.rotation - Math.PI / 2) < 0.35, `bulge to the right (rot ${right[0].sp.rotation.toFixed(2)})`);
|
||||
run(fx, 1);
|
||||
wave(fx, her.id, 8, 10, 8, 9.6, 0, 1); // moving up the screen (+row: the far side)
|
||||
const up = heads();
|
||||
assert.equal(up.length, 1);
|
||||
assert.ok(Math.abs(up[0].sp.rotation) < 0.35, `bulge up (rot ${up[0].sp.rotation.toFixed(2)})`);
|
||||
});
|
||||
|
||||
test('the events of a sweep join into one trail that goes the way it travels', () => {
|
||||
const her = unit(1, 3, 10);
|
||||
const { fx } = makeFx([her]);
|
||||
let prev = 3;
|
||||
const fronts = [];
|
||||
for (let i = 0; i < 8; i++) { // 0.12 s apart at 1.5 tiles/s ⇒ 0.18 tile a step
|
||||
const x = 3 + 0.18 * (i + 1);
|
||||
wave(fx, her.id, x, 10, prev, 10);
|
||||
prev = x;
|
||||
run(fx, 0.12);
|
||||
fronts.push(drawn(fx).x1);
|
||||
}
|
||||
const box = drawn(fx);
|
||||
assert.ok(box && box.n >= 4, `the recent steps are still alive (${box && box.n} pieces)`);
|
||||
// the drawn front walks forward with the wave (each event is drawn ahead of the last, the older ones still fading)
|
||||
for (let i = 1; i < fronts.length; i++) {
|
||||
assert.ok(fronts[i] > fronts[i - 1] - 1, `step ${i}: the front moved on (${Math.round(fronts[i - 1])} → ${Math.round(fronts[i])})`);
|
||||
}
|
||||
assert.ok(fronts[fronts.length - 1] - fronts[0] > 60, `the sweep crossed the field (${Math.round(fronts[0])} → ${Math.round(fronts[fronts.length - 1])} px)`);
|
||||
});
|
||||
|
||||
test('a wave boxed in (no movement) and a junk extra never throw', () => {
|
||||
const her = unit(1, 3, 10);
|
||||
const { fx } = makeFx([her]);
|
||||
wave(fx, her.id, 8, 10, 8, 10, 0, 0); // standing still: no direction
|
||||
wave(fx, her.id, 8, 10, undefined, undefined);
|
||||
fx.simFx('chen3Wave', 8, 10, { id: her.id }); // bare event
|
||||
fx.simFx('chen3Wave', NaN, NaN, { id: her.id, dr: 1, dc: 0 });
|
||||
run(fx, 0.2);
|
||||
assert.ok(fx.counts.particles > 0, 'still drawn');
|
||||
});
|
||||
|
||||
test('the whole thing is gone once the events stop', () => {
|
||||
const her = unit(1, 3, 10);
|
||||
const { fx } = makeFx([her]);
|
||||
wave(fx, her.id, 8, 10, 7.8, 10);
|
||||
run(fx, 0.05);
|
||||
assert.ok(drawn(fx).n > 0);
|
||||
run(fx, 1); // every trail/head particle expires (life 0.34 s max)
|
||||
assert.equal(drawn(fx), null, 'nothing is left hanging on the field');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user