dev/runtime-owned-variable-glyph-bank-palette-protocol #37

Merged
bquarkz merged 9 commits from dev/runtime-owned-variable-glyph-bank-palette-protocol into master 2026-07-14 15:25:05 +00:00
10 changed files with 129 additions and 39 deletions
Showing only changes of commit a415c172c7 - Show all commits

View File

@ -1212,7 +1212,7 @@ impl AssetManager {
&buffer[packed_pixel_bytes..packed_pixel_bytes + GLYPH_BANK_PALETTE_BYTES_V1];
let mut palettes =
[[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1];
vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1];
for (p, pal) in palettes.iter_mut().enumerate() {
for (c, slot) in pal.iter_mut().enumerate() {
let offset = (p * 16 + c) * 4;
@ -1247,7 +1247,7 @@ impl AssetManager {
.map_err(|_| "Buffer too small for GLYPHBANK".to_string())?;
let mut palettes =
[[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1];
vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1];
for (p, pal) in palettes.iter_mut().enumerate() {
for (c, slot) in pal.iter_mut().enumerate() {
let offset = (p * 16 + c) * 4;

View File

@ -557,8 +557,9 @@ mod tests {
fn make_glyph_bank(tile_size: TileSize, palette_id: u8, color: Color) -> GlyphBank {
let size = tile_size as usize;
let mut bank = GlyphBank::new(tile_size, size, size);
bank.palettes[palette_id as usize][1] = color;
let mut bank =
GlyphBank::with_palette_count(tile_size, size, size, palette_id as usize + 1);
bank.palette_mut(palette_id).unwrap()[1] = color;
for pixel in &mut bank.pixel_indices {
*pixel = 1;
}

View File

@ -812,7 +812,10 @@ impl Gfx {
let fetch_x = if tile.entry.flip_x() { size - 1 - local_x } else { local_x };
let fetch_y = if tile.entry.flip_y() { size - 1 - local_y } else { local_y };
let px_index = tile.bank.get_pixel_index(tile.entry.glyph_id, fetch_x, fetch_y);
let color = tile.bank.resolve_color(tile.entry.palette_id, px_index);
let color = tile
.bank
.resolve_color(tile.entry.palette_id, px_index)
.unwrap_or(Color::TRANSPARENT);
if color.alpha() == 0 {
continue;
}
@ -862,7 +865,9 @@ impl Gfx {
let fetch_y = if sprite.flip_y { size - 1 - local_y } else { local_y };
let px_index = bank.get_pixel_index(sprite.glyph.glyph_id, fetch_x, fetch_y);
let color = bank.resolve_color(sprite.glyph.palette_id, px_index);
let color = bank
.resolve_color(sprite.glyph.palette_id, px_index)
.unwrap_or(Color::TRANSPARENT);
if color.alpha() == 0 {
continue;
}
@ -1010,9 +1015,14 @@ mod tests {
fn make_glyph_bank(tile_size: TileSize, palette_colors: &[(u8, u8, Color)]) -> GlyphBank {
let size = tile_size as usize;
let mut bank = GlyphBank::new(tile_size, size, size);
let palette_count = palette_colors
.iter()
.map(|(palette_id, _, _)| *palette_id as usize + 1)
.max()
.unwrap_or(1);
let mut bank = GlyphBank::with_palette_count(tile_size, size, size, palette_count);
for (palette_id, color_index, color) in palette_colors {
bank.palettes[*palette_id as usize][*color_index as usize] = *color;
bank.palette_mut(*palette_id).unwrap()[*color_index as usize] = *color;
}
bank
}

View File

@ -261,7 +261,7 @@ mod tests {
fn make_glyph_bank() -> GlyphBank {
let mut bank = GlyphBank::new(TileSize::Size8, 8, 8);
bank.palettes[0][1] = Color::RED;
bank.palette_mut(0).unwrap()[1] = Color::RED;
for pixel in &mut bank.pixel_indices {
*pixel = 1;
}

View File

@ -196,7 +196,7 @@ mod tests {
fn make_glyph_bank() -> GlyphBank {
let mut bank = GlyphBank::new(TileSize::Size8, 8, 8);
bank.palettes[0][1] = Color::WHITE;
bank.palette_mut(0).unwrap()[1] = Color::WHITE;
bank
}

View File

@ -203,7 +203,7 @@ mod tests {
use super::*;
use crate::asset::{AssetCodec, AssetEntry, BankType, PreloadEntry};
use crate::cartridge::{ASSETS_PA_MAGIC, ASSETS_PA_SCHEMA_VERSION, AssetsPackPrelude};
use crate::glyph_bank::GLYPH_BANK_PALETTE_COUNT_V1;
use crate::glyph_bank::GLYPH_BANK_MAX_PALETTE_COUNT_V1;
use serde_json::json;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
@ -369,14 +369,14 @@ mod tests {
bank_type: BankType::GLYPH,
offset,
size,
decoded_size: 16 * 16 + (GLYPH_BANK_PALETTE_COUNT_V1 as u64 * 16 * 4),
decoded_size: 16 * 16 + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 as u64 * 16 * 4),
codec: AssetCodec::None,
metadata: json!({
"tile_size": 16,
"width": 16,
"height": 16,
"palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
"palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1
}),
}
}
@ -452,14 +452,14 @@ mod tests {
bank_type: BankType::GLYPH,
offset: 4,
size: 4,
decoded_size: 16 * 16 + (GLYPH_BANK_PALETTE_COUNT_V1 as u64 * 16 * 4),
decoded_size: 16 * 16 + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 as u64 * 16 * 4),
codec: AssetCodec::None,
metadata: json!({
"tile_size": 16,
"width": 16,
"height": 16,
"palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
"palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1
}),
},
];
@ -519,8 +519,8 @@ mod tests {
"tile_size": 16,
"width": 16,
"height": 16,
"palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
"palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1
}
}],
"preload": []
@ -561,8 +561,8 @@ mod tests {
"tile_size": 16,
"width": 16,
"height": 16,
"palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
"palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1
}
}],
"preload": []

View File

@ -1,7 +1,7 @@
use crate::color::Color;
use serde::{Deserialize, Serialize};
pub const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64;
pub const GLYPH_BANK_MAX_PALETTE_COUNT_V1: usize = 64;
pub const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16;
/// Standard sizes for square tiles.
@ -35,22 +35,59 @@ pub struct GlyphBank {
/// Palette indices are ordinary indices; transparency is resolved through
/// the RGBA alpha channel of the palette entry.
pub pixel_indices: Vec<u8>,
/// Runtime-facing v1 palette table: 64 palettes of 16 RGBA8888 colors each.
pub palettes: [[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1],
/// Runtime-facing v1 palette table.
///
/// Palette identity is the direct index in this vector. A resident glyph
/// bank carries exactly the palettes materialized from its payload.
pub palettes: Vec<[Color; GLYPH_BANK_COLORS_PER_PALETTE]>,
}
impl GlyphBank {
/// Creates an empty glyph bank with the specified dimensions.
/// Creates an empty glyph bank with one palette.
pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self {
Self::with_palette_count(tile_size, width, height, 1)
}
/// Creates an empty glyph bank with the specified resident palette count.
pub fn with_palette_count(
tile_size: TileSize,
width: usize,
height: usize,
palette_count: usize,
) -> Self {
assert!(
(1..=GLYPH_BANK_MAX_PALETTE_COUNT_V1).contains(&palette_count),
"glyph bank palette_count must be in 1..={}",
GLYPH_BANK_MAX_PALETTE_COUNT_V1
);
Self {
tile_size,
width,
height,
pixel_indices: vec![0; width * height],
palettes: [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1],
palettes: vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count],
}
}
/// Returns the number of resident palettes.
pub fn palette_count(&self) -> usize {
self.palettes.len()
}
/// Returns true when the palette id is valid for this resident bank.
pub fn contains_palette(&self, palette_id: u8) -> bool {
(palette_id as usize) < self.palette_count()
}
/// Returns a mutable palette slot when the palette exists.
pub fn palette_mut(
&mut self,
palette_id: u8,
) -> Option<&mut [Color; GLYPH_BANK_COLORS_PER_PALETTE]> {
self.palettes.get_mut(palette_id as usize)
}
/// Resolves a global tile ID and local pixel coordinates to a palette index.
/// tile_id: the tile index in the bank
/// local_x, local_y: the pixel position inside the tile (0 to tile_size-1)
@ -71,11 +108,48 @@ impl GlyphBank {
}
/// Maps a 4-bit index to a real RGBA8888 Color using the specified palette.
pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color {
pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Option<Color> {
self.palettes
.get(palette_id as usize)
.and_then(|palette| palette.get(pixel_index as usize))
.copied()
.unwrap_or(Color::TRANSPARENT)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_glyph_bank_uses_single_resident_palette() {
let bank = GlyphBank::new(TileSize::Size8, 8, 8);
assert_eq!(bank.palette_count(), 1);
assert!(bank.contains_palette(0));
assert!(!bank.contains_palette(1));
}
#[test]
fn glyph_bank_can_materialize_v1_max_palette_count() {
let bank =
GlyphBank::with_palette_count(TileSize::Size8, 8, 8, GLYPH_BANK_MAX_PALETTE_COUNT_V1);
assert_eq!(bank.palette_count(), GLYPH_BANK_MAX_PALETTE_COUNT_V1);
assert!(bank.contains_palette((GLYPH_BANK_MAX_PALETTE_COUNT_V1 - 1) as u8));
}
#[test]
fn invalid_palette_lookup_is_distinct_from_transparent_color() {
let mut bank = GlyphBank::new(TileSize::Size8, 8, 8);
bank.palette_mut(0).unwrap()[1] = Color::TRANSPARENT;
assert_eq!(bank.resolve_color(0, 1), Some(Color::TRANSPARENT));
assert_eq!(bank.resolve_color(1, 1), None);
}
#[test]
#[should_panic(expected = "glyph bank palette_count must be in 1..=64")]
fn glyph_bank_rejects_zero_resident_palettes() {
let _ = GlyphBank::with_palette_count(TileSize::Size8, 8, 8, 0);
}
}

View File

@ -15,7 +15,7 @@ use prometeu_hal::asset::{
use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge};
use prometeu_hal::color::Color;
use prometeu_hal::glyph::Glyph;
use prometeu_hal::glyph_bank::{GLYPH_BANK_PALETTE_COUNT_V1, GlyphBank, TileSize};
use prometeu_hal::glyph_bank::{GLYPH_BANK_MAX_PALETTE_COUNT_V1, GlyphBank, TileSize};
use prometeu_hal::scene_bank::SceneBank;
use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer};
use prometeu_hal::syscalls::caps;
@ -113,11 +113,12 @@ fn serialized_single_function_module_with_consts(
}
fn test_glyph_payload_size(width: usize, height: usize) -> usize {
(width * height).div_ceil(2) + (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u32>())
(width * height).div_ceil(2)
+ (GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u32>())
}
fn test_glyph_decoded_size(width: usize, height: usize) -> usize {
width * height + (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u32>())
width * height + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u32>())
}
fn test_glyph_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry {
@ -133,23 +134,26 @@ fn test_glyph_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry {
"tile_size": 16,
"width": 16,
"height": 16,
"palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
"palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1,
"palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1
}),
}
}
fn test_glyph_asset_data() -> Vec<u8> {
let mut data =
vec![0x11u8; test_glyph_payload_size(16, 16) - (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * 4)];
data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_COUNT_V1 * 16 * 4]);
vec![
0x11u8;
test_glyph_payload_size(16, 16) - (GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * 4)
];
data.extend_from_slice(&[0u8; GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * 4]);
data
}
fn runtime_test_glyph_bank(tile_size: TileSize, palette_id: u8, color: Color) -> GlyphBank {
let size = tile_size as usize;
let mut bank = GlyphBank::new(tile_size, size, size);
bank.palettes[palette_id as usize][1] = color;
let mut bank = GlyphBank::with_palette_count(tile_size, size, size, palette_id as usize + 1);
bank.palette_mut(palette_id).unwrap()[1] = color;
for pixel in &mut bank.pixel_indices {
*pixel = 1;
}

View File

@ -44,4 +44,4 @@
{"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0036","status":"done","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-22","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0045","file":"discussion/lessons/DSC-0036-prometeu-hub-ui-direction/LSN-0045-hub-ui-slices-should-prove-os-boundaries.md","status":"done","created_at":"2026-05-22","updated_at":"2026-05-22"}]}
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0046","file":"discussion/lessons/DSC-0037-rgba8888-framebuffer-and-pixel-format-direction/LSN-0046-pixel-format-contracts-must-move-as-one-surface.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23"}]}
{"type":"discussion","id":"DSC-0046","status":"in_progress","ticket":"runtime-owned-variable-glyph-bank-palette-protocol","title":"Runtime-Owned Variable Glyph Bank Palette Protocol","created_at":"2026-07-14","updated_at":"2026-07-14","tags":["runtime","gfx","assets","glyph-bank","palette-serialization","protocol"],"agendas":[{"id":"AGD-0049","file":"AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14"}],"decisions":[{"id":"DEC-0041","file":"DEC-0041-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14","ref_agenda":"AGD-0049"}],"plans":[{"id":"PLN-0167","file":"PLN-0167-spec-contract-update-for-variable-glyph-palettes.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0168","file":"PLN-0168-glyphbank-variable-palette-resident-model.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0169","file":"PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0170","file":"PLN-0170-composer-palette-reference-failure-semantics.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0171","file":"PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0172","file":"PLN-0172-runtime-spec-handoff-to-packer-and-studio.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0046","status":"in_progress","ticket":"runtime-owned-variable-glyph-bank-palette-protocol","title":"Runtime-Owned Variable Glyph Bank Palette Protocol","created_at":"2026-07-14","updated_at":"2026-07-14","tags":["runtime","gfx","assets","glyph-bank","palette-serialization","protocol"],"agendas":[{"id":"AGD-0049","file":"AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14"}],"decisions":[{"id":"DEC-0041","file":"DEC-0041-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14","ref_agenda":"AGD-0049"}],"plans":[{"id":"PLN-0167","file":"PLN-0167-spec-contract-update-for-variable-glyph-palettes.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0168","file":"PLN-0168-glyphbank-variable-palette-resident-model.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0169","file":"PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0170","file":"PLN-0170-composer-palette-reference-failure-semantics.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0171","file":"PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0172","file":"PLN-0172-runtime-spec-handoff-to-packer-and-studio.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]}],"lessons":[]}

View File

@ -2,7 +2,8 @@
id: PLN-0168
ticket: runtime-owned-variable-glyph-bank-palette-protocol
title: GlyphBank Variable Palette Resident Model
status: open
status: done
completed: 2026-07-14
created: 2026-07-14
ref_decisions: [DEC-0041]
tags: [runtime, gfx, assets, glyph-bank, hal]