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. Ultimate Basic v1.5.3 – Release Notes ====================================== 2026-08-18 – Multi-dimensional arrays -------------------------------------------------- - Arrays can now be declared with more than one dimension using a comma-separated dimension list, for both byte and word arrays: var grid = array(8, 8) # 8x8 = 64 bytes var wm = array_word(4, 4) # 4x4 word elements = 32 bytes - Elements are indexed with a comma-separated subscript list and stored row-major; the flat offset is base + i0*(d1*..) + i1*(d2*..) + ... : grid[row, col] = 42 var v = grid[row, col] - Any number of dimensions is supported. Dimensions must be compile-time constants (integer literals or `const` values). - When every subscript is a constant the address is folded at compile time to a single absolute store/load; a variable subscript emits the row*stride + col computation followed by an indexed (ptr),Y access. - A single subscript into a multi-dimensional array is still allowed and is treated as a flat/linear index (e.g. grid[10]). - Indexing with a subscript count that is neither 1 nor the declared rank is reported as a compile-time error. - Implemented entirely in the parser (multi-index folds to one flat index expression), so code generation is unchanged. - Added examples/array2d_demo.ub and integration tests covering constant and variable indices, row-major layout, word-element stride, linear access, const-derived dimensions, and the wrong-rank error. 2026-08-18 – PETSCII bracket encoding fix -------------------------------------------------- - Fixed string encoding of the '[' , ']' and '^' characters, which previously fell through to the unknown-character placeholder and printed as '?'. - They now emit their correct C64 codes ($5B, $5D, $5E). These code points are identical in the uppercase/graphics and lowercase charsets, so the fix is correct in both `uppercase` and `lowercase` modes. - Added a regression test for bracket encoding in both charset modes. Ultimate Basic v1.5.2 – Release Notes ====================================== 2026-08-15 – Readable assembly codegen listings -------------------------------------------------- - Added the `--asm` build option: ub build demo.ub --asm A successful build writes `demo.asm` next to the selected `.prg` output. - The listing is generated from compiler metadata rather than being only a bare disassembly of the completed PRG. - UB statement boundaries are retained as comments alongside their emitted code. - Zero-page variables, arrays, subroutines, BASIC labels, relative branches, and in-program JMP/JSR targets receive readable symbols or generated labels. - Each instruction includes its final C64 address and exact machine-code bytes. - Compiler-generated helpers are included in final memory order, while unknown opcodes are preserved as `.byte` directives. - Helper entry points now use descriptive `ub_helper_*` labels. - Known data regions (DATA, maps, sprite/character definitions, lookup tables, incbins, SID and Koala payloads) are emitted as named `.byte` blocks instead of being disassembled as instructions; long zero gaps use `.fill`. - Output uses reassemblable KickAssembler syntax, including `.label` constants and a valid `* = origin` program counter directive. - Added automated listing tests and full README.md, MANUAL.md, CLI-help, and developer-guide documentation. Ultimate Basic v1.5.1 – Release Notes ====================================== 2026-08-15 – Koala Painter image import -------------------------------------------------- - Added compile-time loading of standard and raw Koala Painter images: koala load "picture.kla" koala show koala hide - Added file, memory-layout, and SID-conflict diagnostics plus emulation-backed tests and full manual documentation. 2026-08-15 – Character tile maps -------------------------------------------------- - Added compile-time loading and validation of self-describing `.ubmap` files: map load "level.ubmap" VisualAssembler `me-map` `.bin` exports are also accepted directly, without an `.ubmap` conversion step. - Added 40x25 viewport rendering from arbitrary map origins: map draw map_x, map_y Character codes go to $0400 and optional per-cell colors go to $D800. - Added writable runtime access with `map_tile()`, `map_color()`, `map set`, and `map color`, allowing game logic and destructible maps without reloading data. - Added normal and multicolor character-map modes. Multicolor maps configure $D016 MCM and the three shared colors in $D021-$D023. - Documented the byte-exact UBMP v1 format, flags, row-major arrays, viewport constraints, and charset relationship in MANUAL.md and CLAUDE.md. - Added emulation-backed tests for normal/multicolor setup, screen and color-RAM rendering, tile/color mutation and queries, and invalid-file diagnostics. 2026-08-12 – Debug symbol exports -------------------------------------------------- - Added side-effect-free software bounding-box collision detection: var hit = box_hit(l1, t1, r1, b1, l2, t2, r2, b2) It returns 1 for overlapping or edge-touching inclusive AABBs and 0 otherwise, without reading or clearing the VIC-II $D01E/$D01F collision registers. - Extended `sprite_frame` with an optional animation frame expression: sprite_frame id, base_addr, frame Consecutive frames occupy 64-byte slots; the original two-argument syntax is unchanged and remains backward compatible. - Added emulation-backed integration tests for overlap, separation, touching edges, and runtime animation-frame pointer selection. - Added the `--debug` build option: ub build demo.ub --debug A successful build now writes three debugger files next to the `.prg`, using the output file's stem. - Added KickAssembler-compatible `.sym` export. It can be imported by assembler source and contains resolved symbols in `.label name = $address` form. - Added C64Debugger/RetroDebugger `.dbg` export in KickAssembler debug-dump XML format. It contains the UltimateBasic program segment, source filename, and resolved address labels. - Added VICE `.vs` monitor-command export. It contains `al C:address .label` commands and can be loaded with VICE's `-moncommands` option or `ll` command. - Debug exports include `program_start`, `program_end`, zero-page variables, arrays, subroutines, and BASIC labels. - BASIC labels are now retained with their final machine-code addresses in the compiler memory map, making them available to all debug exporters. - Symbol names are sanitized for debugger compatibility, and source paths are XML-escaped in `.dbg` output. - Added unit coverage for all three symbol syntaxes and documented the new CLI option and file formats in README.md, MANUAL.md, and CLAUDE.md. - Current limitation: `.dbg` provides segments and address symbols, but does not yet contain instruction-to-source-line mappings for source-level stepping. Ultimate Basic v1.5.0 – Release Notes ====================================== 2026-06-25 – Double buffering + full-width X -------------------------------------------------- - **Double-buffered hires bitmap** for flicker-free animation: graphics on double # two complete bitmaps; drawing goes to the hidden one gcls # clears the hidden back buffer ... draw a frame ... # plot/line/circle/rect/paint all hit the back buffer flip # show the drawn buffer, draw into the other one Buffer A = bitmap $2000 / matrix $0400 (VIC bank 0); buffer B = bitmap $6000 / matrix $4400 (VIC bank 1). Only $DD00 selects which is shown ($D018 stays $18 for both). `flip` waits for the lower border (raster >= 251) before switching the VIC bank, so the swap is tear-free, then redirects drawing to the now-hidden buffer. - All pixel commands and `gcls` now write through a runtime "draw base" (two zero-page bytes: bitmap hi + matrix hi), initialised to the single-buffer layout ($2000/$0400) at program start. Single-buffer programs are byte-for-byte unaffected in behaviour; `flip` simply repoints the base at the hidden buffer. - Requirements: hires only (not multicolor); the back buffer uses $4000-$7FFF, so program code must stay below $4400; not combinable with sprites across a flip. On a stock 1 MHz C64 the per-frame full gcls limits frame rate — raise `speed` on the Commodore 64 Ultimate for smooth high-rate animation. - **Full 320-pixel X coordinate range** for plot/line/circle/rect: X values above 255 are handled automatically (9th X bit added in the pixel helper), so drawing reaches the right edge of the screen. Use a `word` variable for X when it can exceed 255. - **rect / rect erase / rect xor** — rectangle outline (4 edges) in hires bitmap mode. - Fix: assigning an 8-bit expression to an existing `word` variable (e.g. `w = cos(a)` after `w = w * 70`) now clears the high byte. Previously a stale high byte survived and corrupted later 16-bit arithmetic. - New examples: examples/cube_demo.ub – tumbling 3D wireframe cube, double-buffered examples/wide_x_demo.ub – verifies plot/line/circle/rect past X=255 examples/reu_bitmap_demo.ub – full-width (0-319) random lines/circles + REU Ultimate Basic v1.4.4 – Release Notes ====================================== 2026-06-14 – Strict variables + mouse support -------------------------------------------------- - **BREAKING**: Strict variable checking — all variables must be declared with `var` before use. The parser now pre-scans all tokens (including included files) to collect `var` declarations, then reports an error for any undeclared variable reference in expressions, assignments, for/loop counters, inc/dec, input, and read statements. var x = 0 # REQUIRED before for-loop for x = 1 to 10 print x next print kukk # ERROR: variable 'kukk' is not declared Sub/fn parameters are automatically added to the declared set so they can be used inside the body. irq/nmi/cia_timer handler names are exempt (they reference subroutines, not variables). - 1351 proportional mouse support via mouse_x(), mouse_y(), mouse_btn() Built-in mouse_read_helper assembly subroutine handles: CIA1 $DC00 capacitor charging, LDX#$67 delay, SID POT X/Y read, signed 7-bit delta computation, Y-axis EOR#$FF inversion, accumulated position tracking, and dual-sample per frame. - mouse_btn() reads $DC01 (control port 1) — bit0=left, bit1=right. - mouse_x() returns accum_x via JSR to helper; mouse_y() reads accum_y directly (avoids double-update per frame). - Word var assignment (sx: word = mouse_x()) sets both lo/hi bytes for proper $D010 MSB handling by the sprite command. - 9-bit X carry/borrow via accum_x_hi (INC/DEC on overflow). - Demo: examples/mouse_demo.ub — 1351 mouse pointer sprite with button feedback (left=red border, right=blue background). - Fixed: KERNAL IRQ touches $DC00 during keyboard scan → corrupts POT; demo disables CIA1 interrupts (poke $DC0D, $7F + asm { SEI }). - Fixed: helper RTS must return LDA accum_x (not leftover raw_y). - Fixed: mouse helper ZP zeroed before game loop (fill $02, 7, 0). - Fixed: helper init uses same charge_delay as do_delta for consistent POT readings (no initial delta jump). - Fixed: prev_raw only updated when delta != 0 (matches original). Ultimate Basic v1.4.3 – Release Notes ====================================== Recent development notes (2026-06-12) -------------------------------------------------- 2026-06-12 - Added `fn` keyword — functions with return values. fn square(x) → 8-bit return (result in A register) return x * x end fn get_ptr(): word → 16-bit return (result in fn_ret_zp pair) return $C000 end var s = square(9) # fn call as expression var ptr: word = get_ptr() # word return - Typed subroutine parameters are now preserved end-to-end: sub init_message(src:string) var c = src[i] # string indexing → LDA (ptr),Y end The parser, AST, and codegen all propagate string, int, word, float annotations on sub/fn parameters. - Unused variable reporting: the build output now lists variables that are declared but never read (tracked via mark_used across all variable read paths — eval_expr, print, inc/dec, array get/set, string params). - New example: examples/function_demo.ub — demonstrates fn with multiple return paths, word params, and expression-context calls. Ultimate Basic v1.4.2 – Release Notes ====================================== Recent development notes (2026-05-30 / 2026-05-31) -------------------------------------------------- 2026-05-31 - Added parallax starfield example programs: examples/parallax.ub examples/parallax_demo.ub examples/text_scroll_demo.ub - Added smooth text-scroller demo using hardware horizontal fine scroll. - Added new scroll helpers for pixel-scrolling examples: scroll x n narrow (set x fine-scroll and force 38-column mode) scroll x n wide (set x fine-scroll and restore 40-column mode) scroll row R left (shift one screen RAM row left by one character) - Reworked text-scroll example to use UltimateBasic commands instead of inline asm for 38/40-column mode and row shifting. - Clarified documentation: semicolon is not a BASIC comment marker; it is an asm-source comment marker inside asm { } blocks. BASIC comments are # and rem. - Added focused tests for scroll x narrow/wide, scroll row left, and semicolon handling. 2026-05-30 - Added graphics on block: 80x50 chunky block-pixel mode using a custom 4-pixel charset at $2800, with no bitmap RAM required. - Added plot4 x, y and plot4 erase x, y for setting and clearing block pixels. - Added circle4 x, y, r for midpoint circle outlines in 80x50 block-pixel mode. - Fixed gcls in block mode so it clears all screen/color RAM pages correctly; the old descending DEX/BPL loop failed for counts >= 128. - Added lowercase and uppercase commands for switching C64 charset mode via CHROUT. - Updated TENPRINT example to demonstrate lowercase charset mode. - Removed temporary test/demo files from tmp/. New language features --------------------- Variables & types - float type (Q8.8 fixed-point): var f: float = 3.5 / int(f) / print f ("N.DD") - array_word(N): 16-bit word arrays at $C000+ - Compound assignments: and= or= xor= shl= shr= - Full 16-bit arithmetic on word vars: * / mod shl shr and or xor - inc/dec carry-aware for word variables Control flow - for/next with optional step and continue - repeat/until (do-while) - select/case/else - gosub label / return - continue / break in all loop types Print & formatting - spc(n), tab(n) in print - print at col, row, ... (no trailing newline) - hex(n), bin(n), dec(n, width) - numstr var, addr (8-bit int → 3-digit decimal string) Math & string functions - sin(angle), cos(angle) (0–255 lookup table) - rnd(n) (rnd() mod n) - min, max, abs, sgn - val(s) (decimal string → 8-bit int) - s[i] (string character by index) Screen & display - screen col, row, char [, color] (direct screen/color RAM write) - display on / display off (VIC DEN bit, independent of mode) - scroll x n / scroll y n (hardware fine-scroll registers) - scroll x n narrow / scroll x n wide (fine scroll + 38/40-column mode control) - scroll row R left (single screen-row character shift for smooth scrollers) - lowercase / uppercase (switch C64 charset mode) - cursor col, row Bitmap graphics - graphics on multi (multicolor bitmap 160×200, 4 colours/cell) - mplot x, y, color (multicolor pixel; requires graphics on multi) - plot erase, plot xor, line, paint Block graphics (80×50) - graphics on block (chunky 80×50 mode; custom 4-pixel charset @ $2800; no bitmap RAM) - plot4 x, y / plot4 erase x, y (set/clear a block pixel, x: 0-79, y: 0-49) - circle4 x, y, r (draw clipped midpoint circle outlines in block-pixel coordinates) - gcls clears screen RAM $0400-$07FF + color RAM $D800-$DBFF in block mode - Fix: gcls block-mode no longer leaves the bottom ~6 rows uncleared (the old descending LDX #231/DEX/BPL loop ran only once for counts ≥128; replaced with forward INX/BNE page loops) Sprites & charset - sprite_frame id, addr (update data pointer at runtime) - sprdef id...end (inline 63-byte shape + auto pointer; binary literals) - sprite multicolor / expand x/y / priority - charset base / chardef id...end (custom character definitions) SID & music - sid volume N / sid stop - load sid "file.sid" [, addr] (compile-time embed; defines sid_init/sid_play) - music play [N] (CIA1 timer A 50 Hz IRQ + wrapper; auto-installs handler) - music stop / pause / resume Interrupts & timing - nmi handler / nmi_exit - cia_timer period, handler - irq_exit (JMP $EA81) - delay N (N PAL frames) - wait raster N Error handling - onerr goto label (KERNAL I/O error vector $0300/$0301; forward refs OK) Memory & I/O - peek16 / poke16 (16-bit little-endian) - fill, memcopy, drawmem - reu stash / fetch / swap + reu_present() - load / save / open / close / print# / input - data / read Ultimate 64 - speed N / speed max / speed off - badlines on / off - turbo() CLI --- - Binary renamed from ultimate-basic to ub - --d64 [file] produce .d64 disk image - --add add extra files to .d64 (repeatable) - -v / --verbose zero-page layout + hex dump