Bitsy data model¶
A Bitsy game is a plain-text .bitsy file: a sequence of typed segments, each defining one entity (palette, tile, room, …). citsy parses this file into an in-memory Game structure (src/model/game.hpp).
This document describes every entity type, its properties, how segments map to file syntax, and how entities relate to each other.
Overview¶
Game
├── version, room_format, title
├── palettes[id] → Palette
├── tiles[id] → Tile
├── sprites[id] → Sprite (avatar is always id "A")
├── items[id] → Item
├── rooms[id] → Room
├── dialogues[id] → Dialogue
├── variables[name]→ Variable
└── endings[id] → Ending
All entity collections are keyed by string id (or name for variables). There is no implicit ordering — rooms, tiles, and other segments can appear in any sequence in the file.
Coordinate system¶
| Concept | Value |
|---|---|
| Room grid | 16 × 16 tiles |
| Tile pixel size | 8 × 8 pixels |
| Logical screen | 128 × 128 pixels (16 tiles × 8 px) |
| Origin | Top-left corner |
| Tile coordinates | x increases right, y increases down |
| Range | x and y are 0–15 within a room |
Positions in file data use x,y notation (e.g. 4,7 means column 4, row 7).
The avatar sprite, room-placed items, exits, and endings all use tile coordinates within a room grid.
Pixel art: TileFrame¶
All drawable entities (tiles, sprites, items) store pixel data as one or more frames.
| Property | Type | Description |
|---|---|---|
| Storage | std::array<std::uint8_t, 64> |
Row-major: index = y * 8 + x |
| Pixel values | 0 or 1 |
0 = background color, 1 = drawing color |
| Frame size | 8 rows × 8 columns | Each row is exactly 8 characters in the file |
File format¶
Each frame is eight lines of eight binary digits:
Multiple frames are separated by a > line on its own:
Animated tiles, sprites, and items cycle through frames every 400 ms (Bitsy 8.15).
Color mapping¶
Each pixel is binary, but the drawing color is not always palette index 1 or 2. Entities can specify a COL n directive to use extended palette color index n. Background pixels always use palette index 0.
Game — root aggregate¶
The top-level container for all parsed game data.
| Property | C++ type | Default | Description |
|---|---|---|---|
version |
GameVersion |
0.0 |
Parsed from # BITSY VERSION major.minor header |
room_format |
int |
1 |
0 = legacy single-char rows; 1 = comma-separated |
title |
string |
"" |
First line of the file (Bitsy title dialog) or top-level NAME |
title_dialog |
string |
"" |
Full title script shown at game start |
palettes |
map<string, Palette> |
— | All color palettes, keyed by id |
tiles |
map<string, Tile> |
— | Tile definitions |
sprites |
map<string, Sprite> |
— | Sprite definitions |
items |
map<string, Item> |
— | Item definitions |
rooms |
map<string, Room> |
— | Room definitions |
dialogues |
map<string, Dialogue> |
— | Dialog scripts |
variables |
map<string, Variable> |
— | Global game variables |
endings |
map<string, Ending> |
— | End-game messages |
Constants and helpers¶
| Name | Value / signature | Description |
|---|---|---|
Game::kAvatarId |
"A" |
Avatar sprite id is always A |
avatar() |
const Sprite* |
Returns the avatar sprite, or nullptr |
start_room_id() |
string |
Room from avatar POS, or first room if unset |
File header directives¶
| Directive | Maps to |
|---|---|
First line (before # / ! / segments) |
title, title_dialog, dialogues["title"] |
# BITSY VERSION X.Y |
version.major, version.minor |
NAME <title> |
title (top-level fallback) |
! ROOM_FORMAT 1 |
room_format = 1 (comma-separated room rows) |
! ROOM_FORMAT 0 |
room_format = 0 (legacy single-char tile ids) |
! TXT_MODE 0/1 |
Hirez / lorez textbox |
! DLG_COMPAT 1 |
Pre-7.0 sprite↔dialog id linking (auto for VER_MAJ < 7) |
TEXT_DIRECTION RTL |
Right-to-left textbox layout |
GameVersion¶
| Property | Type | Description |
|---|---|---|
major |
int |
Major version number |
minor |
int |
Minor version number |
Method to_string() returns "major.minor".
Palette¶
A named set of RGB colors used by rooms and drawing entities.
| Property | C++ type | Description |
|---|---|---|
id |
string |
Palette identifier (e.g. "0", "1") |
name |
string |
Human-readable name |
colors |
vector<Color> |
Ordered RGB values |
Color indices¶
| Index | Role | Typical use |
|---|---|---|
0 |
Background | Room fill, pixel value 0 in art |
1 |
Tile | Default drawing color for tiles (COL default) |
2 |
Sprite | Default drawing color for sprites and items |
3+ |
Extended | Multi-color art via COL n directives |
Each Color is { r, g, b } with components 0–255.
File format¶
Optional COL n lines before a color row are accepted and ignored (some editor versions emit them). Additional RGB lines extend the palette beyond three colors.
Usage¶
Rooms reference a palette via PAL <id>. When the avatar enters a room, the active palette switches to that room's palette for rendering.
Tile¶
Static 8×8 pixel art placed on the room grid. Tiles form the background layer — walls, floors, decorations.
| Property | C++ type | Default | Description |
|---|---|---|---|
id |
string |
— | Tile identifier (single char or short string, e.g. "a", "0") |
name |
string |
"" |
Display name |
frames |
vector<TileFrame> |
— | ≥1 frame; multiple frames = animation |
is_wall |
bool |
false |
If true, blocks avatar movement |
color_index |
uint8_t |
1 |
Palette index for drawing pixels (1 = tile color) |
File format¶
TIL a
11111111
10000001
10000001
10000001
10000001
10000001
10000001
11111111
NAME wall
WAL true
COL 2
| Sub-key | Maps to |
|---|---|
NAME <text> |
name |
WAL true / WAL false |
is_wall |
COL n |
color_index |
| 8×8 binary rows | One animation frame |
> |
Frame separator |
Room placement¶
Rooms reference tiles by id in their 16×16 grid. The default background tile is typically id "0".
Wall collision¶
When is_wall is true, the avatar cannot move onto that tile. Legacy games may also list wall tile ids in a room-level WAL directive; modern games set WAL true on the tile definition itself.
Sprite¶
An animated 8×8 character or object that exists independently of the room grid. The avatar (player character) is always sprite id A.
| Property | C++ type | Default | Description |
|---|---|---|---|
id |
string |
— | Sprite identifier |
name |
string |
"" |
Display name |
frames |
vector<TileFrame> |
— | Animation frames |
position |
optional<SpritePos> |
nullopt |
Starting room and tile coordinates |
dialog_id |
string |
"" |
Dialog script id (empty = no dialog) |
color_index |
uint8_t |
2 |
Palette index for drawing pixels |
SpritePos¶
| Property | Type | Description |
|---|---|---|
room_id |
string |
Room the sprite starts in |
x |
int |
Tile column (0–15) |
y |
int |
Tile row (0–15) |
File format¶
SPR A
00011000
00011000
00111100
01111110
10111101
00111100
00011000
00011000
NAME Avatar
POS 0 4,7
DLG DLG_0
COL 2
| Sub-key | Maps to |
|---|---|
NAME <text> |
name |
POS <room_id> <x>,<y> |
position |
DLG <dialog_id> |
dialog_id |
COL n |
color_index |
Avatar rules¶
- Id is always
A(Game::kAvatarId) - Exactly one avatar per game
start_room_id()derives from the avatar'sPOSdirective- The avatar moves tile-by-tile in response to directional input
Non-avatar sprites (id 0, 1, …) are NPCs or decorative characters placed at fixed positions. They can have dialog attached.
Item¶
A collectible 8×8 object. Items are defined globally and placed in specific rooms.
| Property | C++ type | Default | Description |
|---|---|---|---|
id |
string |
— | Item identifier |
name |
string |
"" |
Display name (shown in inventory) |
frames |
vector<TileFrame> |
— | Animation frames |
dialog_id |
string |
"" |
Default dialog when interacted with |
color_index |
uint8_t |
2 |
Palette index for drawing pixels |
File format (definition)¶
ITM 0
00000000
00011000
00111100
01111110
01111110
00111100
00011000
00000000
NAME key
DLG DLG_0
COL 2
Room placement: RoomItem¶
Items appear in rooms via ITM sub-keys on the room segment:
| Property | Type | Description |
|---|---|---|
item_id |
string |
References an Item definition |
x |
int |
Tile column in the room |
y |
int |
Tile row in the room |
dialog_id |
string |
Optional per-instance dialog override |
Runtime behavior (Phase 1)¶
- Avatar walks onto an item to pick it up and show its dialog
- The item is removed from the room so it is no longer drawn
- Inventory counts and
{item}give/take scripts are Phase 2
Room¶
A 16×16 grid of tiles plus placed items, exits, and endings.
| Property | C++ type | Default | Description |
|---|---|---|---|
id |
string |
— | Room identifier |
name |
string |
"" |
Display name |
tiles |
TileGrid |
— | 16×16 grid of tile id strings |
palette_id |
string |
"" |
Active palette for this room |
items |
vector<RoomItem> |
— | Placed collectible items |
exits |
vector<Exit> |
— | Warp points to other rooms |
endings |
vector<EndingRef> |
— | Tiles that trigger game endings |
avatar_id |
string |
"" |
Per-room avatar appearance (AVA) |
tune_id |
string |
"" |
Room music (TUNE); "0" / empty = silence |
TileGrid¶
using TileGrid = std::array<std::array<std::string, 16>, 16>;
// tiles[row][col] — tile id at each cell
Each cell holds a tile id string (e.g. "0", "a"). The string "0" typically refers to the default background tile.
File format¶
ROOM 0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0
...
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
NAME room one
PAL 0
ITM 0 4,4
EXT 15,8 1 0,8
END 0 8,0
The room body has two parts:
- Tile rows — exactly 16 lines at the top of the segment
- Sub-keys — metadata and placed entities after the grid
| Sub-key | Maps to |
|---|---|
NAME <text> |
name |
PAL <id> |
palette_id |
ITM <item_id> <x>,<y> |
Entry in items |
EXT <x>,<y> <dest_room> <dx>,<dy> … |
Entry in exits |
END <ending_id> <x>,<y> |
Entry in endings |
AVA <sprite_id> |
Per-room avatar appearance |
TUNE <tune_id> |
Room music |
WAL <id>,… |
Legacy wall list |
Room format modes¶
room_format |
Tile row syntax | Example row |
|---|---|---|
1 (modern) |
Comma-separated ids | 0,a,a,0,0,0,0,0,0,0,0,0,0,a,0,0 |
0 (legacy) |
16 consecutive id chars | 0aaa000000000aa0 |
Both formats produce the same TileGrid in memory.
Exit¶
A warp tile that transports the avatar to another room when stepped on.
| Property | C++ type | Default | Description |
|---|---|---|---|
x |
int |
0 |
Source tile column in this room |
y |
int |
0 |
Source tile row in this room |
dest_room_id |
string |
— | Target room id |
dest_x |
int |
0 |
Avatar tile column in destination room |
dest_y |
int |
0 |
Avatar tile row in destination room |
transition_effect |
string |
"" |
Visual transition name (empty = instant) |
dialog_id |
string |
"" |
Optional dialog before transition |
File format¶
Syntax: EXT <src_x>,<src_y> <dest_room> <dest_x>,<dest_y> [TRANSITION <fx>] [DLG <id>]
Common transition effects include fade_w, fade_b, wave, tunnel, and slide_u / slide_d / slide_l / slide_r (rendered in video mode). none or a missing FX warps instantly.
When the avatar's tile position matches (x, y), the engine moves them to (dest_x, dest_y) in dest_room_id.
EndingRef¶
Links a tile in a room to an ending definition.
| Property | C++ type | Description |
|---|---|---|
ending_id |
string |
References an Ending definition |
x |
int |
Tile column |
y |
int |
Tile row |
File format¶
When the avatar steps on tile (x, y), the game triggers ending ending_id and displays its text.
Note:
ENDis overloaded in the file format. As a room sub-key, it defines a tile trigger (EndingRef). As a top-level segment, it defines the ending message (Ending). The parser distinguishes them by context.
Dialogue¶
A script attached to sprites, items, or exits. Dialog is Bitsy's lightweight game logic language — not just text display.
| Property | C++ type | Description |
|---|---|---|
id |
string |
Dialog identifier (e.g. DLG_0) |
content |
string |
Raw script text, preserved verbatim |
File format¶
Multiline content is stored with newline separators. The script may include:
| Feature | Example syntax | Status in citsy |
|---|---|---|
| Text lines | "Hello!" |
Yes |
| Variable interpolation | {print name} / {name} |
Yes |
| Assignment | {score = 5} |
Yes |
| Conditionals | {score}? … {else} {/} |
Yes (list form) |
| Sequence / cycle / shuffle lists | {sequence / {cycle / {shuffle |
Yes |
| Item actions | {item id} / {item id n} |
Yes |
| Exit / end / lock | {exit} {end} {lock} |
Yes |
| Avatar / palette / tune / blip | {ava} {pal} {tune} {blip} |
Yes |
| Drawings in dialog | {printSprite} {printTile} {printItem} |
Yes |
| Text effects | {wvy} {shk} {rbw} {clr} |
Yes |
citsy stores dialog content as raw text. DialogVM evaluates the script at runtime.
Variable¶
Global game state — numbers or strings that dialog scripts read and write.
| Property | C++ type | Description |
|---|---|---|
name |
string |
Variable name (also the map key) |
value |
string |
Initial value as text |
File format¶
Values are stored as strings. The runtime distinguishes numeric and string variables by whether the value parses as a number. Dialog scripts use variables for branching, counters, and state tracking.
Ending¶
The message displayed when the player reaches a game ending.
| Property | C++ type | Description |
|---|---|---|
id |
string |
Ending identifier |
text |
string |
End-game message (may be multiline) |
name |
string |
Display name (editor label) |
File format¶
Text lines come first; an optional NAME sub-key at the end sets the display name.
Entity relationships¶
┌─────────────┐
│ Game │
└──────┬──────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌───────────┐
│ Palette │ │ Tile │ │ Sprite │
└────┬────┘ └────┬─────┘ └─────┬─────┘
│ │ │
│ tile ids in grid │ dialog_id
│ │ ▼
│ ▼ ┌───────────┐
└────────►┌─────────┐ │ Dialogue │
│ Room │◄─────┤ │
└────┬────┘ └───────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌───────────┐
│ Item │ │ Exit │ │ EndingRef │
│(placed) │ │ │ │ │
└────┬────┘ └────┬────┘ └─────┬─────┘
│ │ │
│ dest_room_id │ ending_id
│ │ ▼
│ ▼ ┌─────────┐
│ (Room) │ Ending │
│ └─────────┘
▼
(Item def)
Reference summary¶
| From | Field | References |
|---|---|---|
Room |
palette_id |
Palette.id |
Room.tiles[y][x] |
tile id string | Tile.id |
RoomItem |
item_id |
Item.id |
RoomItem |
dialog_id |
Dialogue.id (optional override) |
Exit |
dest_room_id |
Room.id |
Exit |
dialog_id |
Dialogue.id |
EndingRef |
ending_id |
Ending.id |
Sprite |
dialog_id |
Dialogue.id |
Sprite.position |
room_id |
Room.id |
Item |
dialog_id |
Dialogue.id |
References are by string id. The parser does not validate that referenced ids exist — that is left to higher-level checks or runtime errors.
Segment reference¶
Quick lookup of top-level .bitsy segment keywords:
| Keyword | Entity | Id format | Example |
|---|---|---|---|
PAL |
Palette | string | PAL 0 |
TIL |
Tile | string | TIL a |
SPR |
Sprite | string | SPR A |
ITM |
Item | string | ITM 0 |
ROOM |
Room | string | ROOM 0 |
DLG |
Dialogue | string | DLG DLG_0 |
VAR |
Variable | name | VAR score |
END |
Ending | string | END 0 |
FONT |
Custom font | — | Parsed (.bitsyfont) |
DEFAULT_FONT |
Font name | — | ascii_small if omitted |
TEXT_DIRECTION |
LTR / RTL |
— | Right-to-left dialog layout |
TUNE |
Music | string | Melody + harmony bars |
EXT |
Exit (legacy top-level) | — | Skipped by citsy |
Room-level sub-keys (ITM, EXT, END, PAL, NAME, WAL) appear inside a ROOM segment body, not as top-level segments.
C++ struct quick reference¶
All types live in src/model/game.hpp (internal API).
struct Game {
GameVersion version;
int room_format;
std::string title;
std::unordered_map<std::string, Palette> palettes;
std::unordered_map<std::string, Tile> tiles;
std::unordered_map<std::string, Sprite> sprites;
std::unordered_map<std::string, Item> items;
std::unordered_map<std::string, Room> rooms;
std::unordered_map<std::string, Dialogue> dialogues;
std::unordered_map<std::string, Variable> variables;
std::unordered_map<std::string, Tune> tunes;
std::unordered_map<std::string, Blip> blips;
};
struct Palette { id, name, colors };
struct Tile { id, name, frames, is_wall, color_index };
struct Sprite { id, name, frames, position, dialog_id, color_index };
struct SpritePos { room_id, x, y };
struct Item { id, name, frames, dialog_id, color_index };
struct Room { id, name, tiles, palette_id, items, exits, endings };
struct RoomItem { item_id, x, y, dialog_id };
struct Exit { x, y, dest_room_id, dest_x, dest_y, transition_effect, dialog_id };
struct EndingRef { ending_id, x, y };
struct Dialogue { id, content };
struct Variable { name, value };
struct Ending { id, text, name };
struct GameVersion { major, minor };
using TileFrame = std::array<std::uint8_t, 64>;
using TileGrid = std::array<std::array<std::string, 16>, 16>;
Parse a file into this structure:
#include "src/parser/parser.hpp"
auto game = citsy::parse(bitsy_text); // throws ParseError on failure
Or via the public API:
Example: minimal game¶
See tests/data/minimal.bitsy for a complete game using every entity type. After parsing:
| Entity | Id | Key properties |
|---|---|---|
| Palette | 0 |
3 colors, name "palette" |
| Tile | a |
Wall tile with border pixel art |
| Room | 0 |
16×16 grid, item at (4,4), ending at (8,0) |
| Sprite | A |
Avatar at room 0, position (8,8) |
| Item | 0 |
Key with dialog DLG_0 |
| Dialogue | DLG_0 |
"Hello, world!" |
| Variable | score |
Initial value "0" |
| Ending | 0 |
"You found the treasure!" |
Compatibility notes¶
| Feature | Parsed by citsy | Simulated |
|---|---|---|
| Version header | Yes | — |
| Comma-separated rooms | Yes | Phase 1 |
| Legacy single-char rooms | Yes | Phase 1 |
| Multi-frame animation | Yes (stored) | Yes (400 ms) |
Extended palettes (COL n) |
Yes | Yes |
Wall tiles (WAL true) |
Yes | Yes |
| Variables | Yes | Yes |
| Dialog scripts | Yes | Yes (Bitsy 8.15 tags) |
| Exit transitions | Yes | Instant or FX in video mode |
Custom fonts (FONT) |
Yes | Yes |
TEXT_DIRECTION RTL |
Yes | Yes |
| Blips / tunes | Yes | Channel params each frame |
Room avatar (AVA) |
Yes | Yes |
See the roadmap for the project-level compatibility matrix.