You have the ingredients, but your pack is full. Pressing “make” should not eat the ingredients and give you nothing. This helper checks the whole recipe first, then changes all three counts together.

Include and initialise
Combine the numbered helper and caller, reserving 3000–3220. Set output capacity C from 1 to 255 and call 3000 once at a new-session boundary. It clears ingredient counts H/I, result count R, requested costs A/B, produced quantity P and results O/E, retaining C. Set starting stocks and a recipe after initialising. Put END, STOP or a jump before the helper. Spectrum spells calls GO SUB; C64/BBC use GOSUB. The supplied build.py combines numbered source and rejects duplicates.
Supply a recipe
H and I are counts of two distinct ingredient categories; R counts a separate result category. Supply required quantities A and B, and the quantity P to produce. Call 3100. O=1 means all three changes happened: H decreases by A, I by B, and R increases by P. O=0 with E=0 means insufficient ingredients or output space, and all stocks stay unchanged.
H/I and costs A/B must be integral 0–255; output R is 0..C and P is 1–255. At least one ingredient cost must be positive. A zero cost for one ingredient is allowed. E=1 rejects damaged stock/capacity; E=2 rejects an invalid recipe. Invalid calls clear O and leave all stocks unchanged. The helper does not combine two references to the same stock: H and I represent distinct resources.
The routine
Reusable numbered routines
3000 LET H=0: LET I=0: LET R=0: LET A=0: LET B=0: LET P=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 IF H<0 OR H>255 OR H<>INT (H) THEN RETURN
3130 IF I<0 OR I>255 OR I<>INT (I) THEN RETURN
3140 IF R<0 OR R>C OR R<>INT (R) THEN RETURN
3150 LET E=2
3160 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3170 IF B<0 OR B>255 OR B<>INT (B) THEN RETURN
3180 IF A+B=0 THEN RETURN
3190 IF P<1 OR P>255 OR P<>INT (P) THEN RETURN
3200 LET E=0
3210 IF H<A OR I<B OR P>C-R THEN RETURN
3220 LET H=H-A: LET I=I-B: LET R=R+P: LET O=1: RETURNCheck before spending
The retained client starts with enough ingredients but only one free output space, then asks to produce two. It checks that both ingredient stocks remain unchanged. After explicitly clearing output storage it crafts repeatedly until ingredients run out. The complete diagnostic’s earlier table covers varied stock, exact boundaries, invalid capacity, over-capacity result and zero-cost ingredient variants.
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
70 LET C=31: GOSUB 3000: LET H=5: LET I=4: LET R=C-1: LET A=2: LET B=1: LET P=2
80 GOSUB 3100: GOSUB 2000: LET Y=0: LET D=0
90 IF H=5 THEN LET Y=1
100 IF I=4 THEN LET D=1
110 LET R=0: FOR J=1 TO 4: GOSUB 3100: GOSUB 2000: NEXT J
120 PRINT "MAKE RESULTS";Y;D
130 ?(W+995)=Y: ?(W+996)=D: ?(W+997)=G: ?(W+998)=Z-256*INT (Z/256): ?(W+999)=INT (Z/256): ?(W+1000)=239: STOPOwnership and customisation
The helper owns H/I/R/A/B/P/C/O/E and uses no scratch loop variables. It owns no recipe names, UI, item discovery, animation or elapsed production time. A successful call is an immediate transaction. A timed crafting system needs a separate reservation/commit/cancel policy; do not subtract now and hope a later completion succeeds.
The custom caller lowers result capacity from 31 to 7 while keeping the same recipe. Choose different costs and output quantities in the caller. Increasing the number of ingredients requires checking every resource before subtracting any, with corresponding model and failure examples. Selling or exchanging items can use the same transaction idea, but money, weight and item slots introduce their own bounds.
This scalar version owns one recipe context. Separate simultaneous transactions need distinct state or an array-based wrapper. Changing the meaning of H/I/R between calls is possible only when the caller deliberately loads and stores the matching game resources.
Mistake and repair
The wrong caller spends H and I before asking the routine whether the output will fit. The routine correctly refuses the craft, but those earlier deductions have already happened. Its saved ingredient checks become 0,0. Retrying can then fail for a second reason: ingredients that used to be available are gone.
The repair leaves spending to the successful transaction path. Its source and actual loaded tokenised program match the working caller exactly.

Costs and alternatives
This has constant scalar storage and a bounded sequence of validation/comparison/subtraction operations. It checks available room as C-R, avoiding a machine-code byte-overflow assumption; BASIC numeric arithmetic is wider, but the interface still enforces byte-valued counts. There is no rollback buffer because nothing mutates until all conditions pass. Larger transactions may benefit from a checked plan followed by a separate commit. The Craft connects crafting rules to player expectations; Maths for Games supplies inequalities, bounds and conservation reasoning.
Machine and measured size
This helper uses scalar variables and needs no array allocation. 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 412 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 recipe-transaction bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-rules.py recipe-transaction.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.