Data formats

These are the bytes that the tools write and the library reads. Numbers of more than one byte are little-endian, if a table does not tell otherwise.

Packed pixels and the SHR screen

A pixel is a 4-bit palette index. A byte holds two pixels. The left pixel is in bits 7-4. A row of w pixels uses (w + 1) / 2 bytes.

AUX bank 0Contents
$2000-$9CFF200 rows of 160 bytes. Row y starts at $2000 + 160y.
$9D00-$9DC7One scan-line control byte for each row. libtini writes 0: 320-pixel mode, palette 0.
$9DC8-$9DFFPadding. libtini writes 0.
$9E00-$9FFFSixteen palettes of sixteen 0RGB words. libtini uses palette 0 at $9E00.

A palette word is 0x0RGB, with four bits each for red, green and blue. Thus 0x0F00 is red and 0x0FFF is white.

Palette JSON

["#000000", "#FFFFFF", "#FF0000", 255, 4095]

The list has a maximum of sixteen entries. Each entry is a 0RGB integer 0-4095 or a "#RRGGBB" string. A string keeps the high four bits of each channel. Missing entries are 0. assets.py image --palette-output writes the sixteen words as 32 bytes.

TI4 images

OffsetBytesContents
04TI4 and a zero byte
42Width, 1-320
62Height, 1-200
8stride * heightPacked rows, stride = (width + 1) / 2
8 + stride * height((width + 7) / 8) * heightOptional opacity mask: one bit for each pixel, bit 7 at the left, 1 for opaque. Index 0 is transparent.

A surface view uses offset 8. The mask_offset of a ti_sprite is the offset of the mask.

Indexed image JSON

{"width": 4, "height": 2, "pixels": [0, 1, 1, 0, 1, 2, 2, 1], "masked": true}

TIF fonts

OffsetBytesContents
04TIF1
41First character code
51Glyph count, 1-255. The first code plus the count is a maximum of 256.
61Glyph width, 1-8
71Glyph height, 1-16
8count * heightGlyph rows, one byte each, bit 7 at the left
{"width": 3, "height": 3, "first": 48, "glyphs": [["###", "#.#", "###"]]}

TIM tile maps

OffsetBytesContents
04TIM1
42Columns, 1-256
62Rows, 1-256
8columns * rowsTile numbers, row by row
{"rows": [[0, 1, 2], [3, 4, 5]], "tile_count": 6}

TPK1 resource packs

OffsetBytesContents
04TPK1
42Entry count, 1-16
62Zero
824 for each entryThe directory
8 + 24nEntry data, in directory sequence
Entry offsetBytesContents
016Name, filled with NUL bytes: a capital letter, then capitals, digits, . or _, a maximum of 15 characters
164Absolute offset of the data in the file
204Size, not zero

ti_pack_load checks all of these conditions for each entry before it allocates memory. The pack manifest is {"entries": [{"name": "SHIP", "path": "ship.ti4"}]}. Each entry is 1 byte to 4 MiB.

Pattern songs

tools/music.py writes these bytes for a song document. ti_song_start reads them.

OffsetBytesContents
02Device ticks for each row, 8.8 fixed point: 256-25,600 (1-100 ticks)
21Tracks, 1-12
31Order positions, 1-255
41Loop position, less than the number of positions
51Notes in the period table, 1-96
61Patterns, 1-255
73 for each trackVolume, decay ticks and floor, each 0-15
then2 for each noteTone periods for the 1 MHz base clock, 0-2047
then2 for each patternOffset of each pattern from the first byte of the song
thenpositions for each trackPattern numbers. The order of track 0 is first.
thenPattern bytes
Pattern byteMeaning
$00-$5FPlay that note of the period table
$60Rest
$61Hold the current sound
$80 + nThe next notes, rests and holds last n + 1 rows, 1-64
$C0 + v, $D0 + d, $E0 + fSet the volume, decay or floor of the track
$FFEnd of the pattern

The row length, volume, decay and floor continue into the next pattern. Each pattern starts after the tables. It ends in the song with $FF, and it holds at least one note, rest or hold.

At each row, a track whose note has ended reads its next note, rest or hold. A note sets the tone and starts at the volume of the track. Every decay ticks, it decreases by one level, until it reaches the floor. A decay of 0 keeps the volume.

Decay runs before the row, so a note starts at full volume. After a late update, decay catches up a maximum of 16 ticks.

Song JSON

{"credit": "optional text for the header comment",
 "song": {"bpm": 152, "rows_per_beat": 8, "loop": 0,
   "tracks": [{"name": "lead", "volume": 12, "decay": 4, "floor": 8, "order": ["a", "a", "b"]}],
   "patterns": {"a": "D#5:2 F#5:2 -:4 G#5:8", "b": "v10 B5:12 ^:4"}}}

Event score JSON

{"voices": 6, "events": [{"duration": 0, "period": 956, "voice": 0, "volume": 7},
                         {"duration": 25, "period": 1517, "voice": 1, "volume": 5}]}

voices is 6 or 12. There are 1-512 events. Each event has exactly four keys: duration 0-65,535 ticks, period 0-2047, voice less than voices, and volume 0-15.

At least one duration must not be zero. A maximum of 31 zero durations can follow each other. The header holds a ti_music_event array, with six bytes for each event.

Speech JSON and phoneme bytes

{"phrases": {"SAY_READY": "Ready.", "SAY_SCORE": "Score 100!"},
 "pronunciations": {"UP": ["UH", "P"], "CLEAR": "K L IY R"}}

Each generated byte holds an SSI-263 phoneme code in bits 0-5 and a duration value in bits 6-7. The duration value is 0 for vowels, 2 for stops and the pause, and 1 for all other sounds.

NamesCodes
PA pause$00
Vowels IY AY IH EH AE AA AO OW OY UW UH ER, and AW with the code of AO$01, $05, $07, $0A, $0C, $0F, $10, $11, $12, $14, $18, $1C
Y R L W$03, $1D, $20, $23
Stops B D G P T K$24-$29
CH J TH DH Z S ZH SH V F HH M N NG$2A, $2B, $2C, $2D, $2F, $30, $31, $32, $33, $34, $35, $37, $38, $39