Sometimes independent random choices are the wrong fit. You may want every music cue, challenge or piece type to appear once before the set starts again. A shuffle bag provides that guarantee inside each bag, without promising anything about the random generator that chooses the order.

Include and reset
Reserve B(8), scalars C/N/J/R/O/E and private I/K. Reserve lines 3000–3220, including line 3185. Combine numbered sources with build.py; keep END, STOP or a jump before the helper. Use GOSUB on C64/BBC or GO SUB on Spectrum. Private scratch must not be a surrounding caller’s FOR variable.
Set C to an integral capacity from one to eight and call 3000. A valid reset fills B with item IDs 0..7, sets N=C, returns O=1,E=0,R=255 and retains J. Only B(1)..B(N) are active. An invalid reset returns O=0,R=255,E=1 while preserving the old N/B. Correct C before trying to reset again. Reset success does not return a drawn item: R remains 255.
Draw without replacement
Supply J from zero to N-1 and call 3100. It returns O=1,R=item ID,E=0. It replaces the selected active entry with the last active entry, then decrements N. No active item is returned twice before reset. The consumed tail is not cleared and may contain duplicates; N defines the active prefix.
An empty bag returns O=0,R=255,E=0 and ignores J and the consumed tail. It does not refill itself. E=1 rejects an invalid C/N or a damaged active bag, including duplicate active IDs. E=2 rejects an invalid J after state validation. Rejected draws preserve N and every B entry. Always check O before using R as an index.
The reusable routine
Reusable numbered routines
3000 LET R=255: LET O=0: LET E=1
3010 IF C<1 OR C>8 OR C<>INT (C) THEN RETURN
3020 FOR I=1 TO 8: LET B(I)=I-1: NEXT I: LET N=C: LET O=1: LET E=0: RETURN
3100 LET R=255: LET O=0: LET E=1
3110 IF C<1 OR C>8 OR C<>INT (C) THEN RETURN
3120 IF N<0 OR N>C OR N<>INT (N) THEN RETURN
3130 IF N=0 THEN LET E=0: RETURN
3140 FOR I=1 TO N
3150 IF B(I)<0 OR B(I)>=C OR B(I)<>INT (B(I)) THEN RETURN
3160 IF I=1 THEN GO TO 3190
3170 FOR K=1 TO I-1
3180 IF B(K)=B(I) THEN RETURN
3185 NEXT K
3190 NEXT I
3200 LET E=2
3210 IF J<0 OR J>=N OR J<>INT (J) THEN RETURN
3220 LET R=B(J+1): LET B(J+1)=B(N): LET N=N-1: LET O=1: LET E=0: RETURNSwapping in the last item closes the gap without shifting a whole suffix. The internal order changes, but the remaining set loses exactly the returned ID. Validation checks active IDs and uniqueness before any removal.
Empty one whole bag
The retained caller resets once, draws C times and attempts one extra draw. It tracks returned IDs to check uniqueness and checks that the bag reaches empty. The full diagnostic also covers first/last positions, small capacity, bad indices, invalid counts, damaged active entries and ignored consumed tails.
Maintained retained-state caller
90 LET C=8: GO SUB 3000: GO SUB 2000: LET AA=1: LET SS=0
100 FOR Z=1 TO C
110 LET J=0
120 GO SUB 3100: GO SUB 2000
130 IF O=0 THEN LET AA=0
140 IF O=1 THEN LET FF=INT (SS/(2^R))-2*INT (SS/(2^(R+1)))
150 IF O=1 AND FF=1 THEN LET AA=0
160 IF O=1 THEN LET SS=SS+(1-FF)*2^R
170 NEXT Z: LET BB=0
180 IF N=0 THEN LET BB=1
190 LET J=0: GO SUB 3100: GO SUB 2000
200 PRINT "BAGS RESULTS";AA;BB
210 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 line 2000 writer, mailbox, AA/BB, SS/FF and JJ/KK (Z/Q on Spectrum) are diagnostic bookkeeping. They are not required by the reusable helper. The working caller chooses deterministic first-slot indices so its result is reproducible; it is not a demonstration of a random stream.
Customise and supply randomness
The custom caller uses four items and selects the last active slot. It still returns each item once and empties. Both versions keep the same eight-element array allocation. Your game can map the returned IDs to its own objects, sounds or events; the helper owns no names, rendering or object lifetime.
For a random order, supply an independently uniform J over the current active prefix on each draw. With that assumption, every permutation is equally likely. The helper does not call an interpreter’s RND function or prove a source generator meets that assumption. Fixed J=0 still gives a valid non-repeating bag, but its order is deterministic.
Refill at a deliberate boundary. A bag can end with item two and the next bag can begin with item two: there is no cross-bag no-repeat guarantee. Avoiding that boundary repeat needs an additional policy and retained previous item; it changes the set of permitted orders. A bag of eight different IDs also differs from a bag containing weighted duplicate types. This maintained version requires unique active IDs.
Mistake and repair
The wrong caller resets before every draw. The same selected item returns repeatedly and N never reaches zero. Its uniqueness and exhaustion checks both print zero. Resetting is a session boundary, not part of drawing one item.
The repair resets once, then retains N/B through all draws. Repaired source and loaded tokenised program match the working caller exactly.

Cost and alternatives
Removal itself changes one array entry and one count. The readable defensive version also scans the active prefix and checks duplicates, taking up to 28 pair comparisons for eight items. Calling the whole routine constant-time would hide that validation cost. Numeric array elements and scalar descriptors also use more than one byte each.
A pre-shuffled array with a read cursor moves the shuffle cost to refill. A weighted independent selector permits immediate repeats but can express rare outcomes. Larger bags or frequent draws may justify a different validation boundary, backed by a clear caller guarantee and new evidence. Maths for Games connects sets, permutations and sampling without replacement. The Craft helps decide when repeat prevention improves a game’s rhythm and when it makes events too predictable.
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 532 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-selection.py shuffle-bag sinclair-zx-spectrum --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-selection.py shuffle-bag.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.