Skip to content

Step Each Packed Colour Channel in BBC Micro BASIC

Unpack logical RGB332, move each channel by one towards its target and repack without cross-channel carry.

Treat a packed colour as one ordinary number and a small addition can change the wrong channels. A controlled fade instead approaches each channel separately, then packs the result again.

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

Include and initialise

Reserve public C/T/O, private R/G/B/A/D/E and lines 3000–3210. There are no arrays. Combine routines.bas with the caller using build.py, with END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Call 3000 to clear all public fields, then set current C and target T.

Both are integral bytes in this component’s logical RGB332 format: red bits seven through five, green four through two, blue one through zero. The channel ranges are zero through seven, seven and three respectively. This is a data format, not a promise about a machine’s native palette encoding.

Approach each channel

Call 3100 once per intended step. Each channel moves at most one towards its target. C becomes the repacked result, T survives and O=1 reports valid input. A call already at the target is idempotent. Invalid input preserves C/T and returns O=0.

Reusable numbered routines

routines.bas
3000 LET C=0: LET T=0: LET O=0: RETURN
3100 LET O=0
3110 IF C<0 OR C>255 OR C<>INT (C) THEN RETURN
3120 IF T<0 OR T>255 OR T<>INT (T) THEN RETURN
3130 LET R=INT (C/32): LET G=INT (C/4)-8*R: LET B=C-4*INT (C/4)
3140 LET A=INT (T/32): LET D=INT (T/4)-8*A: LET E=T-4*INT (T/4)
3150 IF R<A THEN LET R=R+1
3160 IF R>A THEN LET R=R-1
3170 IF G<D THEN LET G=G+1
3180 IF G>D THEN LET G=G-1
3190 IF B<E THEN LET B=B+1
3200 IF B>E THEN LET B=B-1
3210 LET C=32*R+4*G+B: LET O=1: RETURN

Each channel is bounded before repacking, so there is no carry from blue into green or green into red. The number of steps to finish is the largest initial channel difference, not the difference between the packed byte values.

Retain progress between calls

Normal C=0,T=255 produces packed values 37,74,111,147,183,219,255,255. The custom reverse trace produces 218,181,144,108,72,36,0,0. The last repeated value checks that the target remains stable. Saved second-step and seventh-step checks show progress before that final call.

Example calls from the maintained caller

demo.bas, excerpt
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET C=0: LET T=255
90 GOSUB 3100: GOSUB 2000
100 GOSUB 3100: GOSUB 2000
110 IF C=74 THEN LET BB=1
120 GOSUB 3100: GOSUB 2000
130 GOSUB 3100: GOSUB 2000
140 GOSUB 3100: GOSUB 2000
150 GOSUB 3100: GOSUB 2000
160 GOSUB 3100: GOSUB 2000
170 IF C=255 THEN LET AA=1
180 GOSUB 3100: GOSUB 2000
190 PRINT "FADE RESULTS";AA;BB
200 ?(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/JJ (Z on Spectrum) and the mailbox are observations. These native runs verify the logical calculation, not a displayed palette fade. A game needs a chosen conversion from RGB332 to its actual palette or output format. The reusable routine writes no hardware colour register and owns no display state.

Choose the visual result

An indexed palette needs an intentional mapping, not a direct write of this packed value. A hardware format with other channel widths needs scaling. Equal numeric steps need not look equally bright, and the blue channel has fewer representable levels than red or green in this example.

A precomputed sequence of actual hardware palette values can look better and cost less runtime, using more data. Wider logical channels can provide smoother intermediate values when the output path can use them. Choose the representation and mapping together rather than calling every numerical interpolation a useful visual fade.

Mistake and repair

The wrong caller resets C to the original endpoint before every step. The normal path stays at 37, the custom path at 218, and neither progresses. Both saved checks fail. Retain current colour until the effect reaches its target or is intentionally restarted. Repaired source and loaded program match normal 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 call splits three channels, makes bounded comparisons and repacks them. There is no search, allocation, timer or device access. More channel precision and perceptual correction add a different cost. Tokenised program size measures this implementation’s storage, not display quality or a hardware fade rate.

Maths for Games connects packed fields, independent interpolation and convergence. The Craft connects colour changes to the visual feedback the player actually sees.

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 354 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, 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-world-policy.py packed-colour-step bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-world-policy.py packed-colour-step.

Explore further

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