Let On key down repeat while the key stays held
A held key fired once and then nothing, so sliding a Tetris piece sideways or walking a menu meant building a timer next to every key node. Two settings on the node do it now: Repeat after and Repeat interval, both in milliseconds so the usual values stay whole numbers (300, 50) instead of 0.3 and 0.05. A delay of 0 keeps the node exactly as it was, an interval of 0 means every frame. The browser's own auto-repeat is deliberately not used - Input.press has always dropped it - because its delay and rate are an operating-system setting of the player's, not a game's. The repeat is built from the measured hold time instead, per node, so two nodes on one key keep their own clocks. The rule itself is a pure function in keyRepeat.ts and is checked without a world. Both rows stay off the node body while the delay is 0: two lines of "0" on every key node in every graph would be noise, and the screenshot said so. NodeDef gains hiddenParams for that; the inspector still shows them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4b0919a224
commit
3ab8d88ad6
13 files changed
+552
-3
No files matched your search
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* The repeat clock behind `On key down`, checked without a world: the cases that are awkward to
|
||||
* stage in one (a key already held when tracking starts, a hostile interval) live here.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { nodeDef } from '../../src/core/graph';
|
||||
import { MIN_REPEAT_INTERVAL, stepKeyRepeat } from '../../src/core/graph/keyRepeat';
|
||||
import type { PropertyBag } from '../../src/core/model/Properties';
|
||||
|
||||
const held = { pressed: false, down: true, dt: 0.1, after: 0.2, interval: 0.2 };
|
||||
|
||||
describe('key repeat', () => {
|
||||
it('starts the clock on the press, whatever the frame was worth', () => {
|
||||
const step = stepKeyRepeat(null, { ...held, pressed: true, dt: 5 });
|
||||
expect(step.fires).toBe(1);
|
||||
expect(step.state).toEqual({ held: 0, repeats: 0 });
|
||||
});
|
||||
|
||||
it('forgets the clock when the key comes up', () => {
|
||||
expect(stepKeyRepeat({ held: 9, repeats: 3 }, { ...held, down: false })).toEqual({
|
||||
state: null,
|
||||
fires: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('stays quiet for a key that was already held when it started watching', () => {
|
||||
// No state means the press was never seen – repeating without a first fire would be a lie.
|
||||
expect(stepKeyRepeat(null, held)).toEqual({ state: null, fires: 0 });
|
||||
});
|
||||
|
||||
it('never repeats while the delay is zero, however long the key is held', () => {
|
||||
let state = { held: 0, repeats: 0 };
|
||||
for (let n = 0; n < 100; n++) {
|
||||
const step = stepKeyRepeat(state, { ...held, after: 0, dt: 1 });
|
||||
expect(step.fires).toBe(0);
|
||||
state = step.state!;
|
||||
}
|
||||
expect(state.held).toBe(100);
|
||||
});
|
||||
|
||||
it('treats a negative delay as no delay at all', () => {
|
||||
expect(stepKeyRepeat({ held: 5, repeats: 0 }, { ...held, after: -1 }).fires).toBe(0);
|
||||
});
|
||||
|
||||
it('holds an absurdly small interval to the floor instead of freezing the frame', () => {
|
||||
// Without the floor a 1 ns interval over a 1 s frame would ask for a billion fires.
|
||||
const step = stepKeyRepeat({ held: 0.2, repeats: 0 }, { ...held, dt: 1, interval: 1e-9 });
|
||||
expect(step.fires).toBeLessThanOrEqual(1 / MIN_REPEAT_INTERVAL + 1);
|
||||
expect(step.fires).toBeGreaterThan(1 / MIN_REPEAT_INTERVAL - 2);
|
||||
});
|
||||
|
||||
it('counts every due repeat when one frame swallows several', () => {
|
||||
const step = stepKeyRepeat({ held: 0, repeats: 0 }, { ...held, dt: 1, after: 0.5, interval: 0.5 });
|
||||
expect(step).toEqual({ state: { held: 1, repeats: 2 }, fires: 2 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('the repeat rows on the node body', () => {
|
||||
const hidden = (params: PropertyBag) => nodeDef('OnKeyDown')?.hiddenParams?.(params);
|
||||
|
||||
it('leaves both off while the repeat is switched off', () => {
|
||||
expect(hidden({ key: 'a' })).toEqual(['repeatAfter', 'repeatInterval']);
|
||||
expect(hidden({ key: 'a', repeatAfter: 0, repeatInterval: 50 })).toEqual([
|
||||
'repeatAfter',
|
||||
'repeatInterval',
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows both once a delay is set, an interval of zero included', () => {
|
||||
// Zero is a setting here ("every frame"), not an absence – hiding that row would lie.
|
||||
expect(hidden({ key: 'a', repeatAfter: 300, repeatInterval: 0 })).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user