You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Ultimate Basic v1.5.5 – Release Notes
2026-08-26 – Magic Desk CRT export
Added .crt output support: when the output filename ends in .crt, the
compiler now writes a Commodore 64 cartridge image instead of a raw PRG.
The cartridge uses the same banked Magic Desk type-19 layout as
VisualAssembler: 64-byte header, bank 0 boot loader, and the compiled PRG
payload copied into RAM on startup.
Existing .prg and .d64 output paths are unchanged.
Added integration tests covering the CRT header, bank layout, payload copy,
and boot stub.
2026-08-18 – Struct types (type ... endtype)
New user-defined struct types with type NAME ... endtype blocks. Fields
are declared inside as var name: int, var name: word, or var name: float.
Element size is the sum of field widths (no padding).
type TEntity
var x: int
var y: int
var hp: word # 1 + 1 + 2 = 4 bytes per element
endtype
Array-of-struct declarations look like regular arrays:
var enemies: TEntity = array(N) ; allocates N * elem_size bytes at $C000+
Field access on both sides of assignment:
enemies[0].x = 5 ; constant idx → STA absolute
enemies[i].hp = enemies[i].hp + 1 ; variable idx → shift-add mul + (ptr),Y
Constant-index writes/reads fold to bare LDA/STA absolute. Variable-index
access emits a small shift-and-add multiplier for idx * elem_size (uses
ASL A chains for power-of-two sizes, or a shift-add sequence for arbitrary
sizes via two scratch zero-page bytes), then indexed (ptr),Y access.
Word and float fields write both bytes (little-endian): STA addr / STA addr+1
for constant idx, STA (ptr),Y ; INY ; STA (ptr),Y for variable idx.
Float fields use Q8.8 fixed-point format (hi byte = integer, lo byte =
fraction × 256). Reading a float field into a float variable works via
gen_word_assign; printing a float struct field uses print_fixed ("N.DD").
The parser resolves arr[i].field into Stmt::StructSet and Expr::StructGet
nodes that carry (field_offset, field_kind, elem_size) — codegen is
self-contained and needs no type table lookup.
Not yet implemented in this release: struct-typed sub/fn parameters,
default field values (parsed for compatibility with the proposal but not
initialized at allocation time), nested struct fields, multi-dimensional
struct arrays, string fields. Errors reported: struct-typed var without
array(N) initializer, and unknown-field access.
Added examples/type_demo.ub and CPU-run integration tests for constant
and variable index, int, word, and float field kinds, and the unknown-field
error.
2026-08-18 – Count-down for-loop fix
for / loop with a negative constant step (e.g. for i = 20 to 0 step -2)
used to compile but never enter the body, and — for values that wrap past 0 —
loop forever. The exit test was unsigned-only, so counting down from 20 to 0
kept re-entering after the 254 (= -2 mod 256) wrap-around.
The compiler now inspects the sign of a constant step at code generation:
positive step or default +1 → BCC/BEQ to body, unchanged (exit when var > to)
negative constant step → BCS to body (exit when var < to), plus a
post-increment BCS loop_top ; JMP exit that catches the ADC underflow
(carry clear) when var wraps below 0. This is what keeps for i = N to 0 step -k finite.
A default-step for/loop where from > to (e.g. loop i = 10 to 1, no step) used to silently skip the body — 0 iterations, no diagnostic. It is
now a compile-time error:
"for-loop: from (10) > to (1) with default step +1 loops 0 times
— use 'step -1' to count down"
Non-constant step (variable or expression) is still treated as positive at
compile time. Use a while loop when you need count-down with a runtime step.
Added examples/countdown_demo.ub (working forms) and examples/countdown_errors_demo.ub (the compile-time error form).
Added integration tests: a codegen check that count-down loops emit BCS
for the body branch, a check that count-up loops still emit BCC + BEQ, should_panic coverage of the from>to default-step error, and CPU-run tests
that execute the loop and verify it terminates with the expected iteration count.
2026-08-18 – --explicit CLI flag
Added the --explicit build flag:
ub build myprog.ub --explicit
With the flag, every var, sub-parameter, and fn-parameter declaration
must carry a :type annotation. Untyped declarations become compile-time
errors:
line 12: 'explicit' mode: 'var loose' has no type
— use 'var loose: int|word|float|string'
line 14: 'explicit' mode: parameter 'a' has no type
— use 'a: int|word|float|string'
Array declarations (array(N), array_word(N)) and const NAME = value
are unaffected — their type is fixed by the declaration form.
The flag is CLI-only; no source-level directive keyword. Existing programs
compile unchanged without the flag. Add it to a Makefile / build script to
enforce typed style project-wide.
Added examples/explicit_demo.ub (annotated code that builds under the flag)
and examples/explicit_errors_demo.ub (loose code that builds without the
flag but reports errors with it).
Added integration tests covering the typed-ok case, untyped var / sub-param /
fn-param errors, and the without-flag regression.