Tidepod Suite v1.0.0-rc5 Updated October 2026

Tidepod Documentation & Developer Guide

The comprehensive manual for building TI-99/4A homebrew games, tools, and FPGA GPU applications in C and Assembly.

← Back to IDE Overview Download PDF Manual (4.6 MB)
Docs / Getting Started / System Overview & Architecture

System Overview & Architecture

Core Architecture

Developing for the 1981 Texas Instruments TI-99/4A home computer in standard C has traditionally been a formidable challenge due to severe hardware realities: an 8KB banked cartridge ROM window, a 256-byte CPU scratchpad RAM, separate Video RAM (VRAM) accessed solely through multiplexed I/O ports, and a CPU architecture lacking hardware stack pointer instructions.

Tidepod is a complete, unified development ecosystem that completely eliminates this complexity. It pairs an intuitive desktop IDE and CLI with an automated linker and packing engine (tms9900-packer) and pre-bundled GCC cross-compiler toolchain.

The Automated Build Pipeline

Every Tidepod project is built around an orchestrated multi-stage pipeline that automates bank allocation and cross-bank calls:

Pipeline Flow Diagram
[Source Files (.c, .asm, .dat)]
        │
        ▼ (tidepod add / sync)
[Project Config (.project)] ───► [Generated Makefile]
                                      │
                                      ▼ (make / make debug)
                               [tms9900-gcc & binutils]
                                      │
                                      ▼ (.o translation units)
                               [tms9900-packer]
                                      ├── Automated 8KB Bank Slicing
                                      ├── Cross-Bank Call Trampolines
                                      ├── String (.rodata) Hoisting
                                      └── 3-Stage Bootstrap Ingestion
                                      │
                                      ▼
                        [Final ROM / .rpk Cartridge]
                                      │
                                      ▼ (tidepod run)
                         [Embedded ti994a-emu Core]
The Golden Rule: Never Edit the Generated Makefile

