The Machine Trusts You
The last beat, and the most important. There's no error message, no bounds check, no guard rail — the machine does exactly what you wrote, even when you're wrong. Learn to debug by observing, not by reading errors.
Every unit so far ended with something that worked. This one ends with something that's wrong — on purpose — because the single most important thing to learn about this machine is what it does when you are wrong.
What you'll see by the end
That was supposed to be one red row. It's about eight. Count them — and notice the last row stops one cell short. That short corner is cell number 255, exactly. The machine filled precisely 255 cells, right to the last one, because that's the number we wrote. It is only a "mistake" because we know we meant 32. The machine couldn't know, and didn't ask.
The bug, running
; ============================================================================
; PRIMER — Beat 12: The Machine Trusts You
; ============================================================================
; Every program so far ended with something that worked. This one ends with
; something that's WRONG -- on purpose -- because the most important thing to
; learn about this machine is what it does when you make a mistake.
;
; This looks like Beat 10's "colour the top row". But the count is 255, not
; 32. We meant one row. Watch what the machine does with what we actually
; wrote: it colours 255 cells -- about eight rows -- and never says a word.
;
; There is no "Wrong count" error. No bounds check. No "did you mean 32?".
; The Z80 does EXACTLY what the bytes say, even when the bytes are wrong.
; Debugging here is not reading an error message -- it's OBSERVING the state
; (the register and memory views you've used all Primer) and reasoning about
; what really happened.
; ============================================================================
org 32768
start:
ld hl, $5800 ; the top colour row
ld a, $17 ; red
ld b, 255 ; BUG: we meant 32. The machine won't mind.
.fill:
ld (hl), a
inc hl
djnz .fill ; runs 255 times, not 32 -- no warning
.loop:
halt
jr .loop
end start
This is the row-fill from Counting Toward Zero with one wrong number: ld b, 255 where we meant ld b, 32. It assembled without a murmur — a wrong count is not a wrong spelling, so the assembler had nothing to object to. Then it ran exactly as written.
There is no safety net
In BASIC, mistakes were met with a friendly stop: Variable not found, Subscript wrong, Out of memory. The language watched over you and halted when something looked off.
The bare machine does none of that. There is no bounds check, no type check, no "are you sure," no error at all. Write past where you meant to, point at the wrong address, leave a call without its ret — and the Z80 does exactly what the bytes say and carries straight on, corrupting whatever it touches, never pausing to warn you. The machine trusts you completely. That trust is the freedom that lets you write the screen with a single poke — and the responsibility that means every byte is yours to get right.
So debugging here is a different craft. It is not reading an error message, because there isn't one. It is observing the state — the register view, the memory view, the stack at SP you watched in Call, Return, and a Stack — and reasoning about what the machine did versus what you meant. Every view you've leaned on through this Primer was you learning to debug by looking. That's the discipline the whole course rests on.
Assemble and run
pasmonext --sna no-safety-net.asm primer.sna
Eight rows of red where you wanted one — and not a word of complaint.
Try this: it was never broken — your number was
Change ld b, 255 to ld b, 32. One clean row. Nothing about the machine changed; only your number did. That's the whole lesson in one edit: the machine was doing its job perfectly the entire time.
Try this: break it a different way
Point the fill at the wrong street: change ld hl, $5800 to ld hl, $4000. Now the same bytes land in the pixel map instead of the colour map, and you'll get a block of garbage pixels instead of colour. Predict what you'll see before you run it — then confirm. Again: no error, just the wrong thing done faithfully.
When it's wrong, see why
- The whole screen goes red and the machine seems stuck. You pushed the count even higher and the fill ran past the colour map into other memory. Reset the machine and bring the count back down — this is exactly the "no guard rail" lesson biting for real.
- Nothing happens at all. The
.fillloop or itsdjnzis mistyped, so the body never runs. Compare against the listing. - You wanted it to misbehave and it didn't. Then you fixed the bug — which is the happiest failure on this page.
What you've learnt
The machine has no safety net: it does exactly what you wrote, even when you're wrong, and never warns you — so you debug by observing the state, not by reading errors.
What's next — you've met the machine
That's the Primer. Look back at what stopped being magic: the build-run loop; registers; bytes with no type; memory as a street of boxes; the screen and its colour as just memory; decisions built from test-and-jump; reading a key; pointers; counted loops; subroutines and the stack; arithmetic; setting and clearing bits; numbers bigger than a byte; the CPU banging the speaker into a tone — and now the mindset that ties it together.
You haven't built a game yet — that's the point. You've built the understanding a game needs. Next comes the tiny first game, where this all turns into something you play, and where the two mysteries we deliberately left open get solved: the screen's strange layout (The Screen Is Memory) and the full keyboard (The Machine Can Hear You), each resolved exactly when the game needs it.
Go and write something the machine will trust you to get right.