tic80

    MoonBit bindings for TIC-80.

    wasm
    tic80
    fantasy console
    Download zip
    Author
    Version
    0.2.1
    License
    Apache-2.0
    Last updated
    2 days ago
    Downloads
    12

    #Milky2018/tic80-mbt

    #Installation

    Run the following command from the root of the cartridge module:

    moon add Milky2018/tic80

    This adds Milky2018/tic80 to the module's dependencies. Each cartridge package must also import it and use the linker configuration below.

    #TIC-80 runtime requirement

    Use a TIC-80 build based on the main branch of Milky2018/TIC-80. This branch updates the bundled wasm3 runtime and its TIC-80 integration to support the imported linear memory emitted by the current MoonBit toolchain.

    TIC-80 builds without these changes may reject a MoonBit cartridge with only one memory per module is supported or unallocated linear memory, even when the cartridge package uses the required linker configuration below.

    #Configuring a cartridge package

    Every package that builds a TIC-80 WebAssembly cartridge must import this package, use the foreign_library package kind, and configure the WebAssembly linker to use TIC-80's imported linear memory. Add the following declarations to the cartridge package's moon.pkg file:

    import { "Milky2018/tic80", } pkgtype(kind: "foreign_library") options( link: { "wasm": { "import-memory": { "module": "env", "name": "memory" }, "memory-limits": { "min": 4, "max": 4 }, "heap-start-address": 98304, }, }, )

    Important: Do not omit pkgtype(kind: "foreign_library") from a cartridge package. It tells MoonBit to link the package as a foreign library whose exported callbacks can be loaded by TIC-80, rather than as a standalone program with a MoonBit main entry point.

    This configuration has five responsibilities:

    • import { "Milky2018/tic80" } makes the TIC-80 API available through the @tic80 package qualifier.
    • pkgtype(kind: "foreign_library") builds a library-style WebAssembly module whose #export_name callbacks can be discovered by TIC-80. The cartridge is not a standalone WASI executable and does not define a MoonBit main entry point.
    • "import-memory": { "module": "env", "name": "memory" } makes the module import the env.memory linear memory supplied by TIC-80 instead of defining its own memory.
    • "memory-limits": { "min": 4, "max": 4 } declares that the imported memory is exactly four WebAssembly pages (256 KiB), matching TIC-80's limit. The first 96 KiB belongs to TIC-80 RAM, leaving at most 160 KiB for MoonBit static data, allocator metadata, and heap allocations.
    • "heap-start-address": 98304 reserves the first 96 KiB (0x18000) of that memory for TIC-80's RAM layout and starts MoonBit heap allocation after it. TIC-80 copies VRAM, tiles, sprites, map data, input state, audio state, and the rest of its runtime RAM into this region before calling the cartridge.

    These settings belong to each cartridge package rather than to the reusable Milky2018/tic80 API package itself.

    #Building and running a cartridge

    Run moon build from the cartridge module's root directory. If the cartridge is the root package, no package path is needed:

    moon build --target wasm --release

    If the cartridge is a subpackage, pass the directory containing its moon.pkg file. For example, a package in game/ can be built with:

    moon build --target wasm --release game

    MoonBit places foreign-library output under _build/wasm/release/build/. In the game/ example, the cartridge is normally written to _build/wasm/release/build/game/game.wasm.

    For a quick run that does not preserve project-specific sprites, maps, audio, or other resources, create a fresh WASM cartridge, import the compiled binary, and run it:

    tic80 --skip --fs . --cmd 'new wasm & import binary _build/wasm/release/build/game/game.wasm & run'

    --fs . makes the current project directory visible to TIC-80. new wasm creates a temporary WASM cartridge, import binary replaces its executable code with the MoonBit build output, and run starts the cartridge. Omit --cli and exit for interactive play so that the TIC-80 window remains open.

    #Using a .wasmp project

    A .wasmp file is TIC-80's text project format for a WASM cartridge. It stores cartridge metadata and editable resource sections such as tiles, sprites, maps, flags, palettes, SFX, and music. It does not embed the compiled WASM binary, so the current MoonBit output must be imported before each run.

    Loading and saving text project files is a TIC-80 Pro feature. With a Pro build, start TIC-80 in the game project's directory:

    tic80 --fs .

    Then create and save a WASM project from the TIC-80 console:

    new wasm save game.wasmp

    Open the TIC-80 editors with Escape or F1, edit the cartridge resources, and use Ctrl+S to write the changes back to game.wasmp. Keep this file in version control alongside the MoonBit source; keep generated _build/ output ignored.

    The normal development loop loads those resources, imports the newly compiled WASM, and runs the combined cartridge:

    moon build --target wasm --release game tic80 --skip --fs . --cmd 'load game.wasmp & import binary _build/wasm/release/build/game/game.wasm & run'

    The same two commands can be placed in the game project's own run.sh for one-command development. The binding library does not provide a fixed script because the package path, output filename, and project filename belong to each game.

    To create a distributable binary cartridge that embeds both the .wasmp resources and the compiled WASM, run:

    tic80 --cli --fs . --cmd 'load game.wasmp & import binary _build/wasm/release/build/game/game.wasm & save game.tic & exit'

    The free edition cannot load or save .wasmp text projects. It can still use .tic cartridges: create one with new wasm, import the compiled binary, save it as game.tic, and subsequently load that .tic file before importing newer builds.

    #Low-overhead ASCII text

    The regular print, font, trace, and abort functions accept StringView and encode it as ASCII for TIC-80. Cartridges that only use known ASCII byte strings can instead call print_ascii, font_ascii, trace_ascii, and abort_ascii. These variants avoid ASCII encoding and validation, allowing the linker to remove ascii.encode when none of the regular text functions are reachable.

    The _ascii variants accept Bytes directly and do not validate them. Every value must contain only ASCII bytes; the wrapper supplies the NUL terminator required by TIC-80:

    @tic80.print_ascii(b"SCORE", x=8, y=8)

    When a value already ends with NUL, it is passed to TIC-80 without copying. Otherwise the wrapper creates a terminated copy. An earlier NUL truncates the text. Use the regular StringView APIs unless cartridge size makes this lower-level contract worthwhile.

    #Video banks

    TIC-80 provides two 16 KiB video-memory banks, represented by VideoBank. Drawing commands and direct access to VRAM operate on the currently selected bank. Bank1 is composited over Bank0; pixels matching Bank1's clear color are transparent and reveal Bank0 underneath.

    Use vbank() to query the current drawing target without changing it. Use vbank_set() to select a target; it returns the previously selected bank so a temporary switch can be restored without assuming which bank was active:

    let previous = @tic80.vbank_set(Bank1)
    // Drawing here targets Bank1.
    @tic80.cls(0)
    @tic80.print("overlay", x=8, y=8)
    ignore(@tic80.vbank_set(previous))

    Video banks are available in both the free and Pro editions. They are separate from the cartridge resource banks addressed by the bank argument of sync(): vbank_set() changes the active VRAM drawing target, while sync() copies tiles, sprites, maps, audio, palettes, flags, or screen data between a cartridge resource bank and runtime memory.

    #Cartridge callbacks

    TIC-80 cartridge callbacks are functions exported by the WebAssembly module and called by TIC-80. They are the reverse of the functions in the raw API: the cartridge calls imported TIC-80 APIs such as cls, map, and spr, while TIC-80 calls these exported lifecycle functions.

    The WebAssembly ABI signatures are:

    CallbackWebAssembly signatureRequiredPurpose
    BOOT() -> voidNoInitialize the cartridge once.
    TIC() -> voidYesUpdate and draw one frame at 60 FPS.
    SCN(i32) -> voidNoApply per-scanline effects to the 240x136 game area.
    BDR(i32) -> voidNoApply per-scanline effects to the full 256x144 output, including the border.
    MENU(i32) -> voidNoHandle a custom game-menu selection.

    Export names are case-sensitive. A MoonBit function may use an idiomatic lowercase name internally, but its #export_name must use the uppercase name expected by TIC-80.

    #Lifecycle

    The first frame follows this sequence:

    load cartridge -> initialize the WebAssembly VM -> BOOT() -> TIC() -> BDR(row) and SCN(row) while the frame is presented

    Subsequent frames call TIC() and then the scanline callbacks. MENU(index) is only called when the player selects a custom game-menu item.

    #BOOT()

    BOOT() is called once after the WebAssembly VM has been initialized and before the first call to TIC(). It is suitable for one-time state, resource bank, and palette initialization.

    ///|
    #export_name("BOOT")
    pub fn boot() -> Unit {
    // One-time initialization.
    }

    #TIC()

    TIC() is the required main callback. TIC-80 calls it once per frame at 60 FPS. Input handling, game-state updates, and ordinary drawing normally belong here.

    ///|
    #export_name("TIC")
    pub fn tic() -> Unit {
    @tic80.cls(0)
    @tic80.map()
    @tic80.spr(1, 100, 60)
    }

    The cartridge fails to load if its WebAssembly module does not export TIC.

    #SCN(row)

    SCN(row) is called for each scanline in the 240x136 game area. Its row argument ranges from 0 through 135.

    Use it for raster effects that only affect the game area, such as per-line palette changes, gradients, water distortion, or horizontal scrolling. The frame should normally be drawn in TIC() first; SCN() then adjusts display state while that frame is presented.

    ///|
    #export_name("SCN")
    pub fn scn(row : Int) -> Unit {
    // Adjust palette or screen-offset state for this game-area row.
    }

    #BDR(row)

    BDR(row) is called for every scanline in the complete 256x144 output. Its row argument ranges from 0 through 143. The complete output consists of the 240x136 game area plus 8-pixel left and right borders and 4-pixel top and bottom borders.

    Use SCN() when an effect should be limited to the game area. Use BDR() when the effect must also control the border or otherwise cover the complete output. TIC-80 processes BDR() before SCN() on scanlines where both callbacks participate, and applies palette changes before presenting the line.

    ///|
    #export_name("BDR")
    pub fn bdr(row : Int) -> Unit {
    // Adjust palette or border state for this full-output row.
    }

    MENU(index) handles selections from the cartridge's custom game menu. Menu items are declared by the menu cartridge metadata, for example in a .wasmp project:

    -- menu: RESTART MUSIC DIFFICULTY

    The selected item is passed as a zero-based index:

    SelectionCallback
    RESTARTMENU(0)
    MUSICMENU(1)
    DIFFICULTYMENU(2)

    ///|
    #export_name("MENU")
    pub fn menu(index : Int) -> Unit {
    match index {
    0 => restart_game()
    1 => toggle_music()
    _ => ()
    }
    }

    If the cartridge does not declare custom menu items, MENU() is not called.

    #Minimal callback set

    Most cartridges only need BOOT() and TIC() initially. Add SCN(), BDR(), or MENU() only when the cartridge uses their corresponding features.

    Button

    pub(all) enum Button {
    P1Up
    P1Down
    P1Left
    P1Right
    P1A
    P1B
    P1X
    P1Y
    P2Up
    P2Down
    P2Left
    P2Right
    P2A
    P2B
    P2X
    P2Y
    P3Up
    P3Down
    P3Left
    P3Right
    P3A
    P3B
    P3X
    P3Y
    P4Up
    P4Down
    P4Left
    P4Right
    P4A
    P4B
    P4X
    P4Y
    } derive(Eq,
    Debug
    )

    A button on one of the four TIC-80 gamepads.

    The explicit tags are the button IDs expected by the TIC-80 host ABI.

    ButtonState

    pub struct ButtonState {
    // private fields
    } derive(Eq,
    Debug
    )

    An immutable snapshot of all 32 TIC-80 gamepad buttons.

    ButtonState::any

    fn ButtonState::any(self : ButtonState) -> Bool

    Returns whether this snapshot contains any held button.

    ButtonState::held

    fn ButtonState::held(self : ButtonState, button : Button) -> Bool

    Returns whether the selected button was held in this snapshot.

    Flip

    pub(all) enum Flip {
    NoFlip
    Horizontal
    Vertical
    Both
    } derive(Eq,
    Debug
    )

    A sprite or map tile flip applied by TIC-80.

    Key

    pub(all) enum Key {
    A
    B
    C
    D
    E
    F
    G
    H
    I
    J
    K
    L
    M
    N
    O
    P
    Q
    R
    S
    T
    U
    V
    W
    X
    Y
    Z
    Digit0
    Digit1
    Digit2
    Digit3
    Digit4
    Digit5
    Digit6
    Digit7
    Digit8
    Digit9
    Minus
    Equals
    LeftBracket
    RightBracket
    Backslash
    Semicolon
    Apostrophe
    Grave
    Comma
    Period
    Slash
    Space
    Tab
    Return
    Backspace
    Delete
    Insert
    PageUp
    PageDown
    Home
    End
    Up
    Down
    Left
    Right
    CapsLock
    Ctrl
    Shift
    Alt
    Escape
    F1
    F2
    F3
    F4
    F5
    F6
    F7
    F8
    F9
    F10
    F11
    F12
    Numpad0
    Numpad1
    Numpad2
    Numpad3
    Numpad4
    Numpad5
    Numpad6
    Numpad7
    Numpad8
    Numpad9
    NumpadPlus
    NumpadMinus
    NumpadMultiply
    NumpadDivide
    NumpadEnter
    NumpadPeriod
    } derive(Eq,
    Debug
    )

    A TIC-80 keyboard key.

    The explicit tags are the key codes expected by the TIC-80 host ABI.

    MapRemapResult

    pub(all) struct MapRemapResult {
    tile_id : Int
    flip : Flip
    rotate : Rotation
    } derive(Eq,
    Debug
    )

    The current tile state passed to a map remap callback, and the replacement state returned by it.

    The callback may replace tile_id, apply a flip, apply a clockwise rotate, or return the value unchanged.

    MouseState

    pub(all) struct MouseState {
    x : Int
    y : Int
    scroll_x : Int
    scroll_y : Int
    left : Bool
    middle : Bool
    right : Bool
    } derive(Eq,
    Debug
    )

    A snapshot of the mouse cursor, wheel, and button state.

    x and y are screen coordinates. scroll_x and scroll_y are signed wheel movements for the current frame. left, middle, and right report whether the corresponding button is pressed.

    Rotation

    pub(all) enum Rotation {
    NoRotation
    Clockwise90
    Clockwise180
    Clockwise270
    } derive(Eq,
    Debug
    )

    A clockwise sprite or map tile rotation applied by TIC-80.

    SfxChannel

    pub(all) enum SfxChannel {
    Channel0
    Channel1
    Channel2
    Channel3
    } derive(Eq,
    Debug
    )

    One of TIC-80's four sound-effect channels.

    The explicit tags are the channel IDs expected by the TIC-80 host ABI.

    SyncSection

    pub(all) enum SyncSection {
    Tiles
    Sprites
    Map
    Sfx
    Music
    Palette
    Flags
    Screen
    } derive(Eq,
    Debug
    )

    A cartridge resource section that can be transferred by sync.

    Each variant corresponds to one bit in TIC-80's resource synchronization mask.

    TextureSource

    pub(all) enum TextureSource {
    TileSheet
    TileMap
    OtherVideoBank
    } derive(Eq,
    Debug
    )

    The texture sampled by ttri.

    TileSheet samples image RAM through the active tile or sprite-sheet segment. TileMap samples map RAM and resolves its tile IDs through image RAM. OtherVideoBank samples the framebuffer of the video bank that is not currently selected by vbank_set.

    VideoBank

    pub(all) enum VideoBank {
    Bank0
    Bank1
    } derive(Eq,
    Debug
    )

    One of TIC-80's two 16 KiB video-memory banks.

    Drawing commands and direct VRAM access operate on the selected bank. Bank1 is composited over Bank0; pixels matching Bank1's clear color are transparent and reveal Bank0. Video banks are always available and are unrelated to the cartridge resource banks selected by sync.

    abort

    fn[T] abort(message : StringView, color? : Int) -> T

    Writes an ASCII message to the TIC-80 console and then panics.

    This is useful for reporting a fatal error before trapping the cartridge. color has the same meaning and default as in trace. Panics while encoding the message if it contains a non-ASCII code unit.

    abort_ascii

    fn[T] abort_ascii(message : Bytes, color? : Int) -> T

    Writes an ASCII message to the TIC-80 console and then panics, without ASCII encoding or validation.

    This is the low-overhead counterpart of abort. The caller must ensure that message contains only ASCII bytes. The wrapper appends the NUL terminator required by TIC-80 when it is missing; an already terminated value is passed without copying. An earlier NUL truncates the message. color has the same meaning and default as in trace.

    any_btn

    fn any_btn() -> Bool

    Returns whether any button on any of the four gamepads is currently pressed.

    As with btn, the result remains true while at least one button is held.

    any_btnp

    fn any_btnp() -> Bool

    Returns whether any gamepad button was newly pressed since the previous frame. This convenience wrapper does not enable repeat timing.

    any_key

    fn any_key() -> Bool

    Returns whether any keyboard key is currently pressed.

    any_keyp

    fn any_keyp() -> Bool

    Returns whether any keyboard key was newly pressed since the previous frame. This convenience wrapper does not enable repeat timing.

    btn

    fn btn(button : Button) -> Bool

    Returns whether the selected gamepad button is currently pressed.

    The result remains true for as long as the button is held. Use btnp when only a new press, or a controlled key-repeat style event, should be reported.

    btnp

    fn btnp(button : Button, hold? : Int, period? : Int) -> Bool

    Returns whether button was newly pressed, with optional repeat timing.

    Without hold and period, this is true only when the button is pressed in the current frame but was not pressed in the previous frame. Both timing values are measured in 60 Hz ticks. After hold ticks, a still-held button produces another true result every period ticks. For example, btnp(button, hold=120, period=6) starts repeating after two seconds and then repeats ten times per second. Leaving both values at -1 disables repeat.

    button_state

    fn button_state() -> ButtonState

    Captures the current state of all four gamepads in one host call.

    The returned value does not change as input changes; call button_state again to obtain a new snapshot.

    circ

    fn circ(x : Int, y : Int, radius : Int, color : Int) -> Unit

    Draws a filled circle of radius in color, centered at (x, y).

    TIC-80 rasterizes the circle with the Bresenham algorithm. Use circb to draw only its circumference.

    circb

    fn circb(x : Int, y : Int, radius : Int, color : Int) -> Unit

    Draws a one-pixel circle circumference of radius in color, centered at (x, y).

    TIC-80 rasterizes the circle with the Bresenham algorithm. Use circ for a filled circle.

    clip

    fn clip(x : Int, y : Int, w : Int, h : Int) -> Unit

    Restricts subsequent drawing to the rectangle at (x, y) with size w by h.

    Pixels drawn outside this viewport are not visible. The clipping rectangle remains active until another call to clip or clip_reset.

    clip_reset

    fn clip_reset() -> Unit

    Restores the clipping rectangle to the entire screen.

    cls

    fn cls(color? : Int) -> Unit

    Clears the entire screen with color, which defaults to palette color 0.

    elli

    fn elli(x : Int, y : Int, a : Int, b : Int, color : Int) -> Unit

    Draws a filled ellipse in color, centered at (x, y) with horizontal and vertical radii a and b.

    TIC-80 rasterizes the ellipse with the Bresenham algorithm. Use ellib to draw only its border.

    ellib

    fn ellib(x : Int, y : Int, a : Int, b : Int, color : Int) -> Unit

    Draws a one-pixel ellipse border in color, centered at (x, y) with horizontal and vertical radii a and b.

    TIC-80 rasterizes the ellipse with the Bresenham algorithm. Use elli for a filled ellipse.

    exit

    fn exit() -> Unit

    Interrupts cartridge execution and returns to the TIC-80 console after the current TIC callback ends.

    fget

    fn fget(sprite_id : Int, flag : Int) -> Bool

    Returns whether one of a sprite's eight user-defined flags is set.

    sprite_id ranges from 0 through 511 and flag ranges from 0 through 7. Flag meanings are defined entirely by the cartridge; for example, a game might use one flag for solid tiles and another for hazards. Panics when either argument is outside its valid range.

    font

    fn font(text : StringView, x : Int, y : Int, transparent_colors? : Bytes, char_width? : Int, char_height? : Int, fixed? : Bool, scale? : Int, alt? : Bool) -> Int

    Draws ASCII text using a custom raster font stored in foreground sprites and returns the rendered width.

    char_width and char_height select each glyph's dimensions, fixed selects fixed-width layout, scale enlarges the glyphs, and alt selects the alternate 128-character glyph set. Every palette index in transparent_colors is treated as transparent; an empty value draws the font opaquely. Use print for TIC-80's configured built-in font and trace for console output. Panics if text contains a non-ASCII code unit.

    font_ascii

    fn font_ascii(text : Bytes, x : Int, y : Int, transparent_colors? : Bytes, char_width? : Int, char_height? : Int, fixed? : Bool, scale? : Int, alt? : Bool) -> Int

    Draws ASCII bytes using a custom raster font and returns the rendered width, without ASCII encoding or validation.

    This is the low-overhead counterpart of font. The caller must ensure that text contains only ASCII bytes. The wrapper appends the NUL terminator required by TIC-80 when it is missing; an already terminated value is passed without copying. An earlier NUL truncates the text. The remaining arguments have the same meaning as in font.

    fset

    fn fset(sprite_id : Int, flag : Int, value : Bool) -> Unit

    Sets or clears one of a sprite's eight user-defined flags.

    sprite_id ranges from 0 through 511 and flag ranges from 0 through 7. TIC-80 does not prescribe meanings for these flags: a cartridge might use flag 0 for invisible sprites or flag 6 for sprites that should be scaled. The change affects runtime RAM; use sync with the Flags section and to_cart=true to copy runtime flags back to a cartridge resource bank. Panics when either argument is outside its valid range.

    key

    fn key(key : Key) -> Bool

    Returns whether the selected keyboard key is currently pressed.

    The result remains true for as long as the key is held. Use keyp when only a new press, or a controlled key-repeat style event, should be reported.

    keyp

    fn keyp(key : Key, hold? : Int, period? : Int) -> Bool

    Returns whether key was newly pressed, with optional repeat timing.

    Without hold and period, this is true only when the key is pressed in the current frame but was not pressed in the previous frame. Both timing values are measured in 60 Hz ticks. After hold ticks, a still-held key produces another true result every period ticks. Leaving both values at -1 disables repeat. This is the keyboard counterpart of btnp.

    line

    fn line(x0 : Float, y0 : Float, x1 : Float, y1 : Float, color : Int) -> Unit

    Draws a straight line in color from (x0, y0) to (x1, y1).

    map

    fn map(x? : Int, y? : Int, w? : Int, h? : Int, sx? : Int, sy? : Int, transparent_colors? : Bytes, scale? : Int, remap? : FuncRef[(MapRemapResult, Int, Int) -> MapRemapResult]) -> Unit

    Draws a rectangular region of TIC-80's tile map at a screen position.

    The map contains 8 by 8 pixel cells and can be up to 240 cells wide by 136 cells high. (x, y) is the first map cell, (w, h) is the region size in cells, and (sx, sy) is its destination in screen pixels. The effective defaults selected by -1 are (x, y) = (0, 0), (w, h) = (30, 17), and scale = 1. Every palette index in transparent_colors is skipped; an empty value draws tiles opaquely.

    When present, remap receives the current tile state and its map coordinates, and returns the state to draw. It can replace, flip, rotate, or hide tiles for this draw without changing map RAM, which is useful for animated tiles, doors, and object-spawn markers. The callback must be capture-free because it is stored as a WebAssembly function reference. Use mset for persistent runtime-map changes.

    Map cells are stored sequentially from byte address 0x08000; adjacent rows are 240 bytes apart. For example, the cell below the top-left cell is at 0x08000 + 240, or 0x080f0.

    memcpy

    fn memcpy(dest : Int, src : Int, length : Int) -> Unit

    Copies length bytes within TIC-80's 96 KiB RAM.

    dest and src are byte addresses. The source and destination ranges may overlap. A negative length or a range outside TIC-80 RAM is ignored. This function is useful for moving runtime sprites, maps, sounds, and other cartridge data, but does not copy MoonBit heap objects or Bytes values.

    memset

    fn memset(address : Int, value : Byte, length : Int) -> Unit

    Fills length bytes of TIC-80 RAM with value.

    address is a byte address. A negative length or a range outside TIC-80 RAM is ignored. This can modify any runtime resource represented in TIC-80 RAM, but does not operate on MoonBit heap objects or Bytes values.

    mget

    fn mget(x : Int, y : Int) -> Int

    Returns the sprite ID stored at map coordinates (x, y).

    mouse

    fn mouse() -> MouseState

    Returns an independent snapshot of the current mouse coordinates, wheel movement, and button state.

    mset

    fn mset(x : Int, y : Int, tile_id : Int) -> Unit

    Changes the sprite ID at map coordinates (x, y) in runtime map RAM.

    The change normally lasts only while the cartridge is running. To save it to a cartridge resource bank, call sync with the Map section and to_cart=true.

    music

    fn music(track : Int, frame? : Int, row? : Int, loop_? : Bool, sustain? : Bool, tempo? : Int, speed? : Int) -> Unit

    Starts playing one of the eight tracks created in TIC-80's Music Editor.

    track ranges from 0 through 7. By default playback starts at the beginning of the track, loops after its final populated frame, does not sustain notes across frame boundaries, and uses the tempo and speed stored in the track. Use frame and row to start at a specific position, or tempo and speed to override the track settings. Call music_stop to stop playback. Panics if track is outside the range 0 through 7.

    music_stop

    fn music_stop() -> Unit

    Stops the currently playing music and resets its channels.

    This does not stop sound effects started independently with sfx.

    peek

    fn peek(address : Int) -> Byte

    Reads one byte from TIC-80 RAM at the byte address address.

    Returns zero when address is outside TIC-80 RAM. Use peek4, peek2, or peek1 when the address is expressed in narrower units.

    peek1

    fn peek1(address : Int) -> Byte

    Reads one bit from TIC-80 RAM.

    address is a bit index. Addresses 8 * n through 8 * n + 7 select the bits of byte n from least to most significant. Returns zero when the address is outside TIC-80 RAM.

    peek2

    fn peek2(address : Int) -> Byte

    Reads one two-bit value from TIC-80 RAM.

    address is a two-bit-field index. Addresses 4 * n through 4 * n + 3 select the four fields of byte n from least to most significant. Returns zero when the address is outside TIC-80 RAM.

    peek4

    fn peek4(address : Int) -> Byte

    Reads one four-bit nibble from TIC-80 RAM.

    address is a nibble index rather than a byte address. Addresses 2 * n and 2 * n + 1 select the low and high nibbles of byte n, respectively. Returns zero when the address is outside TIC-80 RAM.

    pix

    fn pix(x : Int, y : Int) -> Byte

    Returns the palette color at screen coordinates (x, y).

    pix_set

    fn pix_set(x : Int, y : Int, color : Int) -> Unit

    Sets the pixel at screen coordinates (x, y) to color.

    pmem

    fn pmem(index : Int) -> UInt

    Reads a value from a persistent-memory slot.

    A cartridge has 256 unsigned 32-bit slots, suitable for high scores, level progress, achievements, and other small saved values. index ranges from 0 through 255. Panics when it is outside that range.

    By default the save is associated with the cartridge hash, so changing the cartridge can create a different save namespace. Set saveid: in cartridge metadata to keep a stable namespace across cartridge updates.

    pmem_set

    fn pmem_set(index : Int, value : UInt) -> UInt

    Writes a persistent-memory slot and returns its previous value.

    Values are unsigned 32-bit integers. index ranges from 0 through 255. Panics when it is outside that range. See pmem for save identity details.

    poke

    fn poke(address : Int, value : Byte) -> Unit

    Writes one byte to TIC-80 RAM at the byte address address.

    Writes outside TIC-80 RAM are ignored. Use poke4, poke2, or poke1 when the address is expressed in narrower units.

    poke1

    fn poke1(address : Int, value : Byte) -> Unit

    Writes one bit to TIC-80 RAM.

    address uses the bit indexing described by peek1; only the lowest bit of value is stored. Writes outside TIC-80 RAM are ignored.

    poke2

    fn poke2(address : Int, value : Byte) -> Unit

    Writes one two-bit value to TIC-80 RAM.

    address uses the two-bit-field indexing described by peek2; only the low two bits of value are stored. Writes outside TIC-80 RAM are ignored.

    poke4

    fn poke4(address : Int, value : Byte) -> Unit

    Writes one four-bit nibble to TIC-80 RAM.

    address uses the nibble indexing described by peek4; only the low four bits of value are stored. Writes outside TIC-80 RAM are ignored.

    print

    fn print(text : StringView, x? : Int, y? : Int, color? : Int, fixed? : Bool, scale? : Int, small_font? : Bool) -> Int

    Draws ASCII text using TIC-80's configured font and returns its width.

    When fixed is true, every character occupies an equal-width box, so a narrow glyph such as i advances by the same amount as w. When false, proportional glyph widths are used with one pixel of spacing. scale enlarges the text, and small_font selects TIC-80's small built-in font. Use font for a custom raster font or trace for console output. Panics if text contains a non-ASCII code unit.
    fn print_ascii(text : Bytes, x? : Int, y? : Int, color? : Int, fixed? : Bool, scale? : Int, small_font? : Bool) -> Int

    Draws ASCII bytes using TIC-80's configured font and returns the rendered width, without ASCII encoding or validation.

    This is the low-overhead counterpart of print. The caller must ensure that text contains only ASCII bytes. The wrapper appends the NUL terminator required by TIC-80 when it is missing; an already terminated value is passed without copying. An earlier NUL truncates the text. The remaining arguments have the same meaning as in print.

    rect

    fn rect(x : Int, y : Int, w : Int, h : Int, color : Int) -> Unit

    Draws a filled rectangle in color at (x, y) with size w by h. Use rectb when only a one-pixel border is needed.

    rectb

    fn rectb(x : Int, y : Int, w : Int, h : Int, color : Int) -> Unit

    Draws a one-pixel rectangle border in color at (x, y) with size w by h. Use rect for a filled rectangle.

    sfx

    fn sfx(id : Int, note? : Int, duration? : Int, channel? : SfxChannel, volume_left? : Int, volume_right? : Int, speed? : Int) -> Unit

    Plays one of the 64 effects created in TIC-80's SFX Editor.

    note is a combined note number from 0 through 95, with twelve notes per octave. The values in each octave are C, C sharp, D, D sharp, E, F, F sharp, G, G sharp, A, A sharp, and B; flat names are not represented separately. For example, 14 is D in the second octave. When note is omitted, the note stored in the selected effect is used. duration is measured in 60 Hz ticks; -1 plays continuously. The left and right volumes range from 0 through 15. When speed is omitted, the speed stored in the selected effect is used; explicit speeds range from -4 through 3. Call sfx_stop to stop an effect on a channel.

    Panics if id is outside the range 0 through 63.

    sfx_stop

    fn sfx_stop(channel? : SfxChannel) -> Unit

    Stops the sound effect playing on channel.

    This does not stop music playback or effects on the other channels.

    spr

    fn spr(id : Int, x : Int, y : Int, transparent_colors? : Bytes, scale? : Int, flip? : Flip, rotate? : Rotation, w? : Int, h? : Int) -> Unit

    Draws a sprite, or a rectangular region of sprites, at (x, y).

    id selects the top-left sprite. w and h select a composite rectangular region in sprite units. scale = 2, for example, draws each 8 by 8 sprite in a 16 by 16 pixel area. flip mirrors the result and rotate rotates it clockwise in 90-degree steps. Every palette index in transparent_colors is skipped; an empty value draws the sprite opaquely.

    sync

    fn sync(sections? : ArrayView[SyncSection], bank? : Int, to_cart? : Bool) -> Unit

    Synchronizes cartridge resources with runtime memory. An empty sections list selects every section. By default, data is copied from bank 0 into runtime memory; set to_cart to copy runtime data back to the bank. Each call transfers the requested tiles, sprites, map, sound effects, music, palette, flags, and/or screen data.

    TIC-80 Pro cartridges provide eight resource banks. Each resource section can be synchronized at most once per frame; later calls in the same frame may still transfer sections not selected earlier. Calling sync(bank=0) with no sections restores all runtime resources from bank 0. Cartridge code is not loaded by sync; TIC-80 handles code-bank loading automatically.

    time

    fn time() -> Float

    Returns the elapsed time in milliseconds since the cartridge started.

    This is useful for animation, elapsed-time tracking, and timed events.

    trace

    fn trace(text : StringView, color? : Int) -> Unit

    Writes ASCII text to the TIC-80 console in color.

    This is intended for debugging. The default color is palette color 15; use the console's cls command to clear accumulated trace output. Panics if text contains a non-ASCII code unit.

    trace_ascii

    fn trace_ascii(text : Bytes, color? : Int) -> Unit

    Writes ASCII bytes to the TIC-80 console without ASCII encoding or validation.

    This is the low-overhead counterpart of trace. The caller must ensure that text contains only ASCII bytes. The wrapper appends the NUL terminator required by TIC-80 when it is missing; an already terminated value is passed without copying. An earlier NUL truncates the text. color has the same meaning and default as in trace.

    tri

    fn tri(x1 : Float, y1 : Float, x2 : Float, y2 : Float, x3 : Float, y3 : Float, color : Int) -> Unit

    Draws a triangle filled with color using the three supplied vertices.

    trib

    fn trib(x1 : Float, y1 : Float, x2 : Float, y2 : Float, x3 : Float, y3 : Float, color : Int) -> Unit

    Draws a one-pixel triangle border in color using the three supplied vertices.

    tstamp

    fn tstamp() -> UInt

    Returns the current Unix timestamp in seconds.

    The timestamp counts seconds since 1970-01-01 00:00:00 UTC and can be used for cartridge state that evolves between play sessions.

    ttri

    fn ttri(x1 : Float, y1 : Float, x2 : Float, y2 : Float, x3 : Float, y3 : Float, u1 : Float, v1 : Float, u2 : Float, v2 : Float, u3 : Float, v3 : Float, texture_source? : TextureSource, transparent_colors? : Bytes, z1? : Float, z2? : Float, z3? : Float, depth? : Bool) -> Unit

    Draws a triangle textured from texture_source.

    The u and v coordinates are interpreted in the coordinate space of the selected source. Image RAM and map RAM are treated as single large images, and the coordinates address pixels rather than sprite IDs. For example, the top-left corner of sprite 2 is at (u, v) = (16, 0).

    z1, z2, and z3 provide per-vertex depth for perspective correction; triangles whose vertices have different depths can otherwise appear distorted. Set depth to use TIC-80's depth buffer. When OtherVideoBank is selected, ttri samples whichever video bank is not currently selected: it normally samples Bank1 while drawing to Bank0, and samples Bank0 while drawing to Bank1. Every palette index in transparent_colors is skipped; an empty value draws the texture opaquely.

    vbank

    fn vbank() -> VideoBank

    Returns the currently selected video bank without changing it.

    Use vbank_set when subsequent drawing or direct VRAM operations should target a different bank.

    vbank_set

    fn vbank_set(bank : VideoBank) -> VideoBank

    Selects the video bank used by subsequent drawing and direct VRAM operations, and returns the previously selected bank.

    The return value makes temporary switching straightforward: save it, draw into another bank, then pass it back to vbank_set to restore the previous target. This only switches VRAM; it does not select a cartridge resource bank for sync.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io