Skip to content

Change Inventory Counts Atomically in BBC Micro BASIC

Add or remove a whole quantity from four fixed item categories, rejecting requests that exceed stock or capacity.

Picking up five arrows should not quietly credit one arrow unless the game has chosen a partial-pickup rule. These routines accept or reject the whole requested quantity. They are useful for ammunition, keys, ingredients or other fixed categories of counted objects.

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

Include and initialise

Reserve DIM Q(4), select a common integral per-category capacity C from 1 to 255, combine the numbered listings and call 3000. It clears Q(1)..Q(4), S/A/M/O/E, retaining C. Reserve lines 3000–3260 and private loop variable I. Do not wrap the call in a caller’s FOR I loop. END, STOP or a jump must prevent falling into the helpers.

The four slots are fixed categories, not four interchangeable item stacks. Your game defines what slot zero, one, two and three mean. A public slot S=0 maps to Q(1). C64/BBC arrays also allocate an unused element zero; Spectrum arrays start at one. Spectrum spells calls GO SUB; C64/BBC use GOSUB. The supplied build.py combines source lines and rejects duplicates.

Add or remove a quantity

Supply S=0..3, A=0..255 and M=0 to add or M=1 to remove, then call 3100. O=1 means the complete quantity was applied. O=0 with E=0 means insufficient stock or insufficient free capacity. No quantity changes on rejection. A zero request is a successful no-op.

E=1 rejects invalid capacity or any damaged retained count before touching a slot. E=2 rejects an invalid slot, amount or mode. Every count and input is integral, with each Q between zero and C. Validating all four categories costs time but prevents a request from mutating an already inconsistent inventory.

The routine

Reusable numbered routines

routines.bas
3000 FOR I=1 TO 4: LET Q(I)=0: NEXT I
3010 LET S=0: LET A=0: LET M=0: LET O=0: LET E=0: RETURN
3100 LET O=0: LET E=1
3110 IF C<1 OR C>255 OR C<>INT (C) THEN RETURN
3120 FOR I=1 TO 4
3130 IF Q(I)<0 OR Q(I)>C OR Q(I)<>INT (Q(I)) THEN RETURN
3140 NEXT I
3150 LET E=2
3160 IF S<0 OR S>3 OR S<>INT (S) THEN RETURN
3170 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3180 IF M<>0 AND M<>1 THEN RETURN
3190 LET E=0
3200 IF M=1 THEN GOTO 3250
3210 IF A>C-Q(S+1) THEN RETURN
3220 LET Q(S+1)=Q(S+1)+A: LET O=1: RETURN
3250 IF A>Q(S+1) THEN RETURN
3260 LET Q(S+1)=Q(S+1)-A: LET O=1: RETURN

A retained stock example

The caller starts one unit below capacity, tries to add two, and checks that the request is refused without changing stock. It then fills the final space, removes a full stack, tries an unavailable removal and exercises another category. The earlier table in the complete diagnostic covers varied counts, invalid slots, damaged state and invalid modes.

Calls to line 2000 in this excerpt write diagnostic observations; that writer and the mailbox setup are in the complete source. In your own game, omit those observation calls and the final mailbox write. Keep the public helper calls, and supply your game state as described above.

Maintained retained-state caller

demo.bas, excerpt
80 LET C=99: GOSUB 3000: LET Q(1)=C-1: LET S=0: LET M=0: LET A=2
90 GOSUB 3100: GOSUB 2000: LET Y=0: LET D=0
100 IF O=0 THEN LET Y=1
110 IF Q(1)=C-1 THEN LET D=1
120 LET S=0: LET A=1: LET M=0: GOSUB 3100: GOSUB 2000
130 LET S=0: LET A=99: LET M=1: GOSUB 3100: GOSUB 2000
140 LET S=3: LET A=1: LET M=1: GOSUB 3100: GOSUB 2000
150 LET S=2: LET A=5: LET M=0: GOSUB 3100: GOSUB 2000
160 LET S=2: LET A=5: LET M=1: GOSUB 3100: GOSUB 2000
170 LET S=1: LET A=0: LET M=0: GOSUB 3100: GOSUB 2000
180 PRINT "PACK RESULTS";Y;D
190 ?(W+995)=Y: ?(W+996)=D: ?(W+997)=G: ?(W+998)=Z-256*INT (Z/256): ?(W+999)=INT (Z/256): ?(W+1000)=239: STOP

Ownership and customisation

The helper owns S/A/M/C/O/E, Q and scratch I. It owns no item names, icons, prices, carry weight or object handles. To display “arrows”, the caller looks up its own name for that category. Separate inventories need separate arrays, globals and numbered references; sharing Q would share the contents.

The custom caller lowers C from 99 to 9. DIM Q(4) remains unchanged, so there is no claimed allocation saving. Different capacities per item need a capacity array and matching validation. More categories require changing DIM, the clear/validation loops, slot bounds and tests together. The current interface rejects slot 4 instead of guessing that a fifth category exists.

A partial pickup can be useful, but it needs an output reporting the accepted quantity and a caller that leaves the remainder in the world. Silently reducing A changes the meaning of success. Likewise, a weighted inventory or an equipment-slot system is a different contract, not just another title for these four counters.

Mistake and repair

The wrong caller clips the requested addition from two to one before calling. That smaller request succeeds and fills the stack. Its saved checks are 0,0: it was not rejected, and stock no longer equals its original value. The helper cannot know the original request was larger.

The repair passes the original amount and handles failure explicitly. Its source and loaded program match the working caller 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.

Costs and alternatives

Every call validates four counts, then updates at most one. The fixed four-category scan is bounded; expanding the category count grows validation work. Numeric arrays are not packed bytes. A simple scalar is cheaper for a single ammunition count; typed stacks with item IDs are useful when categories are not fixed. A transfer between inventories must validate both sides before either changes. The Craft is the place to choose the player’s pickup/capacity rules; Maths for Games develops the bounds and conservation checks.

Machine and measured size

BBC BASIC arrays include element zero; the inventory interface leaves it unused. 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 414 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 W/Z/G, Y/D 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. 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 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-rules.py inventory-stacks bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-rules.py inventory-stacks.

Explore further

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