JNEXT
Skip to content

Recording and replaying a session

The recorder turns a session you play into a script: every key you press and release, every joystick move, and a screen capture each time you ask for one. Replayed — headless, as a test — the script feeds the same input at the same frames and compares the screens. It is how you turn "I played it and it worked" into a regression test without writing one.

Recording

Start a recording in any of three ways:

  • Script ▸ Record Script... in the debugger window, or Record... in the Script tab — you choose the file;
  • --record-script FILE on the command line, in any frontend (the only way in the SDL build, which has no menus): the recording starts at power-on.

Then use the program. Each time the screen shows something worth checking, press Alt+8 (in either window), or Script ▸ Capture Screen, or Capture in the Script tab: the screen is captured at the next frame edge.

Script ▸ Stop Recording — or leaving JNEXT — writes the script.

The Script tab says what is happening: Recording to … (Alt+8 captures)., the input edges and the captures so far.

What is recorded

  • The keyboard: every change of the 40-key matrix, and of the Next's 16 extended keys (ext:…), as press and release.
  • Both joysticks: every change, as joystick 1|2 bits.
  • Each change is written at the frame it landed on, so a replay applies it at the same frame edge the original did: the guest reads exactly the same input in exactly the same frames.
  • The captures: when only the ULA layer is on — no Layer 2, sprites, tilemap or LoRes — the ULA screen memory is saved as name-0001.scr and the script gets a compare_scr. Otherwise the whole display is saved as name-0001.png, and the script gets a screenshot of name-0001-replay.png for you to compare it with.

A cold boot — a hard reset, or loading a program from the File menu — restarts the recording at frame 0, because a replay starts at power-on too. So Record, then File ▸ Load NEX File…, records the program from its load.

What is not recorded

  • the Kempston mouse;
  • media changes: a tape inserted, an SD card swapped;
  • a soft reset from the GUI;
  • what a loaded script changes in the machine.

Keys JNEXT types by itself are recorded like yours: a tape's automatic LOAD "", --delayed-keypress. Replay without the options that typed them, or they are typed twice.

A recording, end to end

Recorded in the Qt GUI with

$ jnext --machine 48k --record-script session.jds

typing P 2 Ctrl+K (Symbol Shift + K, the +) 2 Enter into 48K BASIC, then Alt+8, then leaving JNEXT. The log says:

[debugger] [info] RECORD: recording to session.jds from FRAME 0 [client 2]
[debugger] [info] RECORD: capture 1 at FRAME 342 [client 2]
[debugger] [info] RECORD: wrote session.jds - 12 input edges, 1 captures

and the script is:

# session.jds - generated by jnext (recorder v1). Record again rather than edit.
# jds-recorder: 1
# machine=48k load=none rtc=live sd=cspect-next-1gb-fixed.img (size=1073741824 vol-id=5ae0ffc1 mbr=d903f791adf39a8b lba=63)
# joystick: nr05=0x41
# recorded: FRAME 0..411, 12 input edges, 1 captures
# replay, in the directory holding the captures (they are named relative to it; the
# program's path is as it was loaded):
#   jnext --headless --machine 48k --script session.jds --delayed-automatic-exit-frames 463
on frame 0 once do assert MACHINE == 0 "recorded on MACHINE 0" end
on frame 0 once do assert nextreg[0x05] == 0x41 "joystick mode NR 0x05 = 0x41, as recorded" end
on frame 188 do press "p" end
on frame 197 do release "p" end
on frame 211 do press "2" end
on frame 219 do release "2" end
on frame 233 do press "7,1" end
on frame 240 do press "k" end
on frame 248 do release "k" end
on frame 261 do release "7,1" end
on frame 273 do press "2" end
on frame 281 do release "2" end
on frame 295 do press "enter" end
on frame 304 do release "enter" end
on frame 342 do compare_scr "session-0001.scr" "capture 1 at FRAME 342" end
on frame 413 do exit 0 end

