A character should be able to stop halfway up a ladder without immediately falling. Climbing is retained state, rather than something reconstructed only from the currently held up/down controls.

Include and initialise
Reserve public M/Y/G/J/O/L/U/D/B/A/H, private T and lines 3000–3300. There are no arrays. Combine routines.bas and the caller with build.py, keeping END, STOP or a jump before the helper. Use GOSUB on C64/BBC or GO SUB on Spectrum. Call 3000 to clear public fields, then supply the character’s actual position and ladder limits.
M is retained mode: zero outside climbing, one climbing. Y is retained vertical position, increasing downwards. L reports current ladder presence. U/D/B are up, down and jump requests; zero is false and any nonzero byte true. A/H are inclusive vertical bounds. State and inputs must be integral bytes, M must be zero or one and Y must lie within A/H.
Update one decision
Call 3100. O=1 means valid data. Outside the ladder, the helper exits climbing and returns G=1 for gravity. On a ladder, an existing climbing mode or either direction request enters/keeps climbing. Without a jump request, it advances one unit down, one up or zero when both are held, clamping Y to A/H; G=0 suppresses gravity.
Reusable numbered routines
3000 LET M=0: LET Y=0: LET G=0: LET J=0: LET O=0: LET L=0: LET U=0: LET D=0: LET B=0: LET A=0: LET H=0: RETURN
3100 LET G=0: LET J=0: LET O=0
3110 IF M<0 OR M>1 OR M<>INT (M) THEN RETURN
3120 IF Y<0 OR Y>255 OR Y<>INT (Y) THEN RETURN
3130 IF L<0 OR L>255 OR L<>INT (L) THEN RETURN
3140 IF U<0 OR U>255 OR U<>INT (U) THEN RETURN
3150 IF D<0 OR D>255 OR D<>INT (D) THEN RETURN
3160 IF B<0 OR B>255 OR B<>INT (B) THEN RETURN
3170 IF A<0 OR A>255 OR A<>INT (A) THEN RETURN
3180 IF H<0 OR H>255 OR H<>INT (H) THEN RETURN
3190 IF A>H OR Y<A OR Y>H THEN RETURN
3200 LET O=1
3210 IF L=0 THEN LET M=0: LET G=1: RETURN
3220 IF M=0 AND U=0 AND D=0 THEN LET G=1: RETURN
3230 LET M=1
3240 IF B<>0 THEN LET M=0: LET J=1: LET G=1: RETURN
3250 LET T=Y
3260 IF U<>0 THEN LET T=T-1
3270 IF D<>0 THEN LET T=T+1
3280 IF T<A THEN LET T=A
3290 IF T>H THEN LET T=H
3300 LET Y=T: RETURNWithin that active climbing branch, a jump request exits M=0, returns J=1 and G=1, without moving Y. Merely standing on a ladder in mode zero with no direction request remains outside climbing, even if jump is requested. Invalid input clears G/J/O while preserving retained M/Y and all inputs.
Stop without falling
The normal caller starts at Y=10 and requests up, entering M=1 at nine. The custom caller starts at twenty and requests down, reaching twenty-one. Both then release the directional requests. M remains one and gravity stays disabled, which the saved second check verifies.
Example calls from the maintained caller
70 GOSUB 3000: LET AA=0: LET BB=0
80 LET Y=10: LET L=1: LET U=1: LET D=0: LET A=0: LET H=30
90 GOSUB 3100: GOSUB 2000
100 IF Y=9 THEN LET AA=1
110 LET U=0: LET D=0
120 GOSUB 3100: GOSUB 2000
130 IF M=1 AND G=0 THEN LET BB=1
140 LET B=1
150 GOSUB 3100: GOSUB 2000
160 LET B=0: LET U=1
170 GOSUB 3100: GOSUB 2000
180 LET L=0
190 GOSUB 3100: GOSUB 2000
200 PRINT "LADR 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 later jump request releases climbing; a subsequent directional request can enter again, and leaving ladder presence exits. AA/BB/JJ (Z on Spectrum), line 2000 and the mailbox are diagnostics. The reusable helper reads no keyboard and performs no map lookup.
Keep control and motion policies explicit
J=1 is a request for the game’s jump behaviour, not a velocity impulse. G=1 says gravity should be active; it does not integrate acceleration or move a falling actor. Determine ladder presence and suitable bounds elsewhere, then consume the outputs in a deliberate update order.
This interface accepts level requests. If holding jump must cause only one release, supply an edge-detected request or another chosen input policy. Both up/down can enter climbing but cancel movement. Faster climbing, animation phases, ladder snapping and dismount platforms need explicit extensions and tests rather than hidden side effects.
Mistake and repair
The wrong caller clears L before every update. The controller correctly sees no ladder, so it neither climbs nor retains the intended mode. Both saved position and retained-mode checks fail. Preserve the map-derived ladder result until the controller has used it. Repaired source and loaded program match normal exactly.

Cost and Maths for Games
One update performs bounded validation, state transitions, a one-unit displacement and optional clamps. It owns no timer, input device or display state. Retained mode adds a small amount of logical state but prevents the control policy from collapsing into a set of disconnected key checks.
Maths for Games connects bounded displacement and state transitions. The Craft connects held input, mode ownership and deliberate jumping/dismount rules.
Global names and line numbers
BASIC numeric names, arrays and numbered lines are global. To combine helpers, rename public and private fields and renumber the helper together with every GO SUB/GO TO reference. Keep each state block under one owner. The maintained build.py rejects duplicate line numbers; it does not automatically namespace or renumber routines.
Machine and measured size
C64 BASIC V2 recognises only the first two significant characters of numeric variable names. Short names make the ownership visible; check for collisions before including another component. This helper uses scalar numeric fields and allocates no arrays. The diagnostic uses a mailbox at $C000, outside the normal BASIC heap.
The numbered helper occupies 613 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 and RAM mailbox are observation machinery, not required game state.
Execution evidence
Working, custom, deliberately wrong and exact repaired callers executed through C64’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-traversal.py ladder-controller commodore-64 --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-traversal.py ladder-controller.
Explore further
Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.