Three independent meteors need three sets of state. Rather than inventing a new variable name for each one, we will place equal-sized records next to each other.
A record is a group of values describing one thing. Our first meteor record has four bytes:
| Offset from its start | Meaning | Example |
|---|---|---|
| 0 | X | 80 |
| 1 | Y | 24 |
| 2 | Active: 0 free, 1 occupied | 1 |
| 3 | Downward speed | 2 |
The program reserves three records with objects: defs SLOTS*4. defs reserves bytes in the assembled program, and SLOTS equ 3 names how many records there are. Three times four is twelve bytes, and that space stays fixed while the game runs. The records sit between state_start and state_end, so unit 11’s block reset empties them at the start of every run.
Source · view and edit
Companion file: assets.inc
The include line reads this editable data when you assemble. Keep it beside your source for a local build.
Edit the source, then assemble.
Edits are saved in this browser. Keep a downloaded copy of work you want to keep.
Focus the Spectrum screen to use the keyboard, or hold these buttons. Assemble again for a fresh machine.
Run the program to inspect it. Editing source does not change the running machine.
ship_x- —
hull- —
phase- —
Read the active object records
These rows read the three records. Empty slots are omitted. The display samples RAM while the program runs, so values can change between observations.
| Slot | X | Y | Kind / active | Speed |
|---|
Launch, then open Read the active object records. new_game now starts three meteors together: X=80 at speed 2, X=116 at speed 3 and X=170 at speed 4. Each row is a live read of one occupied record. It is not a prediction of where a meteor ought to be.
Let IX identify the current object
IX is a sixteen-bit index register. ld ix,objects points it at the first record. (ix+1) means the byte one address after IX, so ld a,(ix+1) reads this object’s Y. The same instruction reads the next object’s Y when IX points four bytes further on.
new_game uses that to fill the records. For each meteor it points IX at one record with ld ix,objects, ld ix,objects+4 or ld ix,objects+8, stores the X and speed in spawn_x and spawn_speed, then calls spawn. spawn writes X, Y=24, active=1 and speed into the record at IX and draws the new meteor. It does not choose a record: the caller does that by setting IX first.
spawn:
ld a,(spawn_x)
ld (ix+0),a
ld (ix+1),24
ld a,1
ld (ix+2),a
ld a,(spawn_speed)
ld (ix+3),a
ld e,(ix+0)
ld d,24
jp draw_meteorThe record layout is an agreement between every routine that reads or writes objects. If spawn writes speed at offset 3 but movement reads offset 2, changing the drawing routine cannot repair it.
If objects starts at $9000, where are the second record's Y and the third record's speed?
Show the explanation
The second record starts at $9004, so its Y is $9005. The third starts at $9008, so its speed is $900B. Add the record stride first, then the field offset. These example addresses explain the layout; use the assembled symbols for the running program’s actual addresses.
One routine, called once per record
Unit 11’s flight loop moved its one meteor with instructions that named meteor_x and meteor_y. Those instructions now live in advance_meteor, which reads every field through IX. advance_meteors points IX at each record in turn and calls it.
; One explicit call per record; IX says which record each call handles.
advance_meteors:
ld ix,objects
call advance_meteor
ld ix,objects+4
call advance_meteor
ld ix,objects+8
call advance_meteor
ret
; IX=record: 0 X, 1 Y, 2 active, 3 speed.
advance_meteor:
ld a,(ix+2)
or a
ret z
ld e,(ix+0)
ld d,(ix+1)
call draw_meteor
ld a,(ix+1)
add a,(ix+3)
ld (ix+1),a
cp 174
jr nc,advance_meteor_remove
; Inset rectangles: meteor y+2..9, ship y+3..13.
cp 154
jr c,advance_meteor_draw
cp 172
jr nc,advance_meteor_draw
ld a,(ship_x)
ld c,a
ld a,(ix+0)
sub c
jr nc,advance_meteor_absolute
neg
advance_meteor_absolute:
cp 16
jr nc,advance_meteor_draw
; One contact ends this attempt; no recovery state is needed.
xor a
ld (hull),a
call impact_sound
advance_meteor_remove:
ld (ix+2),0
ret
advance_meteor_draw:
ld e,(ix+0)
ld d,(ix+1)
jp draw_meteoradvance_meteor returns at once when the record’s active byte is zero. Otherwise it erases the meteor, adds the record’s own speed to its Y, and frees the record once Y reaches 174. Between 154 and 171 it runs unit 10’s contact test, reading the meteor’s X from (ix+0). A contact clears hull and frees the record as well, so the meteor that hit is not drawn again. A surviving meteor is drawn at its new position. draw_meteor takes the position in E and D, where unit 11’s meteor routine read its two variables itself.
Predict, then check: change the third ld ix,objects+8 in advance_meteors to ld ix,objects+4. The second record now receives two calls per update, so the X=116 meteor falls six pixels per update instead of three. No call reaches the third record, so the X=170 meteor hangs at Y=24 and its active byte stays 1. The run never reaches CLEAR SPACE; press Q to leave it. Restore objects+8.
Separate existence from visibility
The active byte tells us whether a record belongs to an object. A zero byte is a free slot even if old X, Y and speed values remain there. We do not need to erase every field when an object leaves; the next spawn will replace them before setting it in motion.
The records replace meteor_x and meteor_y. The ship keeps its own ship_x because there is only one player ship and its behaviour differs from falling objects.
Try following two rows at once. A faster meteor can move farther per update even though both receive updates from the same clock. Separate positions and speeds make that independence possible; adding a second drawing call alone would only make a second picture of the same state.
Finish when every record is free
Unit 11 jumped to won as soon as its one meteor reached y=174. Now a meteor that leaves only frees its record, and the main loop decides after the hull check. It ORs the three active bytes, at objects+2, objects+6 and objects+10. The result is zero only when all three are zero, and then the loop jumps to won.
; Offset 2 of each four-byte record is its active byte.
ld a,(objects+2)
ld hl,objects+6
or (hl)
ld hl,objects+10
or (hl)
jp z,wonThe order matters. A contact frees the meteor’s record too, so a hit on the last meteor leaves all three active bytes at zero. Checking hull first sends that run to lost before the OR can call it clear space.
Steer clear of all three and the run ends in CLEAR SPACE. Each meteor retires on the first update that takes its Y to 174 or more: the speed 4 meteor on update 38, the speed 3 meteor on update 50 and the speed 2 meteor on update 75. Their rows leave the record table in that order, and CLEAR SPACE follows three seconds after launch.
Sources
The maintained Meteor Storm source linked through this page supplies the game rules and data layout. Its experiments run original Z80 instructions on an emulated 48K PAL Spectrum; the game rules are design choices, not hardware properties.
Run it here
This unit's program, built for you.
- Program
- meteor-storm.tap
- Machine
- ZX Spectrum 48K (PAL)