The project Makefile is synthesized automatically by Tidepod (# [GENERATED BY TI-99 IDE - DO NOT EDIT MANUALLY]). Never modify object lists, compiler flags, or trampolines by hand. Always register sources, binary assets, or compiler flags through the Tidepod CLI (tidepod add / remove) or project settings.

Memory Map & Safe Zones

Tidepod programs target the standard TI-99/4A memory map with the 32KB RAM expansion:

Address Range Region Tidepod Usage
0x0000 – 0x1FFF Console ROM TI-99 system BIOS, GPL interpreter, sound tables.
0x2000 – 0x3FFF Lower 8K Expansion RAM .persistent section: C runtime stubs, cross-bank trampolines, hot code.
0x4000 – 0x5FFF DSR Device ROM/RAM Peripheral expansion ROMs (Floppy Disk DSR, HFDC, RS232, SCSI).
0x6000 – 0x7FFF Cartridge ROM Window 8KB banked window. Switched by writing to 0x6000 + (bank * 2).
0x8300 – 0x83FF 16-Bit Fast Scratchpad RAM CPU workspace registers (WP=0x8300) and .scratchpad routines.
0x8800 / 0x8C00 VDP Memory-Mapped Ports Read data, Status register, Write data, VDP address registers.
0xA000 – 0xFFFF Upper 24K Expansion RAM .data, .bss, dynamic heap (libmalloc99), and C workspace stack.

The 3-Stage Bootstrap Architecture

When power is turned on, the TI console starts execution in Bank 0 ROM with interrupts disabled. Tidepod manages the transition to C via a safe 3-stage bootloader:

  1. Stage 1 (Bank 0 ROM): _start locks at 0x6000, disables interrupts, and copies an ephemeral bootstrap copier into fast scratchpad RAM at 0x8342.
  2. Stage 2 (Scratchpad Copier): Running safely in scratchpad, the copier relocates .persistent trampolines into lower RAM (0x2000), unpacks initialized global variables (.data) into upper RAM (0xA000), and copies user scratchpad functions.
  3. Stage 3 (Expansion RAM Initialization): _persistent_init zeroes out .bss, sets up the C stack pointer in high RAM, enables interrupts, and jumps to your C main() function.
Docs / Getting Started / Installation & Release Packages

Installation Guide

Pre-built Binaries

Tidepod follows a zero-setup philosophy. Every official release bundle comes pre-packaged with the complete tms9900-gcc compiler, assembler, linker, runtime C libraries, man pages, and the ti994a-emu emulator. You do not need to compile or configure a cross-compiler toolchain.

macOS Installation

Download the native Apple Silicon installer: tidepod-1.0.0-rc5-macos-arm64.dmg.

  1. Double-click the .dmg file to mount it.
  2. Drag Tidepod.app into your /Applications folder.
  3. To enable the tidepod CLI globally in your terminal, add it to your PATH:
    ~/.zshrc
    export PATH="/Applications/Tidepod.app/Contents/MacOS:$PATH"

Linux Installation (Ubuntu / Debian)

Download the .deb package for your architecture (amd64 or arm64):

Terminal
sudo dpkg -i tidepod-1.0.0-rc5-linux-amd64.deb
sudo apt-get install -f   # Resolves any missing system OpenGL/X11 libraries

The tidepod executable and toolchain are installed into /opt/tidepod/ and symlinked directly to /usr/local/bin/tidepod.

Windows Installation

Download the Windows setup installer: tidepod-1.0.0-rc5-windows-x86_64-setup.exe.

  • Run the installer and follow the on-screen wizard.
  • Alternatively, download the portable .zip archive and run tidepod.exe directly. It includes a self-contained MSYS2/w64devkit environment.
Docs / Tutorials / Tutorial 1: Minimal 8K Cartridge

Tutorial 1: Minimal 8K C Cartridge

Beginner Walkthrough
Draft — being rewritten

This tutorial's code has not yet been verified against the current Tidepod toolchain and contains API calls that do not exist in libc. Use the API reference and the examples shipped in resources/examples/ until this page is updated.

In this tutorial, we will scaffold, write, compile, and run a minimal standard 8K ROM cartridge in C. We will walk through every single line of code to explain how the TMS9900 16-bit microprocessor and the TMS9918A Video Display Processor (VDP) interact at the register level.

Hardware Architecture & Memory Map

An 8K non-banked cartridge occupies the CPU address window 0x6000–0x7FFF (8,192 bytes). The TI-99/4A console ROM checks this memory range at power-on for a standard cartridge header (containing the string "HELLO CART", entrypoint vectors, and menu item index).

Memory Address Bus Access Function & Description
0x6000 – 0x7FFF 16-bit Read-Only Cartridge ROM space. Contains cartridge header, startup bootstrap, and compiled C code.
0x8300 – 0x83FF 16-bit Read/Write 256 bytes of internal CPU zero-wait-state Scratchpad RAM. Holds active CPU register workspaces (R0–R15).
0x8800 / 0x8C00 8-bit Read / Write VDP Data Port. Reads and writes VRAM byte streams into the TMS9918A.
0x8802 / 0x8C02 8-bit Read / Write VDP Control Port. Read queries the Status Register (VDPST); write sets the 14-bit VRAM address pointer or configures VDP Registers 0–7.

Step 1: Create the Project via CLI

Scaffold a clean workspace targeting standard 8K cartridge geometry. This automatically writes the project manifest (HelloCart.project), source boilerplate, and builds the Makefile:

Terminal
tidepod new HelloCart --type 8k --menu "HELLO CART" --path ./HelloCart
cd HelloCart

Step 2: Complete Code Listing (`src/main.c`)

Replace the contents of src/main.c with this complete, self-contained demonstration:

src/main.c
 1: #include <stdio.h>
 2: #include <conio.h>
 3: 
 4: int main(void) {
 5:     // Initialize 40-column hardware text mode
 6:     ti_text_mode(40);
 7: 
 8:     // Set screen border color to Dark Blue (palette index 4)
 9:     bordercolor(4);
10: 
11:     // Clear screen and position cursor at column 4, row 3
12:     clrscr();
13:     gotoxy(4, 3);
14:     puts("HELLO FROM C ON TI-99/4A!");
15: 
16:     // Display toolchain banner
17:     gotoxy(4, 5);
18:     puts("BUILT WITH TIDEPOD C99 SUITE");
19: 
20:     // Display interactive prompt
21:     gotoxy(4, 8);
22:     puts("PRESS ANY KEY TO CONTINUE...");
23: 
24:     // Scan keyboard matrix and wait for user keypress
25:     int key = cgetc();
26: 
27:     // Output confirmation with ASCII character value
28:     gotoxy(4, 11);
29:     printf("KEY DETECTED: ASCII %d ('%c')
", key, (char)key);
30: 
31:     gotoxy(4, 13);
32:     puts("SYSTEM READY. HALTING IN IDLE LOOP.");
33: 
34:     // Bare-metal halt loop (prevents crash on OS return)
35:     while (1) {
36:     }
37: 
38:     return 0;
39: }

Line-by-Line Code Breakdown

Here is the exhaustive line-by-line explanation detailing every operation executed by the TMS9900 CPU and 9918A VDP:

Lines 1–2
#include <stdio.h> & #include <conio.h>: Ingests standard C formatted output declarations (printf) and Tidepod's direct hardware console I/O library (conio.h). On the TI-99/4A, conio.h functions bypass heavy file-stream abstractions and talk directly to the VDP memory-mapped I/O ports at 0x8C00 and 0x8C02.
Line 4
int main(void): The application entrypoint. When the console boots, the TI ROM checks 0x6000, detects the cartridge header, and displays "HELLO CART" on the master menu. Selecting it jumps to the Tidepod C runtime bootstrap (_start), which initializes the TMS9900 Workspace Pointer (WP), sets up the R10 stack pointer in high RAM, clears .bss, copies initialized globals from ROM to RAM, and calls main().
Line 6
ti_text_mode(40);: Programs the TMS9918A Video Display Processor into Text Mode (40 columns by 24 rows, 960 total character tiles). This writes a 2-byte command sequence to VDP control port 0x8C02: clearing Bit 1 in Register 0 (mode select M2=0) and setting Bit 4 in Register 1 (mode select M1=1, screen unblanked). It also sets up the Pattern Name Table at VRAM 0x0000 and Character Pattern Generator at VRAM 0x0800.
Line 9
bordercolor(4);: Sets the screen backdrop and overscan border color. This writes palette index 4 (Dark Blue) to VDP Register 7 via control port 0x8C02.
Line 12
clrscr();: Clears the entire 40×24 display by streaming 960 ASCII space characters (byte 0x20) into VRAM address 0x0000 through VDP data port 0x8C00. It then resets the internal hardware cursor to (0, 0).
Lines 13–14
gotoxy(4, 3); puts("HELLO FROM C ON TI-99/4A!");: gotoxy(col, row) computes the VRAM offset using the formula address = 0x0000 + (row * 40) + col and transmits the 14-bit VRAM address to port 0x8C02. puts(...) then loops through each character of the string and writes it to port 0x8C00. Because the TMS9918A hardware auto-increments its internal address latch on each write, subsequent characters draw horizontally across the screen at maximum bus speed.
Lines 17–18
gotoxy(4, 5); puts("BUILT WITH TIDEPOD C99 SUITE");: Repurposes the VRAM address pointer to row 5, column 4 and writes the second header line.
Lines 21–22
gotoxy(4, 8); puts("PRESS ANY KEY TO CONTINUE...");: Renders the user prompt at row 8.
Line 25
int key = cgetc();: Executes a hardware keyboard scan routine. The TI-99/4A keyboard is not memory-mapped; it is connected to the TMS9901 Communications Register Unit (CRU) serial I/O chip. cgetc() pulses column select wires, reads back row pins, performs software switch debouncing, translates matrix scan codes into standard ASCII, and blocks CPU execution until a key is pressed.
Lines 28–29
gotoxy(4, 11); printf("KEY DETECTED: ASCII %d ('%c') ", key, (char)key);: Uses the lightweight Tidepod C99 printf formatter to format the integer ASCII code and corresponding glyph character into a scratch buffer and streams the characters into VRAM.
Lines 31–32
gotoxy(4, 13); puts("SYSTEM READY. HALTING IN IDLE LOOP.");: Prints confirmation that the program flow has concluded successfully.
Lines 35–36
while (1) {}: A fundamental bare-metal embedded rule: a vintage microcomputer running a cartridge has no operating system command prompt or shell to return to. If main() returned, the program counter (PC) would branch into unmapped memory, executing random garbage opcodes and crashing the console. The infinite loop safely keeps the CPU in a stable idle halt.
Lines 38–39
return 0; }: Closes the function definition for ISO C99 conformance.

Step 3: Compile and Test

Terminal
tidepod sync && make
tidepod run

tidepod run launches the native standalone emulator (ti994a-emu), mounts your generated cartridge image (HelloCart.rpk), and runs the program at real-time 3.0 MHz TMS9900 clock speed.

Docs / Tutorials / Tutorial 2: Sprites, Joystick & Sound

Tutorial 2: Sprites, Joystick & 3-Voice Sound

Intermediate Walkthrough
Draft — being rewritten

This tutorial's code has not yet been verified against the current Tidepod toolchain and contains API calls that do not exist in libc. Use the API reference and the examples shipped in resources/examples/ until this page is updated.

In this tutorial, we will build a complete interactive arcade loop targeting a Banked 512KB Cartridge. We will design custom hardware sprites, poll analog joysticks via CRU, synthesize audio waveforms on the SN76489 sound chip, and lock the animation to the 60Hz vertical blank interrupt.

Hardware Subsystems Explained

  • Hardware Sprites: The TMS9918A supports up to 32 simultaneous hardware sprites (indices 0 to 31). Each sprite's position, pattern glyph, and color are stored in a 4-byte record inside the Sprite Attribute Table (SAT at VRAM 0x0380).
  • PSG Sound Generation: The TI-99/4A features a Texas Instruments SN76489 Complex Sound Generator mapped to CPU write port 0x8400. It contains 3 independent square-wave tone channels and 1 periodic/white noise generator. Volume levels range from 0 (maximum loudness / 0 dB) to 30 (complete attenuation / silence).
  • VSYNC Synchronization: Reading the VDP Status Register (VDPST at 0x8802) checks the vertical blanking flag (Bit 7). Crucial rule: reading this port automatically resets the flag to zero. Tidepod's vsync() polls this register in a tight assembly loop, locking the game update rate to exactly 60 FPS (NTSC) or 50 FPS (PAL) and eliminating sprite flicker and tearing.

Step 1: Scaffold Banked Game

Terminal
tidepod new SpaceBlaster --type banked --size 512 --menu "SPACE BLASTER" --path ./SpaceBlaster
cd SpaceBlaster

Step 2: Complete Code Listing (`src/main.c`)

src/main.c
 1: #include <stdio.h>
 2: #include <conio.h>
 3: 
 4: // Tidepod Extended BASIC C runtime library bindings
 5: extern void pattern(int char_code, const char *hex_pattern);
 6: extern void sprite(int num, int char_code, int color, int r, int c, int rv, int cv);
 7: extern void locate(int num, int r, int c);
 8: extern void color(int char_set, int f_color, int b_color);
 9: extern void joyst(int unit, int *x, int *y);
10: extern void sound(int duration_ms, int freq, int vol);
11: extern void vsync(void);
12: 
13: int main(void) {
14:     // Player spaceship coordinates (pixels)
15:     int ship_x = 128;
16:     int ship_y = 96;
17:     int joy_x = 0;
18:     int joy_y = 0;
19: 
20:     // Enemy star coordinates and automatic velocity
21:     int enemy_x = 30;
22:     int enemy_y = 40;
23:     int enemy_vx = 2;
24:     int enemy_vy = 1;
25: 
26:     // Switch to Graphics I Mode (32x24 tiles with 32 hardware sprites)
27:     ti_text_mode(32);
28:     clrscr();
29: 
30:     // Define custom 8x8 sprite pattern 65 ('A') for Player Spaceship
31:     // Hex: 18 (..XX....), 3C (.XXXX...), 7E (XXXXXX..), DB (XX.XX.XX), FF (XXXXXXXX), 24 (..X..X..), 5A (.X.XX.X.), 42 (.X....X.)
32:     pattern(65, "183C7EDBFF245A42");
33: 
34:     // Define custom 8x8 sprite pattern 66 ('B') for Enemy Hazard Star
35:     pattern(66, "002418FF18240000");
36: 
37:     // Spawn Hardware Sprite #1: Player Ship (Cyan, 7) at (96, 128)
38:     sprite(1, 65, 7, ship_y, ship_x, 0, 0);
39: 
40:     // Spawn Hardware Sprite #2: Enemy Star (Dark Red, 6) at (40, 30)
41:     sprite(2, 66, 6, enemy_y, enemy_x, 0, 0);
42: 
43:     // Play startup fanfare on PSG: 440 Hz (A4) for 200 ms at full volume (0)
44:     sound(200, 440, 0);
45: 
46:     // 60 FPS Interactive Game Loop
47:     while (1) {
48:         // 1. Wait for VDP Vertical Blank interrupt (prevents visual tearing)
49:         vsync();
50: 
51:         // 2. Poll Joystick #1 (Wired to 9901 CRU)
52:         joyst(1, &joy_x, &joy_y);
53: 
54:         // 3. Update player position based on joystick deflection
55:         if (joy_x != 0 || joy_y != 0) {
56:             ship_x += (joy_x * 2);
57:             ship_y -= (joy_y * 2); // Invert Y because screen rows increase downwards
58: 
59:             // Clamp player to visible screen boundaries
60:             if (ship_x < 8) ship_x = 8;
61:             if (ship_x > 248) ship_x = 248;
62:             if (ship_y < 8) ship_y = 8;
63:             if (ship_y > 184) ship_y = 184;
64: 
65:             // Update Sprite #1 position in VRAM Attribute Table
66:             locate(1, ship_y, ship_x);
67:         }
68: 
69:         // 4. Update and bounce enemy hazard
70:         enemy_x += enemy_vx;
71:         enemy_y += enemy_vy;
72: 
73:         // Bounce off screen walls and play a bounce blip
74:         if (enemy_x <= 8 || enemy_x >= 248) {
75:             enemy_vx = -enemy_vx;
76:             sound(30, 880, 5); // Short high-pitch chirp (880 Hz, volume 5)
77:         }
78:         if (enemy_y <= 8 || enemy_y >= 184) {
79:             enemy_vy = -enemy_vy;
80:             sound(30, 660, 5); // Short mid-pitch chirp (660 Hz, volume 5)
81:         }
82: 
83:         // Update Sprite #2 position in VRAM
84:         locate(2, enemy_y, enemy_x);
85:     }
86: 
87:     return 0;
88: }

Line-by-Line Code Breakdown

Lines 1–2
#include <stdio.h> & #include <conio.h>: Includes basic console and runtime headers.
Lines 5–11
Extended BASIC Runtime Bindings: Declares the standard Tidepod C runtime functions matching TI Extended BASIC semantics. pattern sets sprite shape, sprite registers a hardware sprite in VRAM, locate repositions it, joyst scans hardware controllers, sound triggers PSG frequency generation, and vsync halts execution until the electron beam enters vertical blank.
Lines 14–18
Player State Variables: Allocates integers on the local stack frame for player coordinates (ship_x, ship_y) and joystick offsets (joy_x, joy_y). Scalar integer locals are safe and register-promoted by tms9900-gcc.
Lines 21–24
Enemy Hazard State Variables: Initializes position (30, 40) and per-frame delta velocity (vx=2, vy=1) for an automated bouncing hazard star.
Lines 27–28
ti_text_mode(32); clrscr();: Switches the VDP to Graphics Mode I (32 columns × 24 rows, 256×192 pixels). Unlike 40-column text mode, 32-column graphics mode enables hardware sprite rendering and individual tile color attributes.
Lines 32 & 35
pattern(65, "183C7EDBFF245A42"); pattern(66, ...);: Uploads 8 bytes (16 hex digits) of bitmap data to the Sprite Pattern Generator table in VRAM. Each hex pair corresponds to 8 horizontal pixels. 18 in binary is 00011000, creating the tip of the spaceship. Pattern 65 is assigned to character 'A' and Pattern 66 to 'B'.
Lines 38 & 41
sprite(1, 65, 7, ...); sprite(2, 66, 6, ...);: Writes entries into the VRAM Sprite Attribute Table (SAT at 0x0380). Sprite #1 is configured with pattern 65, color Cyan (index 7), and placed at pixel coordinates (96, 128). Sprite #2 is configured with pattern 66, Dark Red (index 6), at (40, 30). Velocities (0, 0) keep automatic hardware motion disabled so we control coordinates manually in C.
Line 44
sound(200, 440, 0);: Programs Channel 1 of the SN76489 sound chip mapped to 0x8400. It emits a 440 Hz square wave (musical pitch A4) for 200 milliseconds at full volume (0 dB attenuation).
Line 47
while (1) {: The main game loop. Runs indefinitely at a rock-solid 60 frames per second.
Line 49
vsync();: Polls the VDP Status Register (0x8802) until Bit 7 (VBLANK) becomes high. This synchronizes the loop with the TV refresh cycle, preventing video tearing and standardizing game speed across all hardware.
Line 52
joyst(1, &joy_x, &joy_y);: Queries Joystick #1 via the 9901 CRU bus. Fills joy_x and joy_y with deflection vectors: -4 (left / down), 0 (centered), or +4 (right / up).
Lines 55–67
Player Movement & Clamping: Adjusts ship_x and ship_y by the joystick vectors, clamps values within visible CRT display boundaries (8 to 248 for X, 8 to 184 for Y), and calls locate(1, ship_y, ship_x) to update Sprite #1's coordinate bytes directly in VRAM SAT.
Lines 70–82
Hazard Physics & Sound Effects: Advances enemy position by its velocity. When the star strikes any screen edge, its velocity is negated (enemy_vx = -enemy_vx) and sound(30, freq, 5) fires a 30ms high-pitch audio blip (880 Hz or 660 Hz at moderate volume) on the PSG chip.
Line 84
locate(2, enemy_y, enemy_x);: Updates Sprite #2's position record in VRAM.
Lines 87–88
Function Exit: Closes the main game loop and completes the C function frame.

Step 3: Compile and Test

Terminal
tidepod sync && make
tidepod run
Docs / Tutorials / Tutorial 3: The Binary Asset Pipeline

Tutorial 3: The Binary Asset Pipeline (`.dat`)

Resource Management
Draft — being rewritten

This tutorial's code has not yet been verified against the current Tidepod toolchain and contains API calls that do not exist in libc. Use the API reference and the examples shipped in resources/examples/ until this page is updated.

Complex homebrew games require hundreds of kilobytes of level maps, tile pattern tables, PCM digitized voice samples, and music tracks. Tidepod provides an automated asset pipeline that ingests any raw binary file, assigns it to a cartridge ROM bank, and generates type-safe cross-bank access macros.

How Banking Works Under the Hood

On the TI-99/4A, cartridge ROM space is fixed at 8KB (0x6000–0x7FFF). To support cartridges up to 512KB (or larger), hardware banking boards swap 8KB chunks into this address window whenever the CPU writes to a specific trigger address.

Tidepod's tms9900-packer tool automatically groups your compiled C objects and binary assets (.dat) into separate 8KB banks. It creates header symbol files:

  • resource_defs.h: Defines symbol names, bank indices, and byte lengths for all .dat files.
  • resource_copy.h: Provides high-speed streaming routines (rom_to_vram, rom_to_ram) that latch the bank, transfer data, and restore the previous bank safely.

Step 1: Adding Assets to the Build

Place your raw binary files (e.g. resources/tileset.dat and resources/level_map.dat) into the project folder, then register them with the CLI:

Terminal
tidepod add resources/tileset.dat
tidepod add resources/level_map.dat
tidepod sync

Step 2: Complete Code Listing (`src/main.c`)

src/main.c
 1: #include <stdio.h>
 2: #include <conio.h>
 3: #include "resource_defs.h"
 4: #include "resource_copy.h"
 5: 
 6: // Allocate persistent level buffer in CPU BSS (off the C stack!)
 7: static unsigned char s_active_level[1024];
 8: 
 9: int main(void) {
10:     // Initialize 32-column graphics mode
11:     ti_text_mode(32);
12:     clrscr();
13: 
14:     // 1. Stream 2048 bytes of background tileset from banked ROM into VRAM at 0x0000.
15:     //    rom_to_vram handles bank switching, VDP address register programming,
16:     //    burst byte streaming, and original bank restoration!
17:     rom_to_vram(unwrap(tileset), 0x0000);
18: 
19:     // 2. Stream 1024 bytes of level map data from banked ROM into CPU RAM buffer
20:     rom_to_ram(unwrap(level_map), s_active_level, sizeof(s_active_level));
21: 
22:     // 3. Draw first 32 tiles of the level onto the top row of the screen
23:     gotoxy(0, 0);
24:     for (int i = 0; i < 32; i++) {
25:         cputc(s_active_level[i]);
26:     }
27: 
28:     gotoxy(2, 5);
29:     puts("ASSET PIPELINE STREAMING COMPLETE!");
30:     gotoxy(2, 7);
31:     printf("TILES LOADED: %u BYTES TO VRAM
", (unsigned int)tileset_size);
32:     gotoxy(2, 8);
33:     printf("MAP LOADED:   %u BYTES TO RAM
", (unsigned int)level_map_size);
34: 
35:     // Idle loop
36:     while (1) {
37:     }
38: 
39:     return 0;
40: }

Line-by-Line Code Breakdown

Lines 1–4
Headers: Includes standard I/O, console routines, and the auto-generated headers resource_defs.h and resource_copy.h produced by tms9900-packer.
Lines 6–7
Static RAM Buffer (s_active_level): Declares a 1024-byte buffer for level map tiles. Notice the static keyword: this places the array in the .bss segment located in upper 24K expansion RAM (0xA000). Never declare arrays of this size as local variables inside a function, as it would cause an instant stack overflow on the TMS9900!
Lines 10–12
Screen Setup: Activates 32-column graphics mode and erases the screen.
Lines 14–17
rom_to_vram(unwrap(tileset), 0x0000);: unwrap(tileset) is a macro defined in resource_defs.h that expands to: tileset_bank, tileset_rom_addr, tileset_size. The rom_to_vram routine reads the target ROM bank, switches the hardware cartridge latch to map that bank into 0x6000, sets the VDP address register to 0x0000, copies all pattern bytes directly from ROM into VRAM, and then switches back to the caller's bank.
Lines 19–20
rom_to_ram(unwrap(level_map), s_active_level, ...);: Transfers the raw level layout from its assigned ROM bank directly into CPU expansion RAM for rapid tile lookup during gameplay.
Lines 23–26
cputc(s_active_level[i]);: Renders the loaded level tiles onto the top row of the screen, proving that the RAM buffer was successfully populated from banked ROM.
Lines 28–33
Diagnostics: Prints confirmation messages and displays the exact byte sizes of the assets using the generated constants tileset_size and level_map_size.
Lines 36–40
Halt Loop & Clean Exit: Keeps the CPU safely running in an idle loop.
Docs / Tutorials / Tutorial 4: RAM Placement & Stack Safety

Tutorial 4: RAM Placement & Stack Safety

Advanced Optimization
Draft — being rewritten

This tutorial's code has not yet been verified against the current Tidepod toolchain and contains API calls that do not exist in libc. Use the API reference and the examples shipped in resources/examples/ until this page is updated.

The TMS9900 CPU has unique memory layout requirements and hazards. Understanding memory segmentation and the strict rules governing the C workspace stack is vital for building robust homebrew applications that never crash.

TI-99/4A Memory Segmentation Map

Address Window Size Bus Speed Placement Role & Strategy
0x2000 – 0x3FFF 8 KB 8-bit multiplexed (Wait states) Lower Expansion RAM (Persistent): Always resident, never bank-swapped. Use for interrupt service routines, audio playback drivers, and shared state across ROM banks (--persistent).
0x6000 – 0x7FFF 8 KB 8-bit Cartridge ROM Cartridge Window: Bank-switched ROM space for code, read-only tables, and packed .dat assets.
0x8300 – 0x83FF 256 Bytes 16-bit Zero-Wait-State Internal CPU Scratchpad RAM: Extremely fast on-die RAM. Stores active register workspaces R0–R15. Critical hot-loops can be placed here using --scratchpad.
0xA000 – 0xFFFF 24 KB 8-bit multiplexed (Wait states) Upper Expansion RAM: Standard C .text, .data, .bss, and C call stack (pointed to by R10).

The Cardinal Stack Rule: Never Allocate Large Locals

Stack Overflow Signature: Runaway Crash at 0x839C

The C workspace stack on the TMS9900 is managed via Register 10 (R10). The stack starts near the top of memory and grows downwards. Never allocate sector buffers (char buf[256]), directory slot tables, or file copy windows as local function variables. Doing so smashes the return address and corrupts system scratchpad vectors around 0x839C, causing an immediate console crash! Always use file-scope static variables.

Complete Code Listing (`src/safe_memory.c`)

src/safe_memory.c
 1: #include <stdio.h>
 2: #include <string.h>
 3: #include <conio.h>
 4: 
 5: // Fast raster math function placed in ultra-fast 16-bit Scratchpad RAM (0x8300)
 6: __attribute__((section(".scratchpad")))
 7: int fast_sin(int angle) {
 8:     static const int s_sin_lut[8] = { 0, 70, 100, 70, 0, -70, -100, -70 };
 9:     return s_sin_lut[angle & 7];
10: }
11: 
12: // Audio playback routine placed in Lower 8K Persistent RAM (0x2000)
13: // Guaranteed to remain accessible regardless of which ROM bank is currently active!
14: __attribute__((section(".persistent")))
15: void persistent_audio_tick(void) {
16:     // Audio driver code safe from bank swaps
17: }
18: 
19: // SAFE: 256-byte sector buffer placed in .bss Expansion RAM (NOT on the stack!)
20: static unsigned char s_disk_sector_buffer[256];
21: 
22: int safe_process_sector(unsigned char fill_pattern) {
23:     // Fills the 256-byte static buffer safely
24:     memset(s_disk_sector_buffer, fill_pattern, sizeof(s_disk_sector_buffer));
25:     
26:     int checksum = 0;
27:     for (int i = 0; i < 256; i++) {
28:         checksum += s_disk_sector_buffer[i];
29:     }
30:     return checksum;
31: }
32: 
33: int main(void) {
34:     ti_text_mode(40);
35:     clrscr();
36: 
37:     int val = fast_sin(2);
38:     int sum = safe_process_sector(0x42);
39: 
40:     printf("SCRATCHPAD SIN(2): %d
", val);
41:     printf("STATIC BSS BUFFER CHECKSUM: %d
", sum);
42:     puts("MEMORY TEST COMPLETED WITH ZERO STACK VIOLATIONS.");
43: 
44:     while (1) {
45:     }
46: 
47:     return 0;
48: }

Line-by-Line Code Breakdown

Lines 1–3
Headers: Includes standard declarations for stdio.h, string.h (for memset), and conio.h.
Lines 5–10
fast_sin(...) in Scratchpad RAM: The __attribute__((section(".scratchpad"))) attribute directs the linker to place this function inside the 256-byte internal 16-bit CPU RAM at 0x8300. Because this memory has zero wait states and does not go through the console 8-bit multiplexer, instruction fetch and lookup operations execute up to 300% faster than ROM.
Lines 12–17
persistent_audio_tick(...) in Persistent RAM: The __attribute__((section(".persistent"))) attribute compiles this routine into Lower 8K RAM (0x2000–0x3FFF). This memory is permanently connected and never affected by cartridge banking latches, making it the ideal home for interrupt-driven sound players that must execute while other ROM banks are swapped in. (You can also achieve this via CLI: tidepod add src/audio.c --persistent).
Lines 19–20
static unsigned char s_disk_sector_buffer[256];: Crucial best practice: By declaring the 256-byte buffer at file-scope with static, the compiler allocates storage in the uninitialized data segment (.bss) in high RAM. Zero bytes are taken from the R10 stack pointer.
Lines 22–31
safe_process_sector(...): Operates on s_disk_sector_buffer without any danger of stack corruption. The local variables fill_pattern and checksum fit into CPU registers R1 and R2.
Lines 33–48
Main Execution Flow: Initializes text mode, exercises the Scratchpad routine, computes the checksum, prints diagnostic messages to screen, and halts in an idle loop.
Docs / Tutorials / Tutorial 5: F18A FPGA Graphics & GPU

Tutorial 5: F18A FPGA Graphics & GPU in C

FPGA Coprocessor
Draft — being rewritten

This tutorial's code has not yet been verified against the current Tidepod toolchain and contains API calls that do not exist in libc. Use the API reference and the examples shipped in resources/examples/ until this page is updated.

The F18A is a modern FPGA drop-in replacement for the TMS9918A VDP. In addition to 80-column text mode, 64-color palettes, and bitmap scrolling, it includes an embedded 100MHz 16-bit GPU coprocessor with direct access to 16KB VRAM. Tidepod is the only toolchain in the world that allows you to write F18A GPU routines directly in ISO C99!

Dual-Processor Architecture

Building an F18A GPU project involves two separate programs executing in tandem:

  1. GPU Coprocessor Shader (`src/gpu_fx.c`): Compiled by tms9900-gcc with GPU target flags. Runs entirely inside the F18A FPGA at 100MHz (over 30× faster than the 3MHz host CPU!).
  2. Host TMS9900 Controller (`src/main.c`): Runs on the console CPU. Unlocks F18A enhanced registers, uploads the GPU binary to VRAM, and triggers execution.

Step 1: Write the GPU Coprocessor Shader (`src/gpu_fx.c`)

src/gpu_fx.c
 1: // Code executed natively on the 100MHz 16-bit F18A GPU coprocessor
 2: void gpu_main(void) {
 3:     // Direct pointer to F18A VRAM (mapped at address 0x0000 in GPU memory space)
 4:     volatile unsigned char *vram = (volatile unsigned char *)0x0000;
 5:     
 6:     // Rapidly fill screen tiles with a dynamic XOR geometric pattern
 7:     for (int y = 0; y < 24; y++) {
 8:         for (int x = 0; x < 40; x++) {
 9:             int offset = (y * 40) + x;
10:             vram[offset] = (unsigned char)((x ^ y) + 65);
11:         }
12:     }
13:     
14:     // Halt GPU execution cleanly and wait for next host trigger
15:     __asm__ volatile ("idle");
16: }

Line-by-Line Breakdown: GPU Shader

Lines 1–4
volatile unsigned char *vram = 0x0000;: On the host TMS9900, VRAM must be accessed through slow 8-bit ports (0x8C00). On the F18A GPU, the entire 16KB VRAM is mapped directly into the GPU's memory address space starting at 0x0000. Memory reads and writes execute in a single 100MHz clock cycle!
Lines 6–12
Procedural Screen Generation: Loops through all 960 character tiles (40×24) and writes an XOR calculation directly into VRAM. At 100MHz, this entire 960-byte screen refresh completes in a fraction of a millisecond.
Lines 14–16
__asm__ volatile ("idle");: Executes the TMS9900 IDLE opcode. On the F18A GPU, IDLE stops the coprocessor pipeline and clears the GPU busy bit in VDP status, signaling to the host CPU that the GPU task is complete.

Step 2: Register GPU File with the Build System

Terminal
tidepod add src/gpu_fx.c --gpu --gpu-base 0x3000 --gpu-stack 0x3F00
tidepod sync

This tells the toolchain to compile gpu_fx.c to link at VRAM address 0x3000 with its stack at 0x3F00, and to generate the header gpu_fx_map.h.

Step 3: Host Execution from TMS9900 CPU (`src/main.c`)

src/main.c
 1: #include <stdio.h>
 2: #include <conio.h>
 3: #include "resource_defs.h"
 4: #include "resource_copy.h"
 5: #include "gpu_fx_map.h"
 6: 
 7: // F18A hardware register control routines
 8: extern void f18a_unlock(void);
 9: extern void f18a_gpu_start(unsigned int vram_addr);
10: 
11: int main(void) {
12:     ti_text_mode(40);
13:     clrscr();
14:     puts("INITIALIZING F18A HARDWARE...");
15: 
16:     // 1. Unlock enhanced F18A registers by writing magic sequence >55, >55 to VR57
17:     f18a_unlock();
18: 
19:     // 2. Stream compiled GPU machine code from cartridge ROM into VRAM at 0x3000
20:     rom_to_vram(unwrap(gpu_fx), 0x3000);
21: 
22:     gotoxy(0, 5);
23:     puts("LAUNCHING 100 MHZ F18A GPU SHADER...");
24: 
25:     // 3. Trigger GPU execution at VRAM address 0x3000 via VDP Register 54
26:     f18a_gpu_start(0x3000);
27: 
28:     gotoxy(0, 12);
29:     puts("GPU EXECUTED SUCCESSFULLY!");
30: 
31:     while (1) {
32:     }
33: 
34:     return 0;
35: }

Line-by-Line Breakdown: Host Controller

Lines 1–5
Headers: Includes standard I/O and the auto-generated gpu_fx_map.h header, which defines symbols for the compiled GPU coprocessor image.
Lines 7–9
F18A Library Prototypes: Declares f18a_unlock() and f18a_gpu_start() from Tidepod's hardware abstraction library.
Lines 16–17
f18a_unlock();: The F18A starts in strict TMS9918A compatibility mode. To enable the GPU and enhanced registers 16–63, the software must write the 2-byte magic unlock sequence (0x55, 0x55) to VDP Register 57.
Lines 19–20
rom_to_vram(unwrap(gpu_fx), 0x3000);: Transfers the compiled GPU machine code from the cartridge ROM bank into VRAM starting at address 0x3000.
Lines 25–26
f18a_gpu_start(0x3000);: Writes the target execution address (0x3000) to VDP Registers 54 and 55. This activates the 100MHz GPU coprocessor clock, which instantly begins executing gpu_main()!
Lines 31–35
Host Idle Loop: Keeps the TMS9900 CPU active while the GPU runs in hardware.
Docs / Tutorials / Tutorial 6: Disk I/O & Standalone EA5

Tutorial 6: Disk I/O & Standalone EA5 Programs

File Operations
Draft — being rewritten

This tutorial's code has not yet been verified against the current Tidepod toolchain and contains API calls that do not exist in libc. Use the API reference and the examples shipped in resources/examples/ until this page is updated.

Tidepod includes a fully compliant C stdio subsystem supporting standard file operations on TI floppy sector disk images (.dsk) and hard disks (.hd). It also supports building standalone binary programs executable via the classic Editor/Assembler Option 5 menu without requiring a cartridge ROM.

TI Disk Architecture & Path Notation

TI-99/4A file access uses disk controller DSR (Device Service Routine) protocols. File paths are specified using standard TI notation:

  • "DSK1.SCORES": Refers to file SCORES on Floppy Drive 1.
  • "DSK2.LEVELDATA": Refers to file LEVELDATA on Floppy Drive 2.
  • Tidepod's C runtime automatically converts standard POSIX stream calls (fopen, fprintf, fgets, fclose) into TI sector PAB (Peripheral Access Block) requests.

Complete Code Listing (`src/main.c`)

src/main.c
 1: #include <stdio.h>
 2: #include <string.h>
 3: #include <conio.h>
 4: 
 5: // Static I/O buffer in BSS to prevent stack overflow!
 6: static char s_io_buffer[128];
 7: 
 8: int main(void) {
 9:     ti_text_mode(40);
10:     clrscr();
11:     puts("TI-99/4A DISK I/O DEMONSTRATION");
12: 
13:     // 1. Open file for writing on Floppy Drive 1
14:     FILE *fp = fopen("DSK1.SCORES", "w");
15:     if (!fp) {
16:         puts("ERROR: CANNOT CREATE DSK1.SCORES");
17:         while (1) {}
18:     }
19: 
20:     // 2. Write high score records to disk
21:     fprintf(fp, "DANNY:98500
");
22:     fprintf(fp, "RETRO:82100
");
23:     fclose(fp);
24:     puts("HIGH SCORES SAVED SUCCESSFULLY.");
25: 
26:     // 3. Re-open file for sequential reading
27:     fp = fopen("DSK1.SCORES", "r");
28:     if (!fp) {
29:         puts("ERROR: CANNOT READ DSK1.SCORES");
30:         while (1) {}
31:     }
32: 
33:     // 4. Stream and print records line by line
34:     int line_num = 1;
35:     while (fgets(s_io_buffer, sizeof(s_io_buffer), fp)) {
36:         gotoxy(2, 6 + line_num);
37:         printf("RECORD %d: %s", line_num, s_io_buffer);
38:         line_num++;
39:     }
40:     fclose(fp);
41: 
42:     gotoxy(2, 11);
43:     puts("DISK I/O OPERATION COMPLETED OK.");
44: 
45:     while (1) {
46:     }
47: 
48:     return 0;
49: }

Line-by-Line Code Breakdown

Lines 1–3
Headers: Includes standard C declarations for stdio.h (file streams), string.h, and conio.h.
Lines 5–6
Static Line Buffer: Allocates a 128-byte line buffer s_io_buffer in the .bss segment. Maintaining file I/O buffers in static memory is essential to prevent C workspace stack overflows.
Lines 8–11
Screen Initialization: Configures 40-column text mode, clears the screen, and outputs the title header.
Lines 14–18
fopen("DSK1.SCORES", "w");: Instructs the TI disk controller DSR to allocate a file descriptor record (FDR) on disk drive 1 and open a sequential write stream. If the drive is unmounted or write-protected, fopen returns NULL, which is caught and reported.
Lines 21–24
fprintf & fclose: Writes two formatted string records into the disk sector buffer. Calling fclose(fp) flushes remaining dirty sectors to floppy media, updates the catalog directory track, and releases the DSR Peripheral Access Block.
Lines 27–31
fopen("DSK1.SCORES", "r");: Re-opens the newly written file in read-only sequential mode.
Lines 34–40
fgets(s_io_buffer, sizeof(s_io_buffer), fp);: Reads records from disk until encountering EOF (End of File). Each line is printed to the screen using printf at incrementing row coordinates. Calling fclose(fp) cleanly closes the stream.
Lines 42–49
Completion & Idle Loop: Displays completion banner and enters safe bare-metal halt loop.

Building Standalone EA5 Disks

If you wish to distribute your application as a disk-based binary that users can launch from the TI Editor/Assembler cartridge (Option 5: RUN PROGRAM FILE):

Terminal
tidepod new FileTool --type ea5 --path ./FileTool
cd FileTool && make

This links your code with an origin at 0xA000 in Expansion RAM and packages the resulting binary onto an autorun floppy image (FileTool.dsk).

Docs / Tools & Workflows / Tidepod CLI (`tidepod`)

Tidepod CLI Reference

Command-Line Automation

The tidepod executable serves as both the desktop GUI IDE and a headless command-line project manager that drives the build orchestrator.

Command Arguments & Options Description
tidepod new <name> [--type 8k|banked|xb|ea5] [--size KB] [--menu NAME] Scaffold a new project workspace, boilerplate files, and .project configuration.
tidepod add <files...> [--persistent] [--scratchpad] [--gpu] Register source or binary .dat files into the project and regenerate the Makefile.
tidepod remove <files...> Remove files from the project build list. (Files remain on disk).
tidepod list None Print all registered sources, memory placement tags, and project configuration.
tidepod set-type <8k | banked | xb | ea5 | custom> Change the project target type and re-sync the build system.
tidepod sync None Parse .project and regenerate the build Makefile.
tidepod run [--debug] Launch ti994a-emu with project mounts and settings applied.
Docs / Tools & Workflows / Stand-Alone Emulator (`ti994a-emu`)

Stand-Alone Emulator Reference

ti994a-emu CLI

ti994a-emu is Tidepod's cycle-accurate hardware emulator. It can be invoked directly from the terminal with granular hardware options:

Option Parameter Description
--cart <file.rpk | file.bin> Load a cartridge ROM or RPK package.
--dsk1..4 <file.dsk> Mount a floppy sector disk image to DSK1, DSK2, DSK3, or DSK4.
--hard1..3 <file.hd> Mount a raw sector hard disk image to HARD1, HARD2, or HARD3.
--vdp <engine> Select VDP: tidepod-tms9918a, tidepod-f18a, pico-f18a, pico-v9938.
--autorun None Automatically press ENTER and launch the cartridge menu entry on boot.
--fast-disk None Bypass realistic motor spin-up delays for instantaneous disk sector reads.
--peb-card <type@cru> Configure PEB slot: tifdc@1100, hfdc@1100, whtscsi@1200.
Docs / Tools & Workflows / DWARF & GDB Debugging

Source-Level Debugging (DWARF + GDB)

GDB Remote Server (:3333)

Tidepod produces full DWARF debugging sidecars during compilation. You can debug your code either through the graphical IDE or via command-line GDB:

Terminal 1: Launch Emulator GDB Server
make debug
ti994a-emu --cart mygame.rpk --gdb 3333
Terminal 2: Attach Toolchain GDB
tms9900-elf-gdb mygame.elf \
  -ex 'target remote :3333' \
  -ex 'break main' \
  -ex 'continue'
Docs / Tools & Workflows / Headless Automated Testing

Headless Automated Regression Testing

CI / CD Verification

Test and verify your game in headless continuous integration without a GUI window by scripting keystrokes and capturing verification screenshots:

Headless Regression Test
ti994a-emu --cart mygame.rpk --headless --f18a \
  --keys "WAIT:240 SPACE WAIT:60 2 WAIT:300" \
  --screenshot-at 600 --screenshot-path verification.png \
  --frames 620
Docs / API Reference / Standard C Lib & Extended BASIC Runtime

Full API Reference Catalog

123+ Functions Synced with Man Pages
Function / Prototype Category Description & Parameters Man Page
fopen(path, mode) stdio Opens a stream to a TI device path (e.g. DSK1.FILE or WDS1.DIR.FILE). Supports modes "r", "w", "wb", "a", with record length flags (:df, :dv80, :if128). fopen.3
fclose(stream) stdio Flushes and closes an open file stream, writing updated catalog / FDR headers to disk. fclose.3
fread(ptr, size, nmemb, stream) stdio Reads binary records or characters from disk stream into CPU memory. fread.3
fwrite(ptr, size, nmemb, stream) stdio Writes data records from memory into an active disk stream. fwrite.3
fseek(stream, offset, whence) stdio Repositions the stream record offset. ftell() returns current stream position. fseek.3
fcopy_path(src, dst) stdio Fast native sector-level copy between two file paths across drives. fcopy_path.3
printf(fmt, ...) stdio Formatted output to active console. sprintf and snprintf write to buffers. printf.3
malloc(size) / free(ptr) stdlib Dynamic memory allocation in upper expansion RAM powered by libmalloc99. malloc.3
rand() / srand(seed) stdlib Pseudo-random number generation optimized for 16-bit registers. rand.3
qsort(base, num, size, cmp) stdlib In-place quicksort algorithm. qsort.3
atoi(str) / abs(val) stdlib String to integer conversions and absolute value calculation. atoi.3
memcpy(dst, src, n) string Fast block memory copy and fill routines utilizing 16-bit word moves. memcpy.3
strlen / strcpy / strcmp string Standard C string manipulation functions. strlen.3
sinf(x) / cosf(x) / sqrtf(x) math Single-precision floating-point trigonometric and algebraic operations. sinf.3
isalpha(c) / isdigit(c) ctype Fast character classification lookup routines. isalpha.3
gotoxy(x, y) / cputc(ch) conio Direct text screen positioning and character output without stdio overhead. conio.3
ti_text_mode(columns) conio Configures standard 40-column or 80-column (F18A) text display mode. ti_text_mode.3
sprite(num, char, col, r, c, rv, cv) Extended BASIC Spawns hardware sprite (1-32) at pixel position (r, c) with velocities (rv, cv). sprite.3xb
pattern(char_code, hex_string) Extended BASIC Defines custom 8x8 bitmap pattern using 16-character hex string. pattern.3xb
locate(num, r, c) Extended BASIC Moves or updates sprite coordinates. delsprite(num) removes sprite. locate.3xb
sound(duration, freq, vol) Extended BASIC Plays tone (110-44733 Hz) or noise channel (-1 to -8) through SN76489 sound chip. sound.3xb
joyst(unit, &x, &y) Extended BASIC Reads joystick directional offsets (-4, 0, 4). key(unit, &ch, &status) reads keys. joyst.3xb
coinc(s1, s2, tolerance) Extended BASIC Tests for hardware or pixel collision between sprites. coinc.3xb
vsync() Extended BASIC Waits for next 60Hz vertical blanking interrupt for smooth, tear-free animations. vsync.3xb
color(set, fg, bg) / screen(color) Extended BASIC Sets character set foreground/background colors and screen backdrop color (1-16). color.3xb
peek(addr) / poke(addr, val) Extended BASIC Reads or writes CPU memory. peekv / pokev access Video VRAM directly. peek.3xb
Docs / AI & Best Practices / Agent Skill (`ti99-tidepod-gamedev`)

The Shipped Agent Skill (`ti99-tidepod-gamedev`)

AI Pair Programming Standard

Tidepod ships with an official, open-standard AI agent skill (following agentskills.io) located at .cursor/skills/ti99-tidepod-gamedev/.

This teaches AI coding assistants (Cursor, Claude Code, GitHub Copilot, Gemini Antigravity) how to properly develop software for Tidepod:

How to Install in Your Workspace

Terminal
# In your project root, symlink or copy the skill:
mkdir -p .cursor/skills
cp -r /path/to/tidepod/.cursor/skills/ti99-tidepod-gamedev .cursor/skills/

Rules Taught to the AI

  • Enforces Makefile Integrity: Prevents the AI from corrupting the generated Makefile by insisting on tidepod add.
  • Prevents Stack Overflows: Trains the AI to never allocate sector buffers (e.g. char buf[256]) on the stack.
  • Teaches the Asset Workflow: Guides the AI to wrap resources with unwrap(sym) and rom_to_vram().
  • Automated Headless Smoke Tests: Enables the AI to autonomously test its own code changes by running ti994a-emu --headless with scripted keys.
Docs / AI & Best Practices / Toolchain Rules & Gotchas

Toolchain Rules & Hardware Gotchas

Essential Best Practices
1. Reading VDP Status Register Clears Hardware Flags

Reading VDPST (address 0x8802) automatically clears the VBLANK interrupt flag, sprite collision bit, and 5th-sprite flag. Read it once per frame and store it in a local variable if you need to check multiple flags.

2. Cross-Bank String Literals

Because only one 8KB ROM bank is mapped at a time, calling a function in another bank with a pointer to a string literal in the caller's ROM bank would point to invalid data after the bank switch. The Tidepod packer automatically hoists cross-bank string literals into always-mapped RAM (.data).

3. Cross-Bank Function Pointers

Tidepod wraps direct cross-bank function calls with trampolines automatically. However, function pointers (callback handlers) across banks are not automatically trampolined. Place callback functions in the --persistent lower RAM section so they are accessible from all banks.