An enemy disappears and its pool slot is reused. An old target reference should not suddenly point at the new enemy. A generation accompanies the slot number so the game can check which lifetime that reference belongs to.

Include and initialise
Reserve G(4), public A/M/S/T/O/R/V/E and private I/U/K. Reserve lines 3000–3370, combine numbered sources using build.py and put END, STOP or a jump before the helper. Use GOSUB on C64/BBC or GO SUB on Spectrum. Private scratch must not be an enclosing caller FOR variable.
Call 3000 at a deliberate new-session boundary. It clears A/M/S/T/O/V/E and every generation, with R=255. A is a four-bit live mask, from zero to fifteen; G(1)..G(4) are byte-valued generations. Slot zero uses the bit with value one and G(1). Discard all external handles when resetting the component: resetting generations can make references from a previous session alias new objects.
Allocate, query and release
Set M and call 3100:
| M | Call | Result |
|---|---|---|
| 0 | Allocate lowest free slot | O=1; R=slot; V=new generation |
| 1 | Release handle S/T | O=1; R=S; V=T; clears its live bit |
| 2 | Query handle S/T | O=1; R=S; V=T; retains live state |
Allocation ignores S/T, increments the chosen generation modulo 256 and marks the slot live. A full pool returns O=0,R=255,V=0,E=0. Query/release also return that ordinary failure for an inactive slot or a stale generation. Release keeps the stored generation; the next allocation advances it before returning a handle.
A valid query/release requires integral S=0..3 and T=0..255. E=1 rejects an invalid live mask or generation state. E=2 rejects an invalid mode or needed handle inputs. Invalid requests preserve A/G and return O=0,R=255,V=0. Check O before using R. Generation zero is a valid lifetime after wrap, not a free-slot marker; A determines whether the slot is live.
The reusable routine
Reusable numbered routines
3000 LET A=0: LET M=0: LET S=0: LET T=0: LET O=0: LET R=255: LET V=0: LET E=0
3010 FOR I=1 TO 4: LET G(I)=0: NEXT I: RETURN
3100 LET O=0: LET R=255: LET V=0: LET E=1
3110 IF A<0 OR A>15 OR A<>INT (A) THEN RETURN
3120 FOR I=1 TO 4
3130 IF G(I)<0 OR G(I)>255 OR G(I)<>INT (G(I)) THEN RETURN
3140 NEXT I: LET E=2
3150 IF M<0 OR M>2 OR M<>INT (M) THEN RETURN
3160 IF M=0 THEN LET E=0: GOTO 3300
3170 IF S<0 OR S>3 OR S<>INT (S) THEN RETURN
3180 IF T<0 OR T>255 OR T<>INT (T) THEN RETURN
3190 LET E=0: LET U=2^S: LET K=INT (A/U)-2*INT (A/(2*U))
3200 IF K=0 THEN RETURN
3210 IF G(S+1)<>T THEN RETURN
3220 LET O=1: LET R=S: LET V=T
3230 IF M=1 THEN LET A=A-U
3240 RETURN
3300 FOR I=0 TO 3: LET U=2^I: LET K=INT (A/U)-2*INT (A/(2*U))
3310 IF K=1 THEN GOTO 3370
3320 LET G(I+1)=G(I+1)+1
3330 IF G(I+1)=256 THEN LET G(I+1)=0
3340 LET A=A+U: LET O=1: LET R=I: LET V=G(I+1): RETURN
3370 NEXT I: RETURNA handle has two parts: where storage lives and which lifetime currently owns it. Matching the slot alone loses that distinction. Keep both returned R and V wherever the game stores a reference.
Reuse the same slot
The working caller allocates, queries and releases a handle, then allocates again. It checks the old handle and the new one separately. The custom caller starts just before generation wrap: its first allocation returns 255 and reusing the released slot returns zero. The diagnostic table also covers full pools, stale/inactive requests, wrap, different slots and invalid inputs.
Example calls from the maintained caller
90 GOSUB 3000
100 GOSUB 3100: GOSUB 2000: LET SS=R: LET GG=V
110 LET M=2: LET S=SS: LET T=GG: GOSUB 3100: GOSUB 2000: LET AA=O
120 LET M=1: LET S=SS: LET T=GG: GOSUB 3100: GOSUB 2000: LET BB=O
130 LET M=0: GOSUB 3100: GOSUB 2000: LET RR=R: LET VV=V
140 LET M=2: LET S=SS: LET T=GG: GOSUB 3100: GOSUB 2000
150 LET M=2: LET S=RR: LET T=VV: GOSUB 3100: GOSUB 2000
160 PRINT "LIFE RESULTS";AA;BB
170 ?(MB+995)=AA: ?(MB+996)=BB: ?(MB+997)=GD: ?(MB+998)=WC-256*INT (WC/256): ?(MB+999)=INT (WC/256): ?(MB+1000)=239: STOPLine 2000, mailbox writes and AA/BB/SS/GG/RR/VV/JJ/KK (Z/Q for loop controls on Spectrum) are diagnostic machinery. A game can call 3100 directly. The custom generation setup exercises a boundary; ordinary new sessions start through 3000.
Ownership and customisation
The helper owns identity bookkeeping, not object payload, type, rendering or resource cleanup. After allocating, initialise the game’s complete payload before another system reads it. After release, payload storage may still contain old values. It is not a newly constructed object merely because the slot becomes available.
Separate pools need separate live masks, generation arrays, globals, scratch and numbered references. A handle from one pool has no built-in pool/type tag. Give mutations one owner; there is no interrupt exclusion or thread-safety mechanism here.
Four slots are fixed. More slots need wider live-state handling or another representation plus revised bounds/tests. Wider generations postpone wrap but add state and comparison work. A one-byte generation can repeat after 256 allocations of the same slot, allowing a sufficiently old handle to alias again. This is bounded protection, not eternal uniqueness. A full reset can cause aliasing sooner if external references survive it.
Mistake and repair
The wrong caller discards the returned generation and supplies zero for its first query and release. Neither matches the actual lifetime, so both saved checks print zero. The supposedly released slot remains live, changing which slot a later allocation can use.
The repair stores and passes both parts of the handle. Repaired source and loaded tokenised program match the working caller exactly.

Cost and further steps
Allocation scans up to four live bits; query/release calculate one bit after state validation. This readable version validates all four generation values first. Four BASIC numeric elements and a numeric mask use more storage than five packed bytes. There is no measured cycle or whole-game performance claim.
Stable slot handles suit storage that stays in place. Compacting object records changes slot indices and needs an explicit identity/remapping policy; these two techniques do not combine automatically. Maths for Games connects sets, modular counters and wraparound. The Craft supplies the game’s ownership/lifetime rules that the handle only helps enforce.
Machine and measured size
BBC BASIC arrays include element zero; this one-based array 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 656 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/KK 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-entities.py generation-handles bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-entities.py generation-handles.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.