arcane-core 0.26.1

Core library for Arcane - agent-native 2D game engine (TypeScript runtime, renderer, platform layer)
Documentation
/// Built-in CP437 8×8 bitmap font.
///
/// Covers ASCII 32 (space) through 127 (DEL) = 96 glyphs.
/// Layout: 16 columns × 6 rows, each glyph 8×8 pixels.
/// Result texture: 128×48 pixels RGBA.

/// 96 glyphs × 8 bytes each. Each byte is one row of 8 pixels (MSB = left).
/// Glyph order: ASCII 32..127 (space, !, ", #, ..., ~, DEL).
const FONT_DATA: [u8; 768] = [
    // 32: space
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    // 33: !
    0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00,
    // 34: "
    0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00,
    // 35: #
    0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00,
    // 36: $
    0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00,
    // 37: %
    0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00,
    // 38: &
    0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00,
    // 39: '
    0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
    // 40: (
    0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00,
    // 41: )
    0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00,
    // 42: *
    0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00,
    // 43: +
    0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00,
    // 44: ,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30,
    // 45: -
    0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
    // 46: .
    0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00,
    // 47: /
    0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00,
    // 48: 0
    0x7C, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0x7C, 0x00,
    // 49: 1
    0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00,
    // 50: 2
    0x7C, 0xC6, 0x06, 0x7C, 0xC0, 0xC0, 0xFE, 0x00,
    // 51: 3
    0x7C, 0xC6, 0x06, 0x3C, 0x06, 0xC6, 0x7C, 0x00,
    // 52: 4
    0x0C, 0x2C, 0x4C, 0x8C, 0xFE, 0x0C, 0x0C, 0x00,
    // 53: 5
    0xFE, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00,
    // 54: 6
    0x7C, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00,
    // 55: 7
    0xFE, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00,
    // 56: 8
    0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00,
    // 57: 9
    0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x7C, 0x00,
    // 58: :
    0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00,
    // 59: ;
    0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30, 0x00,
    // 60: <
    0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00,
    // 61: =
    0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00,
    // 62: >
    0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x00,
    // 63: ?
    0x7C, 0xC6, 0x0C, 0x18, 0x18, 0x00, 0x18, 0x00,
    // 64: @
    0x7C, 0xC6, 0xDE, 0xDE, 0xDC, 0xC0, 0x7C, 0x00,
    // 65: A
    0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00,
    // 66: B
    0xFC, 0xC6, 0xC6, 0xFC, 0xC6, 0xC6, 0xFC, 0x00,
    // 67: C
    0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00,
    // 68: D
    0xF8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0xF8, 0x00,
    // 69: E
    0xFE, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xFE, 0x00,
    // 70: F
    0xFE, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xC0, 0x00,
    // 71: G
    0x7C, 0xC6, 0xC0, 0xCE, 0xC6, 0xC6, 0x7E, 0x00,
    // 72: H
    0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00,
    // 73: I
    0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00,
    // 74: J
    0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0x00,
    // 75: K
    0xC6, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0xC6, 0x00,
    // 76: L
    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0x00,
    // 77: M
    0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00,
    // 78: N
    0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00,
    // 79: O
    0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
    // 80: P
    0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0, 0xC0, 0x00,
    // 81: Q
    0x7C, 0xC6, 0xC6, 0xC6, 0xD6, 0xCC, 0x76, 0x00,
    // 82: R
    0xFC, 0xC6, 0xC6, 0xFC, 0xD8, 0xCC, 0xC6, 0x00,
    // 83: S
    0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x00,
    // 84: T
    0xFE, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00,
    // 85: U
    0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
    // 86: V
    0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00,
    // 87: W
    0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00,
    // 88: X
    0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00,
    // 89: Y
    0xC6, 0xC6, 0x6C, 0x38, 0x18, 0x18, 0x18, 0x00,
    // 90: Z
    0xFE, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFE, 0x00,
    // 91: [
    0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00,
    // 92: backslash
    0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00,
    // 93: ]
    0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00,
    // 94: ^
    0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
    // 95: _
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00,
    // 96: `
    0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
    // 97: a
    0x00, 0x00, 0x7C, 0x06, 0x7E, 0xC6, 0x7E, 0x00,
    // 98: b
    0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xFC, 0x00,
    // 99: c
    0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC6, 0x7C, 0x00,
    // 100: d
    0x06, 0x06, 0x7E, 0xC6, 0xC6, 0xC6, 0x7E, 0x00,
    // 101: e
    0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0x7C, 0x00,
    // 102: f
    0x1C, 0x36, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x00,
    // 103: g
    0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x7C,
    // 104: h
    0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00,
    // 105: i
    0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00,
    // 106: j
    0x06, 0x00, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C,
    // 107: k
    0xC0, 0xC0, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0x00,
    // 108: l
    0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00,
    // 109: m
    0x00, 0x00, 0xCC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00,
    // 110: n
    0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00,
    // 111: o
    0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
    // 112: p
    0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0,
    // 113: q
    0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x06,
    // 114: r
    0x00, 0x00, 0xDC, 0xE6, 0xC0, 0xC0, 0xC0, 0x00,
    // 115: s
    0x00, 0x00, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x00,
    // 116: t
    0x30, 0x30, 0x7C, 0x30, 0x30, 0x36, 0x1C, 0x00,
    // 117: u
    0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x00,
    // 118: v
    0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00,
    // 119: w
    0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00,
    // 120: x
    0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00,
    // 121: y
    0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x7C,
    // 122: z
    0x00, 0x00, 0xFE, 0x0C, 0x38, 0x60, 0xFE, 0x00,
    // 123: {
    0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00,
    // 124: |
    0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00,
    // 125: }
    0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00,
    // 126: ~
    0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    // 127: DEL (block)
    0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x00,
];

