Offer the easings of an animation key on Lerp and sound FX ramps
game-maker / gate (push) Successful in 3m15s
game-maker / suite (push) Successful in 15m36s

A timed Set sound FX parameters ran only linearly, and a Lerp had four
quadratic eases of its own. An animation key already had the full set.
Now all three offer the same choice: Linear, Step, the ten curves from
Sine to Bounce with In, Out or In-out, and a drawn Curve asset.

- easingParams in src/core/graph/nodes/easing.ts gives a node the
  fields Easing, Direction and Curve from the key's own lists, and
  easingOfParams reads them back as a key interpolation.
- There is no migration. A Lerp saved with the old in, out or inOut
  runs linear until its easing is picked again.
- Set sound FX parameters hands the easing to the player. Linear and
  Step stay ramps of the audio clock, every other easing goes to
  setValueCurveAtTime as 200 samples a second, in semitones and dB.
- Step now reaches its end at t 1. A key never asked for that, it
  reads the next key itself, so animations play as before.
- The curve-in-use count and the warning for a gone curve cover the
  sound node too.

Tests:
- node-catalog: Lerp with cubic in, quad out and in-out, a curve
  without a direction, step, an unknown easing as linear. The sound
  node hands on a curve, step and a gone curve as linear.
- sound-library: an eased change becomes a curve in semitones and
  decibels, a step a jump at the end.
- node-catalog and curve after dropping the old values: 399, green.
- e2e 6, 34, 53 on melektaus-atx: ALL PASSED, 198 checks. In Chrome a
  2 s Quad in from depth 7 to -5 read speed 0.864 early and 0.5 at the
  end, a 1 s Step held 1 and then jumped to 0.5.
- Counter-check: with curves sent as linear ramps, the curve test of
  the sound library fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-05 22:05:00 +02:00
1 parent 501a1524ed
commit e7a360db73
17 files changed
+347 -104

No files matched your search

+15 -7
View File
@@ -1150,8 +1150,9 @@ export async function graph(t) {
await showTab('scene');
// 6z-iii. Lerp (#246): typed into the palette it arrives linear with a 0…1 range, and the ease
// picked in the inspector lands on the node and on its setting row.
// 6z-iii. Lerp (#246): typed into the palette it arrives linear with a 0…1 range, and the easing
// picked in the inspector – the choice an animation key has – lands on the node and on its
// setting rows: a curve brings its direction along.
async function lerpNode() {
await mutate(`
const p = __gm.state.project;
@@ -1177,20 +1178,27 @@ export async function graph(t) {
'typing "lerp" into the palette adds a linear Lerp from 0 to 1',
lerp?.type === 'Lerp' &&
JSON.stringify(inputs) === JSON.stringify(['a 0', 'b 1', 't 0']) &&
JSON.stringify(settings) === JSON.stringify(['Ease: Linear']),
JSON.stringify(settings) === JSON.stringify(['Easing: Linear']),
JSON.stringify({ lerp, inputs, settings }),
);
const easeField = await json(`__gm.inspectorField('Ease')`);
const easeField = await json(`__gm.inspectorField('Easing')`);
if (easeField) {
await click(easeField.x, easeField.y);
await clickText('Cubic');
}
const directionField = await json(`__gm.inspectorField('Direction')`);
if (directionField) {
await click(directionField.x, directionField.y);
await clickText('Out');
}
const picked = lerp ? (await asset('lerpy')).graph.nodes.find((n) => n.id === lerp.id) : null;
const pickedRows = lerp ? await json(`__gm.graph.nodeSettings(${JSON.stringify(lerp.id)})`) : null;
check(
'picking "Out" in the inspector sets the node and its setting row',
picked?.params.ease === 'out' && JSON.stringify(pickedRows) === JSON.stringify(['Ease: Out']),
JSON.stringify({ easeField, params: picked?.params, pickedRows }),
'picking "Cubic" and "Out" in the inspector sets the node and its setting rows',
picked?.params.ease === 'cubic' &&
picked.params.direction === 'out' &&
JSON.stringify(pickedRows) === JSON.stringify(['Easing: Cubic', 'Direction: Out']),
JSON.stringify({ easeField, directionField, params: picked?.params, pickedRows }),
);
await shot('22-lerp');
await mutate(`
+32
View File
@@ -492,6 +492,38 @@ export async function sound(t) {
JSON.stringify({ early, later, afterHalf }),
);
await stop();
// The easings of an animation key: Quad in hands the clock a curve, which is still near the start
// 0.7 s in and only then falls to the end (speed 0.5). Read a little later than that for the
// round trips, it is above 0.8 until 1.1 s; linear is below 0.79 from 0.7 s on.
await mutate(`
const tune = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph.nodes.find((n) => n.id === 'sn-tune');
Object.assign(tune.params, { ease: 'quad', direction: 'in' });`);
await play();
await waitFor(speed, (s) => typeof s === 'number');
await new Promise((resolve) => setTimeout(resolve, 700));
const easedEarly = await speed();
const easedEnd = await waitFor(speed, (s) => typeof s === 'number' && s < 0.52);
check(
'Set sound FX parameters with Quad in ramps slowly first, then on to the end',
easedEarly > 0.8 && easedEarly < 0.995 && easedEnd < 0.52,
JSON.stringify({ easedEarly, easedEnd }),
);
await stop();
// Step holds the pitch for the whole time and then jumps there.
await mutate(`
const tune = __gm.state.project.assets.find((a) => a.id === 'sn-logic').graph.nodes.find((n) => n.id === 'sn-tune');
Object.assign(tune.params, { ease: 'step', time: 1 });`);
await play();
await waitFor(speed, (s) => typeof s === 'number');
await new Promise((resolve) => setTimeout(resolve, 500));
const stepHeld = await speed();
const stepEnd = await waitFor(speed, (s) => typeof s === 'number' && s < 0.9);
check(
'Set sound FX parameters with Step holds the pitch for the time, then jumps to the end',
stepHeld === 1 && Math.abs(stepEnd - 0.5) < 0.001,
JSON.stringify({ stepHeld, stepEnd }),
);
await stop();
await mutate(`
const logic = __gm.state.project.assets.find((a) => a.id === 'sn-logic');
logic.variables = [];
+3 -3
View File
@@ -235,7 +235,7 @@ export async function curves(t) {
);
await shot('53-curve-key');
// 53e. A Lerp picks it too – Ease: Curve, then the curve in its row – and the game logs its value.
// 53e. A Lerp picks it too – Easing: Curve, then the curve in its row – and the game logs its value.
await mutate(`
const p = __gm.state.project;
p.assets.push({ id: 'cv-lerpy', type: 'behavior', name: 'Curve lerp', props: { enabled: true, runInEditor: false },
@@ -270,7 +270,7 @@ export async function curves(t) {
await settle();
return option;
};
await pick('Ease', 'Curve');
await pick('Easing', 'Curve');
const unpicked = await json("__gm.graph.nodeSettings('cv-lerp')");
const name = await json(`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(curveId)}).name`);
const picked = await pick('Curve', `${name} (curve)`);
@@ -279,7 +279,7 @@ export async function curves(t) {
);
const settings = await json("__gm.graph.nodeSettings('cv-lerp')");
check(
'Ease: Curve brings the Curve row onto the node, and picking the curve there sets it',
'Easing: Curve brings the Curve row onto the node, and picking the curve there sets it',
params?.ease === 'curve' &&
params.curve === curveId &&
Boolean(picked) &&