Test timer
Example apps / timer / v0.2.0
Node modIn the downloadfront permission
The Test timer: a stopwatch whose marks go to the Mod log with the time, used to match log lines to test steps.
// Test timer: a stopwatch for in-game tests. Every Start, Mark and note is written
// to the Mod log (and red4ext/logs/rednode-mods.log) with the clock time, so the
// lines a test produces can be matched to the steps the player took.
import { ui } from 'rednode';
let startedAt = 0; // Date.now() when started; 0 when stopped
let heldMs = 0; // time counted before the last pause
let step = 0;
const elapsed = () => heldMs + (startedAt ? Date.now() - startedAt : 0);
const clock = (ms) => {
const s = Math.floor(ms / 1000);
return `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
};
const time = ui.heading('00:00');
const status = ui.text('Press Start, then Mark after each step.', { tone: 'dim' });
const last = ui.text('', { tone: 'accent' });
// A note goes to the log on Enter, or with the next Mark if Enter was not pressed
// (laptop test, 2026-10-19: notes typed without Enter were lost).
const writeNote = (text) => {
console.log(`Note at ${clock(elapsed())}, during step ${step + 1}: ${text}`);
note.set({ value: '' });
};
const note = ui.input({
placeholder: 'A note for the log (Enter, or the next Mark)',
onSubmit: (text) => {
if (!text.trim()) return;
writeNote(text.trim());
last.set({ text: 'Note written to the log' });
},
});
const start = ui.button('Start', () => {
if (startedAt) {
heldMs = elapsed();
startedAt = 0;
console.log(`Paused at ${clock(heldMs)}`);
} else {
startedAt = Date.now();
console.log(heldMs ? `Resumed at ${clock(heldMs)}` : 'Started');
}
show();
});
function show() {
time.set({ text: clock(elapsed()) });
start.set({ label: startedAt ? 'Pause' : heldMs ? 'Resume' : 'Start' });
status.set({ text: startedAt ? `Running · next is step ${step + 1}` : heldMs ? 'Paused' : 'Press Start, then Mark after each step.' });
}
ui.window({
title: 'Test timer',
anchor: 'left',
x: 40,
y: 0,
width: 320,
height: 290,
children: [
time,
status,
ui.row(
start,
ui.button('Mark', () => {
if (!startedAt) {
startedAt = Date.now() - heldMs;
heldMs = 0;
console.log('Started');
}
const pending = String(note.props.value ?? '').trim();
if (pending) writeNote(pending);
step += 1;
const at = clock(elapsed());
console.log(`Mark: step ${step} done at ${at}`);
last.set({ text: `Step ${step} marked at ${at}${pending ? ', with the note' : ''}` });
show();
}),
ui.button('Reset', () => {
console.log(`Reset at ${clock(elapsed())} after ${step} step(s)`);
startedAt = 0;
heldMs = 0;
step = 0;
last.set({ text: '' });
show();
}, { variant: 'ghost' }),
),
note,
last,
],
});
setInterval(() => {
if (startedAt) time.set({ text: clock(elapsed()) });
}, 1000);
console.log('Test timer ready');
REDnode runs JavaScript. The rednode package also has TypeScript types, so an editor checks these calls; compile TypeScript to .mjs first.
{
"id": "timer",
"name": "Test timer",
"version": "0.2.0",
"author": "Edgemodders",
"description": "A stopwatch for in-game tests. Mark writes the step number and the time to the Mod log and REDnode's log file, so log lines can be matched to what you did. No phone needed. Asks to stay above other packages' windows (the front permission), so a full-screen package cannot cover it.",
"main": "mod.mjs",
"permissions": [
"front"
]
}
The manifest names the package and says how REDnode runs it. Its fields are in Overlay apps.
How it works
Everything a Node mod prints with console.log goes to the Mod log and to red4ext/logs/rednode-mods.log, tagged with the package id.
State lives in plain variables; show() writes it into the nodes. There is no framework to learn: change a node with set() whenever your data changes.
The manifest asks for the front permission, so the timer stays above other packages' windows.
A field keeps what the player typed in props.value, so **Mark** can send a note that was typed without pressing Enter.
What it uses
ui.heading ui.row ui.input onSubmit props.value console.log "front" permission
Try this
- Add a Lap button that logs the time since the last mark.
Use it
Copy the files into red4ext/plugins/REDnode/apps/timer/ in the game folder, or start your own package from them: change id and name in the manifest first. The whole folder is on GitHub.