/// Font atlas dimensions.
const ATLAS_COLUMNS: u32 = 16;
const ATLAS_ROWS: u32 = 6;
const GLYPH_W: u32 = 8;
const GLYPH_H: u32 = 8;

/// Generate the built-in font as an RGBA texture.
///
/// Returns `(rgba_pixels, width, height)` where the texture is
/// 128×48 (16 glyphs wide × 6 glyphs tall, 8×8 each).
/// White glyphs on transparent background.
pub fn generate_builtin_font() -> (Vec<u8>, u32, u32) {
    let width = ATLAS_COLUMNS * GLYPH_W;
    let height = ATLAS_ROWS * GLYPH_H;
    let mut pixels = vec![0u8; (width * height * 4) as usize];

    for glyph_idx in 0..96u32 {
        let col = glyph_idx % ATLAS_COLUMNS;
        let row = glyph_idx / ATLAS_COLUMNS;
        let base_x = col * GLYPH_W;
        let base_y = row * GLYPH_H;

        for py in 0..GLYPH_H {
            let byte = FONT_DATA[(glyph_idx * 8 + py) as usize];
            for px in 0..GLYPH_W {
                let bit = (byte >> (7 - px)) & 1;
                let x = base_x + px;
                let y = base_y + py;
                let offset = ((y * width + x) * 4) as usize;
                if bit == 1 {
                    pixels[offset] = 255;     // R
                    pixels[offset + 1] = 255; // G
                    pixels[offset + 2] = 255; // B
                    pixels[offset + 3] = 255; // A
                } // else: stays 0,0,0,0 (transparent)
            }
        }
    }

    (pixels, width, height)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn font_texture_dimensions() {
        let (pixels, w, h) = generate_builtin_font();
        assert_eq!(w, 128);
        assert_eq!(h, 48);
        assert_eq!(pixels.len(), (128 * 48 * 4) as usize);
    }

    #[test]
    fn space_is_empty() {
        let (pixels, w, _) = generate_builtin_font();
        // Space is glyph 0, at (0,0). All pixels should be transparent.
        for py in 0..8 {
            for px in 0..8 {
                let offset = ((py * w + px) * 4) as usize;
                assert_eq!(pixels[offset + 3], 0, "space pixel ({px},{py}) should be transparent");
            }
        }
    }

    #[test]
    fn letter_a_has_pixels() {
        let (pixels, w, _) = generate_builtin_font();
        // 'A' = glyph index 33 (65 - 32). col=33%16=1, row=33/16=2
        let base_x: u32 = 1 * 8;
        let base_y: u32 = 2 * 8;
        let mut has_opaque = false;
        for py in 0..8u32 {
            for px in 0..8u32 {
                let offset = (((base_y + py) * w + (base_x + px)) * 4) as usize;
                if pixels[offset + 3] == 255 {
                    has_opaque = true;
                }
            }
        }
        assert!(has_opaque, "'A' glyph should have opaque pixels");
    }
}