Skip to content

Service an Action Cooldown in BBC Micro BASIC

Keep time moving between requests, then grant an action only when the cooldown and caller-supplied availability allow it.

A weapon should recover while the player is not pressing Fire. A cooldown that advances only on a request punishes the player for waiting. Separating the timer service from the action attempt makes that mistake visible.

BBC Micro BASIC shows both retained behaviour checks passing.
The working caller completes with both saved checks equal to one.

Include and initialise

Reserve R/Q/A/P/M/G/O and lines 3000–3210. Combine routines.bas with your caller using build.py, keeping END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Call 3000 once at a session/reset boundary. It clears every public field; set the desired period P afterwards. There are no arrays or private scratch globals.

Field Use
R / P Remaining calls / period to reload on grant
Q / A Request / caller-supplied availability byte
M Operation: zero services time, one attempts an action
G / O Fresh grant pulse / valid-input flag

All retained state and inputs are integral bytes. P=0 disables rate limiting; it does not disable requests or availability checks. Any nonzero byte Q/A is true. Use 0/1 flags, normalising a dialect’s comparison result if necessary. Invalid state or an unknown M clears G/O and preserves R and the inputs.

Service, then try

Call 3100 with M=0 once per simulation update, whether or not a request exists. It reduces positive R by one, clears G and returns O=1. It never grants an action. Then set M=1 and call 3100 when you want to attempt an action. This second operation never advances the timer. It grants only when R=0, Q is nonzero and A is nonzero, returning G=1 and reloading R=P.

Reusable numbered routines

routines.bas
3000 LET R=0: LET Q=0: LET A=0: LET P=0: LET M=0: LET G=0: LET O=0: RETURN
3100 LET G=0: LET O=0
3110 IF R<0 OR R>255 OR R<>INT (R) THEN RETURN
3120 IF Q<0 OR Q>255 OR Q<>INT (Q) THEN RETURN
3130 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3140 IF P<0 OR P>255 OR P<>INT (P) THEN RETURN
3150 IF M<>0 AND M<>1 THEN RETURN
3160 LET O=1
3170 IF M=1 THEN GOTO 3200
3180 IF R>0 THEN LET R=R-1
3190 RETURN
3200 IF R<>0 OR Q=0 OR A=0 THEN RETURN
3210 LET R=P: LET G=1: RETURN

Read G immediately after the attempt. Another service or rejected attempt replaces it. P=3 permits grants on updates zero, three, six and nine when the caller requests on those updates. An attempt on a still-positive timer leaves it alone. A period of zero permits repeated attempts in the same update, so the caller must still enforce its intended action policy.

Keep recovery moving

The normal caller requests at updates zero, three, six and nine. It observes both the time-service and attempt states on every update. Period three grants four times; the custom period five grants twice. The final timer check catches a counter that stopped between taps.

Example calls from the maintained caller

demo.bas, excerpt
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET P=3: LET A=1: LET NN=0
90 LET Q=1: LET M=0
100 GOSUB 3100
110 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
120 LET Q=0: LET M=0
130 GOSUB 3100
140 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
150 LET Q=0: LET M=0
160 GOSUB 3100
170 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
180 LET Q=1: LET M=0
190 GOSUB 3100
200 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
210 LET Q=0: LET M=0
220 GOSUB 3100
230 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
240 LET Q=0: LET M=0
250 GOSUB 3100
260 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
270 LET Q=1: LET M=0
280 GOSUB 3100
290 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
300 LET Q=0: LET M=0
310 GOSUB 3100
320 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
330 LET Q=0: LET M=0
340 GOSUB 3100
350 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
360 LET Q=1: LET M=0
370 GOSUB 3100
380 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
390 LET Q=0: LET M=0
400 GOSUB 3100
410 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
420 LET Q=0: LET M=0
430 GOSUB 3100
440 GOSUB 2000: LET M=1: GOSUB 3100: GOSUB 2000: LET NN=NN+G
450 IF NN=4 THEN LET AA=1
460 IF R=1 THEN LET BB=1
470 PRINT "COOL RESULTS";AA;BB
480 ?(MB+995)=AA: ?(MB+996)=BB: ?(MB+997)=GD: ?(MB+998)=WC-256*INT (WC/256): ?(MB+999)=INT (WC/256): ?(MB+1000)=239: STOP

