Put a direction beside a flag and you have the beginnings of a compact record. The difficult part is changing one field without damaging its neighbours. This writer checks the whole request before changing a destination bit.

Include and initialise
Reserve B(4), public O/C/N/V and private I/J/K/P/T/H. Reserve lines 3000–3240, combine routines.bas and your caller with build.py, and put END, STOP or a jump before the helper. Use GOSUB on C64/BBC and GO SUB on Spectrum. Private scratch must not double as an outer loop variable.
Call 3000 at a new-block boundary. It clears the destination bytes, cursor C, inputs N/V and result O. Load any existing destination bytes after init. Retain C and B between field writes.
Write one field
Supply width N=0..8 and value V=0..255, then call 3100. The helper writes the low N bits of V, most significant selected bit first, starting at bit C. B(1)’s most significant bit is offset zero. O=1 reports success and C advances by N. A zero-width write succeeds without changing any byte or the cursor.
| Field | Use |
|---|---|
| B(1)..B(4) | Retained destination; other bits are preserved |
| C | Retained next-bit offset, 0..32 |
| N / V | Requested width / unsigned source value |
| O | Fresh valid flag after the call |
C, N, V and every destination byte must be integral and in range. The complete span must fit within 32 bits. A failure sets O=0 and preserves C, N, V and all destination bytes. Success retains N/V too. This is all-or-nothing span validation under one mutation owner; it installs no interrupt exclusion or hardware upload.
The reusable routine
Reusable numbered routines
3000 LET O=0: LET C=0: LET N=0: LET V=0: FOR I=1 TO 4: LET B(I)=0: NEXT I: RETURN
3100 LET O=0
3110 IF C<0 OR C>32 OR C<>INT (C) THEN RETURN
3120 IF N<0 OR N>8 OR N<>INT (N) THEN RETURN
3130 IF C+N>32 THEN RETURN
3140 FOR I=1 TO 4
3150 IF B(I)<0 OR B(I)>255 OR B(I)<>INT (B(I)) THEN RETURN
3160 NEXT I
3170 IF V<0 OR V>255 OR V<>INT (V) THEN RETURN
3180 LET O=1
3190 IF N=0 THEN RETURN
3200 FOR J=1 TO N
3210 LET I=INT (C/8)+1: LET K=C-8*INT (C/8): LET P=2^(7-K)
3220 LET T=INT (B(I)/P): LET T=T-2*INT (T/2)
3230 LET H=INT (V/2^(N-J)): LET H=H-2*INT (H/2)
3240 LET B(I)=B(I)+(H-T)*P: LET C=C+1: NEXT J: RETURNFor each target bit, the routine finds the old bit and the required bit, then adds their difference multiplied by that bit’s place value. Surrounding bits stay intact. The selected source width matters: V=255 with N=4 writes 15, not an error. Enforce a narrower game-specific value range before calling if truncation is inappropriate.
Pack a retained record
The normal caller starts with four bytes of 85, writes two nibbles followed by three bytes, and obtains 171,205,239,1. The initial nonzero destination makes preserving and replacing existing bits observable.
Example calls from the maintained caller
90 GO SUB 3000: LET AA=0: LET BB=0
100 FOR Z=1 TO 4: LET B(Z)=85: NEXT Z
110 LET N=4: LET V=10
120 GO SUB 3100: GO SUB 2000
130 LET N=4: LET V=11
140 GO SUB 3100: GO SUB 2000
150 LET N=8: LET V=205
160 GO SUB 3100: GO SUB 2000
170 LET N=8: LET V=239
180 GO SUB 3100: GO SUB 2000
190 LET N=8: LET V=1
200 GO SUB 3100: GO SUB 2000
210 IF C=32 THEN LET AA=1
220 IF B(1)=171 AND B(2)=205 AND B(3)=239 AND B(4)=1 THEN LET BB=1
230 PRINT "PACK RESULTS";AA;BB
240 POKE MB+995,AA: POKE MB+996,BB: POKE MB+997,GD: POKE MB+998,WC-256*INT (WC/256): POKE MB+999,INT (WC/256): POKE MB+1000,239: STOPThe diagnostic’s line 2000, mailbox, AA/BB and JJ/KK (Z/Q on Spectrum) are removable. Your game retains its own B/C, sets N/V and consumes O. The complete native trace checks every destination value after each write, including cross-byte requests and rejected spans.
Customise the layout
The custom caller builds the same bytes with four eight-bit writes. Changing field widths changes your local record layout; update its reader too. This routine accepts no file header or implicit terminator and introduces no project-wide format.
A byte-aligned record is simpler and faster to edit. Native bit masks or a reservoir can reduce hot-loop work, while buffered file output adds cursor/refill ownership of its own. Choose compactness where it helps storage or transfer; packing is not automatically faster.
Mistake and repair
The wrong caller resets C before every field, repeatedly overwriting the start. Its returned O flags remain valid, but C and the completed record are wrong. The repair keeps C/B between writes. The retained trace checks each returned O. Repaired source and loaded program match the normal caller exactly.

Cost and Maths for Games
A call validates four bytes and visits at most eight bits. Powers, division and range checks cost interpreter work; the helper has no measured hardware-cycle budget. Its globals and arrays occupy interpreter-managed numeric storage.
Maths for Games connects bit positions, powers of two and masks to compact state. The Craft helps decide which values deserve packed storage and which should remain easy to inspect. Compare a read-after-write round trip while also checking that bits outside the field survive.
Machine and measured size
Spectrum FOR variables must be single letters: this diagnostic uses Z/Q instead of the other callers’ JJ/KK. Numeric arrays start at one. AND/OR in these listings join comparisons; neither is being used as a bitwise operator. The diagnostic reserves its mailbox above CLEAR 59999.
The numbered helper occupies 566 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, Z/Q and RAM mailbox are observation machinery, not required game state.
Execution evidence
Working, custom, deliberately wrong and exact repaired callers executed through Spectrum’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-packing.py bounded-bit-writer sinclair-zx-spectrum --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-packing.py bounded-bit-writer.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.