Give the machine a frame to wait for

V3. The screen interrupts at each frame on hardware vector 0x30, and WAIT finally has
something worth sleeping on.

THERE WAS NO CLOCK. Every program that wanted to happen at a certain speed counted
instructions and hoped, which is why Snake's pause silently halved the day a cycle stopped
being an instruction and became a memory access - the program was right and the thing it was
counting changed underneath it. A screen finishing sixty times a second is a real beat, and
it is counted in the MACHINE'S cycles rather than the host's, so the same program sees the
same number of frames in the same number of cycles however fast anything really ran. That is
what makes a frame something a test can count and a recorded result can hold.

Status bit 0 goes up when a frame has gone by and reading the status port puts it down, so a
program with no handler can watch for it instead. Control bit 0 asks to be interrupted, and
is OFF when the machine starts: an interrupt with nothing installed to catch it is a fault,
so a screen that began interrupting the moment it was switched on would take down every
program written before frames existed.

More than one frame can pass between two looks, and the flag and the line are each one
thing, so several still mean one of each. A missed frame is missed.

Programs/Examples/frames.asm prints a dot a frame for a second: 1,000,324 cycles, and 996,460
of them spent asleep. That split is the thing worth seeing - a program that polled instead
would print the same sixty dots, take the same second, and spend every cycle of it on the
bus. Its header explains why waiting is not spinning and why a machine with a beat can stop
guessing at one.

Six checks in Tests/video.sh, and two of them are about the clock rather than the output,
because the output cannot tell the difference. That the machine slept through nearly all of
ten frames, and that polling three frames actually took three frames - a status flag that
stayed up once set would print exactly the same character and look perfectly correct.

Breaking the frame interrupt on purpose left a machine asleep for ever and hung the whole
suite, which is a worse way to be told than a failing check. Tests/video.sh bounds its runs
at ten seconds now, the way Tests/run.sh always has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-29 10:24:09 -04:00
co-authored by Claude Opus 5
parent 3da020898c
commit 1174bd9af5
8 changed files with 357 additions and 7 deletions
+28 -1
View File
@@ -550,11 +550,12 @@ The high nibble is reserved and should be left at zero, so that a meaning can be
| Port | Register |
| --- | --- |
| 0x30 | Status. Reserved for the frame interrupt, and reads zero until there is one. |
| 0x30 | Status. Bit 0 a frame has gone by, bit 1 the screen is set to interrupt. |
| 0x31 | Mode. |
| 0x32 | Columns, read only. |
| 0x33 | Rows, read only. |
| 0x34 | Scroll. |
| 0x35 | Control. Bit 0 asks to be interrupted at each frame. |
| Mode | Screen | Cells |
| --- | --- | --- |
@@ -565,6 +566,32 @@ Both are 8 by 8 cells over the same engine, and the pixel count costs a program
How big the screen is, is asked for rather than assumed. A program written once can find out what it is running on.
### The Frame:
A screen finishes drawing sixty times a second and then has a moment before it starts again. That moment is the one safe time to change what it is drawing - and it is also **the only regular beat this machine has.** There is no clock here. Every program that wanted to happen at a certain speed has until now counted instructions and hoped, which is why Snake's pause quietly halved the day a cycle stopped being an instruction and became a memory access.
Sixty a second, counted in the machine's own cycles rather than the host's. So a program sees the same number of frames in the same number of cycles however fast anything really ran, which is what makes a frame something a test can count and a recorded result can contain.
**Status bit 0 goes up when a frame has gone by, and reading the status port puts it down.** Looking is what answers it: a frame that has been noticed is not still waiting to be noticed, and a program polling in a loop would otherwise see the first frame for ever.
**Control bit 0 asks to be interrupted instead**, on hardware vector 0x30, which is the screen's base port. It is **off when the machine starts**, and that is not caution for its own sake: an interrupt with nothing installed to catch it is a fault, so a screen that began interrupting the moment it was switched on would take down every program written before frames existed. Asking to stop takes down any request already standing, for the same reason the console's interrupt bit does.
More than one frame can go by between two looks - the machine runs in batches, and a slow host covers several at once. The flag and the line are each one thing, so several frames still mean one of each. **A missed frame is missed**, which is what missing one means.
This is what `WAIT` was built for. A program does its work, waits, and is woken:
```
INIA 0x01
OUTA 0x35 ; Interrupt me at each frame
SIF
loop:
; ... draw ...
WAIT ; Nothing to do until the screen says so
BRI loop
```
A machine doing that is asleep between frames rather than spinning, and the difference is visible: the cycles it spent are counted as idle rather than as bus, so a program that waited properly and one that polled in a loop can be told apart even though they print the same thing and take the same time.
### Scrolling:
**The map is a ring, and the Scroll register says which of its 128 rows is drawn at the top.** Screen row *r* shows map row *scroll + r*, wrapped.