Line 2000, AA/BB/NN/JJ (Z on Spectrum) and the mailbox are diagnostics. A game needs the service and attempt calls, the retained fields and immediate handling of G/O. A must reflect whether the game can actually create the action now: enough ammunition, a free projectile slot, or whatever that action requires. This helper neither checks nor reserves those resources. Keep one owner for the check, grant and creation sequence.

Customise and extend

Change P to alter recovery in simulation calls. Changing P affects the next grant, not an already running R. Pause by withholding the time service deliberately; do not accidentally pause it by withholding requests. Independent weapons need separate renamed fields/line references, or a wrapper that selects and restores each state block.

Automatic repeat and new-press-only actions are input policies supplied through Q. A fixed-step loop makes the period consistent in simulation time; an untimed turn-based game can service it once per turn instead. No video or hardware clock is read here.

Mistake and repair

The wrong caller services time only on requested updates. Recovery freezes during the gaps: with the normal period it grants at zero and nine instead of four times. Both saved checks fail. Service time independently of input, then attempt. The repair restores normal source and loaded program exactly.

BBC Micro BASIC shows two failed checks from the deliberate caller mistake.
The wrong caller runs to completion, but both saved behaviour checks are zero.

Cost and Maths for Games

Each operation uses a bounded number of validations, comparisons and at most one decrement or reload. There is no input polling, search or allocation. An absolute deadline is an alternative when the game already owns a suitable wrap-aware clock. Maths for Games connects expiry boundaries and discrete time; The Craft connects those boundaries to weapon rhythm and responsiveness.

Global names and line numbers

BASIC numeric names, arrays and numbered lines are global. To combine helpers, rename public and private fields and renumber the helper together with every GO SUB/GO TO reference. Keep each state block under one owner. The maintained build.py rejects duplicate line numbers; it does not automatically namespace or renumber routines.

Machine and measured size

This helper uses scalar numeric fields and allocates no arrays. Its diagnostic uses MODE 7 and HIMEM=&6000 to separate program storage, mailbox and screen. The helper itself changes neither mode nor HIMEM.

The numbered helper occupies 328 tokenised program bytes on this interpreter, including line headers. This excludes the caller, test tables, numeric scalar/array storage, interpreter stacks and ROM. Tokenised size is not machine-code size or a hardware-cycle count. No real-time or whole-game performance budget is inferred from the diagnostic. The caller’s AA/BB/NN, JJ and RAM mailbox are observation machinery, not required game state.

Execution evidence

Working, custom, deliberately wrong and exact repaired callers executed through BBC Micro’s stock BASIC interpreter under Emu198x 0.44.2. Build198x 0.2.9 tokenises C64 PRG and Spectrum TAP programs. C64 enters stock RUN, Spectrum uses ROM tape loading, and BBC enters every numbered source line through MOS/BASIC. Actual loaded program bytes, complete state packets, positive completion/count/guards and visible result pixels are checked. After the positive completion marker, the observer advances two complete display frames before capturing, so a partially scanned final character cannot masquerade as a finished picture. Firmware is not distributed.

An independent mathematical/order model checks the trace and rejects altered source, configuration, loaded program, state, completion and image evidence. Host exhaustive checks complement the native boundary/sequential examples; those host ranges are not claimed as natively exhaustive. The supplied diagnostic callers deliberately end with STOP; the interpreter report is expected. The wrong caller prints 0,0 and the normal/custom/repaired callers print 1,1.

Public CI checks retained evidence; it does not execute restricted firmware. Refresh with python3 scripts/verify-basic-resources.py action-cooldown bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-resources.py action-cooldown.

Explore further

Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.