with session-0001.scr beside it. Read it top to bottom:

  • The header says what it was recorded on — the machine, the program loaded (load=none here), the clock, the SD card's identity, the joystick mode — and the command that replays it.
  • Two preconditions: the replay asserts the machine type and the joystick mode it was recorded with, so it cannot pass on the wrong machine.
  • The input, one line per change. "7,1" is SYMBOL SHIFT — a matrix position, since it has no single-key name — held across the K.
  • The capture: a compare_scr, since only the ULA was on.
  • The end: exit 0, two frames after the last recorded frame.

Replay it with the command from the header, in the directory that holds the captures:

$ jnext --headless --machine 48k --script session.jds --delayed-automatic-exit-frames 463
[debugger] [info] SCRIPT loaded session.jds: 16 rules
[debugger] [info] MUTATE key matrix [5,0] pressed at the next frame edge by 2
[debugger] [info] MUTATE key matrix [5,0] released at the next frame edge by 2
…
[debugger] [info] MUTATE key matrix [6,0] released at the next frame edge by 2
[debugger] [info] [jds F:413 C:231469160] SCRIPT EXIT 0
[platform] [info] script requested exit 0

The screen matched, so the run exits 0. Change the program — here, the replay edited to type 3 for the last digit — and it exits 3, naming the capture:

[debugger] [warning] [jds F:342 C:191772736] compare_scr session-0001.scr: first difference at offset 256 (file 08, screen 7E)
[debugger] [warning] [jds F:342 C:191772736] ASSERT FAILED: capture 1 at FRAME 342
[platform] [info] script requested exit 3

Programs that use the other layers

A Next program drawing on Layer 2, sprites or the tilemap is captured as PNG. The replay writes its own PNG at the same frame, and comparing the two is up to you — they are byte-identical when the run is. JNEXT's own suite replays a keyboard test recorded in the GUI that way (test/scripts/dsl/dapr-keyb.jds):

on frame 647 do press "q" end
on frame 658 do screenshot "dapr-keyb-0001-replay.png" end   # compare with dapr-keyb-0001.png (a layer other than the ULA is on, so a PNG)
on frame 674 do release "q" end

In a directory holding dapr-keyb.jds and its five PNGs:

$ jnext --headless --machine next --load SOURCE/test/00regression/nex/test06keyb.nex \
      --script dapr-keyb.jds --delayed-automatic-exit-frames 1008
$ cmp dapr-keyb-0001.png dapr-keyb-0001-replay.png && echo identical
identical

(SOURCE is the source tree.) All five captures, recorded in the Qt GUI, compare identical with the headless replay's.

What "inexact" means

A replay is exact when every change the recorder saw happened between two frames — which is how a running machine takes input. When it cannot promise that, the recorder says so in the script: a # WARNING: comment in the header, and the same text as a log at the first frame, so the replay prints it too. The warnings are:

  • input change recorded while paused mid-frame at FRAME N; replay is not exact here — you changed a key while the debugger held the machine in the middle of a frame. The replay applies it at the frame edge, which is not where the guest saw it;
  • input already held when the recording began (FRAME N); the replay applies it at FRAME N — a key was down when you started recording;
  • input changed before FRAME 0 ran; the replay applies it at the end of FRAME 0;
  • FRAME went back from N to M while recording (a rewind?); the replay does not go back — a rewind during the recording;
  • for captures: a capture asked for at the end was never taken (no frame edge after it), the last PNG capture (…) was still pending when the recording stopped; dropped, and a capture that could not be written.

A warning does not make the replay fail; it tells you why it might.

Record and replay versus RZX

JNEXT also records RZX (--rzx-record). The two answer different questions: an RZX recording stores what every IN instruction returned and replays it by overriding them, inside its own snapshot; a script recording stores the input, and lets the emulator compute the INs again. To test that your program reads its keyboard and joystick right, you want the script. Both can run in the same session.