Glyph Bank Domain Naming Contract

This commit is contained in:
bQUARKz 2026-04-10 06:00:47 +01:00
parent 59101baf00
commit 54f65f58e6
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
21 changed files with 700 additions and 220 deletions

View File

@ -1,5 +1,5 @@
#![allow(clippy::collapsible_if)] #![allow(clippy::collapsible_if)]
use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; use crate::memory_banks::{GlyphBankPoolInstaller, SoundBankPoolInstaller};
use prometeu_hal::AssetBridge; use prometeu_hal::AssetBridge;
use prometeu_hal::asset::{ use prometeu_hal::asset::{
AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, AssetCodec, AssetEntry, AssetId, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId,
@ -7,19 +7,19 @@ use prometeu_hal::asset::{
}; };
use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::cartridge::AssetsPayloadSource;
use prometeu_hal::color::Color; use prometeu_hal::color::Color;
use prometeu_hal::glyph_bank::{GlyphBank, TileSize};
use prometeu_hal::sample::Sample; use prometeu_hal::sample::Sample;
use prometeu_hal::sound_bank::SoundBank; use prometeu_hal::sound_bank::SoundBank;
use prometeu_hal::tile_bank::{TileBank, TileSize};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Read; use std::io::Read;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::thread; use std::thread;
use std::time::Instant; use std::time::Instant;
const TILE_BANK_PALETTE_COUNT_V1: usize = 64; const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64;
const TILE_BANK_COLORS_PER_PALETTE: usize = 16; const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16;
const TILE_BANK_PALETTE_BYTES_V1: usize = const GLYPH_BANK_PALETTE_BYTES_V1: usize =
TILE_BANK_PALETTE_COUNT_V1 * TILE_BANK_COLORS_PER_PALETTE * std::mem::size_of::<u16>(); GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * std::mem::size_of::<u16>();
/// Resident metadata for a decoded/materialized asset inside a BankPolicy. /// Resident metadata for a decoded/materialized asset inside a BankPolicy.
#[derive(Debug)] #[derive(Debug)]
@ -115,15 +115,15 @@ pub struct AssetManager {
assets_data: Arc<RwLock<AssetsPayloadSource>>, assets_data: Arc<RwLock<AssetsPayloadSource>>,
/// Narrow hardware interfaces /// Narrow hardware interfaces
gfx_installer: Arc<dyn TileBankPoolInstaller>, gfx_installer: Arc<dyn GlyphBankPoolInstaller>,
sound_installer: Arc<dyn SoundBankPoolInstaller>, sound_installer: Arc<dyn SoundBankPoolInstaller>,
/// Track what is installed in each hardware slot (for stats/info). /// Track what is installed in each hardware slot (for stats/info).
gfx_slots: Arc<RwLock<[Option<AssetId>; 16]>>, gfx_slots: Arc<RwLock<[Option<AssetId>; 16]>>,
sound_slots: Arc<RwLock<[Option<AssetId>; 16]>>, sound_slots: Arc<RwLock<[Option<AssetId>; 16]>>,
/// Residency policy for GFX tile banks. /// Residency policy for GFX glyph banks.
gfx_policy: BankPolicy<TileBank>, gfx_policy: BankPolicy<GlyphBank>,
/// Residency policy for sound banks. /// Residency policy for sound banks.
sound_policy: BankPolicy<SoundBank>, sound_policy: BankPolicy<SoundBank>,
@ -179,10 +179,10 @@ impl AssetBridge for AssetManager {
} }
impl AssetManager { impl AssetManager {
fn decode_tile_bank_layout( fn decode_glyph_bank_layout(
entry: &AssetEntry, entry: &AssetEntry,
) -> Result<(TileSize, usize, usize, usize), String> { ) -> Result<(TileSize, usize, usize, usize), String> {
let meta = entry.metadata_as_tiles()?; let meta = entry.metadata_as_glyphs()?;
let tile_size = match meta.tile_size { let tile_size = match meta.tile_size {
8 => TileSize::Size8, 8 => TileSize::Size8,
@ -191,32 +191,32 @@ impl AssetManager {
_ => return Err(format!("Invalid tile_size: {}", meta.tile_size)), _ => return Err(format!("Invalid tile_size: {}", meta.tile_size)),
}; };
if meta.palette_count as usize != TILE_BANK_PALETTE_COUNT_V1 { if meta.palette_count as usize != GLYPH_BANK_PALETTE_COUNT_V1 {
return Err(format!("Invalid palette_count: {}", meta.palette_count)); return Err(format!("Invalid palette_count: {}", meta.palette_count));
} }
let width = meta.width as usize; let width = meta.width as usize;
let height = meta.height as usize; let height = meta.height as usize;
let logical_pixels = width.checked_mul(height).ok_or("TileBank dimensions overflow")?; let logical_pixels = width.checked_mul(height).ok_or("GlyphBank dimensions overflow")?;
let serialized_pixel_bytes = logical_pixels.div_ceil(2); let serialized_pixel_bytes = logical_pixels.div_ceil(2);
let serialized_size = serialized_pixel_bytes let serialized_size = serialized_pixel_bytes
.checked_add(TILE_BANK_PALETTE_BYTES_V1) .checked_add(GLYPH_BANK_PALETTE_BYTES_V1)
.ok_or("TileBank serialized size overflow")?; .ok_or("GlyphBank serialized size overflow")?;
let decoded_size = logical_pixels let decoded_size = logical_pixels
.checked_add(TILE_BANK_PALETTE_BYTES_V1) .checked_add(GLYPH_BANK_PALETTE_BYTES_V1)
.ok_or("TileBank decoded size overflow")?; .ok_or("GlyphBank decoded size overflow")?;
if entry.size != serialized_size as u64 { if entry.size != serialized_size as u64 {
return Err(format!( return Err(format!(
"Invalid TILEBANK serialized size: expected {}, got {}", "Invalid GLYPHBANK serialized size: expected {}, got {}",
serialized_size, entry.size serialized_size, entry.size
)); ));
} }
if entry.decoded_size != decoded_size as u64 { if entry.decoded_size != decoded_size as u64 {
return Err(format!( return Err(format!(
"Invalid TILEBANK decoded_size: expected {}, got {}", "Invalid GLYPHBANK decoded_size: expected {}, got {}",
decoded_size, entry.decoded_size decoded_size, entry.decoded_size
)); ));
} }
@ -224,7 +224,7 @@ impl AssetManager {
Ok((tile_size, width, height, serialized_pixel_bytes)) Ok((tile_size, width, height, serialized_pixel_bytes))
} }
fn unpack_tile_bank_pixels(packed_pixels: &[u8], logical_pixels: usize) -> Vec<u8> { fn unpack_glyph_bank_pixels(packed_pixels: &[u8], logical_pixels: usize) -> Vec<u8> {
let mut pixel_indices = Vec::with_capacity(logical_pixels); let mut pixel_indices = Vec::with_capacity(logical_pixels);
for &packed in packed_pixels { for &packed in packed_pixels {
if pixel_indices.len() < logical_pixels { if pixel_indices.len() < logical_pixels {
@ -239,7 +239,7 @@ impl AssetManager {
fn op_mode_for(entry: &AssetEntry) -> Result<AssetOpMode, String> { fn op_mode_for(entry: &AssetEntry) -> Result<AssetOpMode, String> {
match (entry.bank_type, entry.codec) { match (entry.bank_type, entry.codec) {
(BankType::TILES, AssetCodec::None) => Ok(AssetOpMode::StageInMemory), (BankType::GLYPH, AssetCodec::None) => Ok(AssetOpMode::StageInMemory),
(BankType::SOUNDS, AssetCodec::None) => Ok(AssetOpMode::DirectFromSlice), (BankType::SOUNDS, AssetCodec::None) => Ok(AssetOpMode::DirectFromSlice),
} }
} }
@ -247,7 +247,7 @@ impl AssetManager {
pub fn new( pub fn new(
assets: Vec<AssetEntry>, assets: Vec<AssetEntry>,
assets_data: AssetsPayloadSource, assets_data: AssetsPayloadSource,
gfx_installer: Arc<dyn TileBankPoolInstaller>, gfx_installer: Arc<dyn GlyphBankPoolInstaller>,
sound_installer: Arc<dyn SoundBankPoolInstaller>, sound_installer: Arc<dyn SoundBankPoolInstaller>,
) -> Self { ) -> Self {
let mut asset_map = HashMap::new(); let mut asset_map = HashMap::new();
@ -296,9 +296,9 @@ impl AssetManager {
if let Some(entry) = entry_opt { if let Some(entry) = entry_opt {
let slot_index = item.slot; let slot_index = item.slot;
match entry.bank_type { match entry.bank_type {
BankType::TILES => { BankType::GLYPH => {
if let Ok(bank) = if let Ok(bank) =
Self::perform_load_tile_bank(&entry, self.assets_data.clone()) Self::perform_load_glyph_bank(&entry, self.assets_data.clone())
{ {
let bank_arc = Arc::new(bank); let bank_arc = Arc::new(bank);
self.gfx_policy.put_resident( self.gfx_policy.put_resident(
@ -306,7 +306,7 @@ impl AssetManager {
Arc::clone(&bank_arc), Arc::clone(&bank_arc),
entry.decoded_size as usize, entry.decoded_size as usize,
); );
self.gfx_installer.install_tile_bank(slot_index, bank_arc); self.gfx_installer.install_glyph_bank(slot_index, bank_arc);
let mut slots = self.gfx_slots.write().unwrap(); let mut slots = self.gfx_slots.write().unwrap();
if slot_index < slots.len() { if slot_index < slots.len() {
slots[slot_index] = Some(entry.asset_id); slots[slot_index] = Some(entry.asset_id);
@ -367,7 +367,7 @@ impl AssetManager {
assets.get(&asset_id).ok_or(AssetLoadError::AssetNotFound)?.clone() assets.get(&asset_id).ok_or(AssetLoadError::AssetNotFound)?.clone()
}; };
let slot = match entry.bank_type { let slot = match entry.bank_type {
BankType::TILES => SlotRef::gfx(slot_index), BankType::GLYPH => SlotRef::gfx(slot_index),
BankType::SOUNDS => SlotRef::audio(slot_index), BankType::SOUNDS => SlotRef::audio(slot_index),
}; };
@ -377,7 +377,7 @@ impl AssetManager {
// Check if already resident (Dedup) // Check if already resident (Dedup)
let already_resident = match entry.bank_type { let already_resident = match entry.bank_type {
BankType::TILES => { BankType::GLYPH => {
if let Some(bank) = self.gfx_policy.get_resident(asset_id) { if let Some(bank) = self.gfx_policy.get_resident(asset_id) {
self.gfx_policy.stage(handle_id, bank); self.gfx_policy.stage(handle_id, bank);
true true
@ -435,8 +435,8 @@ impl AssetManager {
} }
match entry_clone.bank_type { match entry_clone.bank_type {
BankType::TILES => { BankType::GLYPH => {
let result = Self::perform_load_tile_bank(&entry_clone, assets_data); let result = Self::perform_load_glyph_bank(&entry_clone, assets_data);
if let Ok(tilebank) = result { if let Ok(tilebank) = result {
let bank_arc = Arc::new(tilebank); let bank_arc = Arc::new(tilebank);
let resident_arc = { let resident_arc = {
@ -507,10 +507,10 @@ impl AssetManager {
Ok(handle_id) Ok(handle_id)
} }
fn perform_load_tile_bank( fn perform_load_glyph_bank(
entry: &AssetEntry, entry: &AssetEntry,
assets_data: Arc<RwLock<AssetsPayloadSource>>, assets_data: Arc<RwLock<AssetsPayloadSource>>,
) -> Result<TileBank, String> { ) -> Result<GlyphBank, String> {
let op_mode = Self::op_mode_for(entry)?; let op_mode = Self::op_mode_for(entry)?;
let slice = { let slice = {
let assets_data = assets_data.read().unwrap(); let assets_data = assets_data.read().unwrap();
@ -523,30 +523,33 @@ impl AssetManager {
AssetOpMode::StageInMemory => { AssetOpMode::StageInMemory => {
let buffer = let buffer =
slice.read_all().map_err(|_| "Asset payload read failed".to_string())?; slice.read_all().map_err(|_| "Asset payload read failed".to_string())?;
Self::decode_tile_bank_from_buffer(entry, &buffer) Self::decode_glyph_bank_from_buffer(entry, &buffer)
} }
AssetOpMode::DirectFromSlice => { AssetOpMode::DirectFromSlice => {
let mut reader = let mut reader =
slice.open_reader().map_err(|_| "Asset payload read failed".to_string())?; slice.open_reader().map_err(|_| "Asset payload read failed".to_string())?;
Self::decode_tile_bank_from_reader(entry, &mut reader) Self::decode_glyph_bank_from_reader(entry, &mut reader)
} }
} }
} }
fn decode_tile_bank_from_buffer(entry: &AssetEntry, buffer: &[u8]) -> Result<TileBank, String> { fn decode_glyph_bank_from_buffer(
let (tile_size, width, height, packed_pixel_bytes) = Self::decode_tile_bank_layout(entry)?; entry: &AssetEntry,
if buffer.len() < packed_pixel_bytes + TILE_BANK_PALETTE_BYTES_V1 { buffer: &[u8],
return Err("Buffer too small for TILEBANK".to_string()); ) -> Result<GlyphBank, String> {
let (tile_size, width, height, packed_pixel_bytes) = Self::decode_glyph_bank_layout(entry)?;
if buffer.len() < packed_pixel_bytes + GLYPH_BANK_PALETTE_BYTES_V1 {
return Err("Buffer too small for GLYPHBANK".to_string());
} }
let logical_pixels = width * height; let logical_pixels = width * height;
let packed_pixels = &buffer[0..packed_pixel_bytes]; let packed_pixels = &buffer[0..packed_pixel_bytes];
let pixel_indices = Self::unpack_tile_bank_pixels(packed_pixels, logical_pixels); let pixel_indices = Self::unpack_glyph_bank_pixels(packed_pixels, logical_pixels);
let palette_data = let palette_data =
&buffer[packed_pixel_bytes..packed_pixel_bytes + TILE_BANK_PALETTE_BYTES_V1]; &buffer[packed_pixel_bytes..packed_pixel_bytes + GLYPH_BANK_PALETTE_BYTES_V1];
let mut palettes = let mut palettes =
[[Color::BLACK; TILE_BANK_COLORS_PER_PALETTE]; TILE_BANK_PALETTE_COUNT_V1]; [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1];
for (p, pal) in palettes.iter_mut().enumerate() { for (p, pal) in palettes.iter_mut().enumerate() {
for (c, slot) in pal.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() {
let offset = (p * 16 + c) * 2; let offset = (p * 16 + c) * 2;
@ -556,29 +559,29 @@ impl AssetManager {
} }
} }
Ok(TileBank { tile_size, width, height, pixel_indices, palettes }) Ok(GlyphBank { tile_size, width, height, pixel_indices, palettes })
} }
fn decode_tile_bank_from_reader( fn decode_glyph_bank_from_reader(
entry: &AssetEntry, entry: &AssetEntry,
reader: &mut impl Read, reader: &mut impl Read,
) -> Result<TileBank, String> { ) -> Result<GlyphBank, String> {
let (tile_size, width, height, packed_pixel_bytes) = Self::decode_tile_bank_layout(entry)?; let (tile_size, width, height, packed_pixel_bytes) = Self::decode_glyph_bank_layout(entry)?;
let logical_pixels = width * height; let logical_pixels = width * height;
let mut packed_pixels = vec![0_u8; packed_pixel_bytes]; let mut packed_pixels = vec![0_u8; packed_pixel_bytes];
reader reader
.read_exact(&mut packed_pixels) .read_exact(&mut packed_pixels)
.map_err(|_| "Buffer too small for TILEBANK".to_string())?; .map_err(|_| "Buffer too small for GLYPHBANK".to_string())?;
let pixel_indices = Self::unpack_tile_bank_pixels(&packed_pixels, logical_pixels); let pixel_indices = Self::unpack_glyph_bank_pixels(&packed_pixels, logical_pixels);
let mut palette_data = [0_u8; TILE_BANK_PALETTE_BYTES_V1]; let mut palette_data = [0_u8; GLYPH_BANK_PALETTE_BYTES_V1];
reader reader
.read_exact(&mut palette_data) .read_exact(&mut palette_data)
.map_err(|_| "Buffer too small for TILEBANK".to_string())?; .map_err(|_| "Buffer too small for GLYPHBANK".to_string())?;
let mut palettes = let mut palettes =
[[Color::BLACK; TILE_BANK_COLORS_PER_PALETTE]; TILE_BANK_PALETTE_COUNT_V1]; [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1];
for (p, pal) in palettes.iter_mut().enumerate() { for (p, pal) in palettes.iter_mut().enumerate() {
for (c, slot) in pal.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() {
let offset = (p * 16 + c) * 2; let offset = (p * 16 + c) * 2;
@ -588,7 +591,7 @@ impl AssetManager {
} }
} }
Ok(TileBank { tile_size, width, height, pixel_indices, palettes }) Ok(GlyphBank { tile_size, width, height, pixel_indices, palettes })
} }
fn perform_load_sound_bank( fn perform_load_sound_bank(
@ -695,9 +698,9 @@ impl AssetManager {
if let Some(h) = handles.get_mut(&handle_id) { if let Some(h) = handles.get_mut(&handle_id) {
if h.status == LoadStatus::READY { if h.status == LoadStatus::READY {
match h.slot.asset_type { match h.slot.asset_type {
BankType::TILES => { BankType::GLYPH => {
if let Some(bank) = self.gfx_policy.take_staging(handle_id) { if let Some(bank) = self.gfx_policy.take_staging(handle_id) {
self.gfx_installer.install_tile_bank(h.slot.index, bank); self.gfx_installer.install_glyph_bank(h.slot.index, bank);
let mut slots = self.gfx_slots.write().unwrap(); let mut slots = self.gfx_slots.write().unwrap();
if h.slot.index < slots.len() { if h.slot.index < slots.len() {
slots[h.slot.index] = Some(h._asset_id); slots[h.slot.index] = Some(h._asset_id);
@ -723,7 +726,7 @@ impl AssetManager {
pub fn bank_info(&self, kind: BankType) -> BankStats { pub fn bank_info(&self, kind: BankType) -> BankStats {
match kind { match kind {
BankType::TILES => { BankType::GLYPH => {
let mut used_bytes = 0; let mut used_bytes = 0;
{ {
let resident = self.gfx_policy.resident.read().unwrap(); let resident = self.gfx_policy.resident.read().unwrap();
@ -814,7 +817,7 @@ impl AssetManager {
pub fn slot_info(&self, slot: SlotRef) -> SlotStats { pub fn slot_info(&self, slot: SlotRef) -> SlotStats {
match slot.asset_type { match slot.asset_type {
BankType::TILES => { BankType::GLYPH => {
let slots = self.gfx_slots.read().unwrap(); let slots = self.gfx_slots.read().unwrap();
let asset_id = slots.get(slot.index).and_then(|s| *s); let asset_id = slots.get(slot.index).and_then(|s| *s);
@ -872,93 +875,94 @@ impl AssetManager {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::memory_banks::{MemoryBanks, SoundBankPoolAccess, TileBankPoolAccess}; use crate::memory_banks::{GlyphBankPoolAccess, MemoryBanks, SoundBankPoolAccess};
use prometeu_hal::asset::AssetCodec; use prometeu_hal::asset::AssetCodec;
fn expected_tile_payload_size(width: usize, height: usize) -> usize { fn expected_glyph_payload_size(width: usize, height: usize) -> usize {
(width * height).div_ceil(2) + TILE_BANK_PALETTE_BYTES_V1 (width * height).div_ceil(2) + GLYPH_BANK_PALETTE_BYTES_V1
} }
fn expected_tile_decoded_size(width: usize, height: usize) -> usize { fn expected_glyph_decoded_size(width: usize, height: usize) -> usize {
width * height + TILE_BANK_PALETTE_BYTES_V1 width * height + GLYPH_BANK_PALETTE_BYTES_V1
} }
fn test_tile_asset_data() -> Vec<u8> { fn test_glyph_asset_data() -> Vec<u8> {
let mut data = vec![0x11u8; 128]; let mut data = vec![0x11u8; 128];
data.extend_from_slice(&[0u8; TILE_BANK_PALETTE_BYTES_V1]); data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_BYTES_V1]);
data data
} }
fn test_tile_asset_entry(asset_name: &str, width: usize, height: usize) -> AssetEntry { fn test_glyph_asset_entry(asset_name: &str, width: usize, height: usize) -> AssetEntry {
AssetEntry { AssetEntry {
asset_id: 0, asset_id: 0,
asset_name: asset_name.to_string(), asset_name: asset_name.to_string(),
bank_type: BankType::TILES, bank_type: BankType::GLYPH,
offset: 0, offset: 0,
size: expected_tile_payload_size(width, height) as u64, size: expected_glyph_payload_size(width, height) as u64,
decoded_size: expected_tile_decoded_size(width, height) as u64, decoded_size: expected_glyph_decoded_size(width, height) as u64,
codec: AssetCodec::None, codec: AssetCodec::None,
metadata: serde_json::json!({ metadata: serde_json::json!({
"tile_size": 16, "tile_size": 16,
"width": width, "width": width,
"height": height, "height": height,
"palette_count": TILE_BANK_PALETTE_COUNT_V1, "palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": TILE_BANK_PALETTE_COUNT_V1 "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
}), }),
} }
} }
#[test] #[test]
fn test_decode_tile_bank_unpacks_packed_pixels_and_reads_palette_colors() { fn test_decode_glyph_bank_unpacks_packed_pixels_and_reads_palette_colors() {
let entry = test_tile_asset_entry("tiles", 2, 2); let entry = test_glyph_asset_entry("tiles", 2, 2);
let mut data = vec![0x10, 0x23]; let mut data = vec![0x10, 0x23];
data.extend_from_slice(&[0u8; TILE_BANK_PALETTE_BYTES_V1]); data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_BYTES_V1]);
data[2] = 0x34; data[2] = 0x34;
data[3] = 0x12; data[3] = 0x12;
let bank = AssetManager::decode_tile_bank_from_buffer(&entry, &data).expect("tile decode"); let bank =
AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("glyph decode");
assert_eq!(bank.pixel_indices, vec![1, 0, 2, 3]); assert_eq!(bank.pixel_indices, vec![1, 0, 2, 3]);
assert_eq!(bank.palettes[0][0], Color(0x1234)); assert_eq!(bank.palettes[0][0], Color(0x1234));
} }
#[test] #[test]
fn test_decode_tile_bank_rejects_short_packed_buffer() { fn test_decode_glyph_bank_rejects_short_packed_buffer() {
let entry = test_tile_asset_entry("tiles", 16, 16); let entry = test_glyph_asset_entry("tiles", 16, 16);
let data = vec![0u8; expected_tile_payload_size(16, 16) - 1]; let data = vec![0u8; expected_glyph_payload_size(16, 16) - 1];
let err = match AssetManager::decode_tile_bank_from_buffer(&entry, &data) { let err = match AssetManager::decode_glyph_bank_from_buffer(&entry, &data) {
Ok(_) => panic!("tile decode should reject short buffer"), Ok(_) => panic!("glyph decode should reject short buffer"),
Err(err) => err, Err(err) => err,
}; };
assert_eq!(err, "Buffer too small for TILEBANK"); assert_eq!(err, "Buffer too small for GLYPHBANK");
} }
#[test] #[test]
fn test_decode_tile_bank_requires_palette_count_64() { fn test_decode_glyph_bank_requires_palette_count_64() {
let mut entry = test_tile_asset_entry("tiles", 16, 16); let mut entry = test_glyph_asset_entry("tiles", 16, 16);
entry.metadata["palette_count"] = serde_json::json!(32); entry.metadata["palette_count"] = serde_json::json!(32);
let err = match AssetManager::decode_tile_bank_from_buffer(&entry, &test_tile_asset_data()) let err =
{ match AssetManager::decode_glyph_bank_from_buffer(&entry, &test_glyph_asset_data()) {
Ok(_) => panic!("tile decode should reject invalid palette_count"), Ok(_) => panic!("glyph decode should reject invalid palette_count"),
Err(err) => err, Err(err) => err,
}; };
assert_eq!(err, "Invalid palette_count: 32"); assert_eq!(err, "Invalid palette_count: 32");
} }
#[test] #[test]
fn test_op_mode_for_tiles_none_stages_in_memory() { fn test_op_mode_for_glyphs_none_stages_in_memory() {
let entry = test_tile_asset_entry("tiles", 16, 16); let entry = test_glyph_asset_entry("tiles", 16, 16);
assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory)); assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory));
} }
#[test] #[test]
fn test_op_mode_for_tiles_none_uses_typed_codec() { fn test_op_mode_for_glyphs_none_uses_typed_codec() {
let entry = test_tile_asset_entry("tiles", 16, 16); let entry = test_glyph_asset_entry("tiles", 16, 16);
assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory)); assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory));
} }
@ -984,11 +988,11 @@ mod tests {
#[test] #[test]
fn test_asset_loading_flow() { fn test_asset_loading_flow() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let data = test_tile_asset_data(); let data = test_glyph_asset_data();
let asset_entry = test_tile_asset_entry("test_tiles", 16, 16); let asset_entry = test_glyph_asset_entry("test_tiles", 16, 16);
let am = AssetManager::new( let am = AssetManager::new(
vec![asset_entry], vec![asset_entry],
@ -1016,17 +1020,17 @@ mod tests {
am.apply_commits(); am.apply_commits();
assert_eq!(am.status(handle), LoadStatus::COMMITTED); assert_eq!(am.status(handle), LoadStatus::COMMITTED);
assert!(banks.tile_bank_slot(0).is_some()); assert!(banks.glyph_bank_slot(0).is_some());
} }
#[test] #[test]
fn test_asset_dedup() { fn test_asset_dedup() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let data = test_tile_asset_data(); let data = test_glyph_asset_data();
let asset_entry = test_tile_asset_entry("test_tiles", 16, 16); let asset_entry = test_glyph_asset_entry("test_tiles", 16, 16);
let am = AssetManager::new( let am = AssetManager::new(
vec![asset_entry], vec![asset_entry],
@ -1053,7 +1057,7 @@ mod tests {
#[test] #[test]
fn test_sound_asset_loading() { fn test_sound_asset_loading() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
// 100 samples of 16-bit PCM (zeros) // 100 samples of 16-bit PCM (zeros)
@ -1098,7 +1102,7 @@ mod tests {
#[test] #[test]
fn test_preload_on_init() { fn test_preload_on_init() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let data = vec![0u8; 200]; let data = vec![0u8; 200];
@ -1139,7 +1143,7 @@ mod tests {
#[test] #[test]
fn test_load_returns_asset_not_found() { fn test_load_returns_asset_not_found() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let am = let am =
AssetManager::new(vec![], AssetsPayloadSource::empty(), gfx_installer, sound_installer); AssetManager::new(vec![], AssetsPayloadSource::empty(), gfx_installer, sound_installer);
@ -1152,11 +1156,11 @@ mod tests {
#[test] #[test]
fn test_load_returns_slot_index_invalid() { fn test_load_returns_slot_index_invalid() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let data = test_tile_asset_data(); let data = test_glyph_asset_data();
let am = AssetManager::new( let am = AssetManager::new(
vec![test_tile_asset_entry("test_tiles", 16, 16)], vec![test_glyph_asset_entry("test_tiles", 16, 16)],
AssetsPayloadSource::from_bytes(data), AssetsPayloadSource::from_bytes(data),
gfx_installer, gfx_installer,
sound_installer, sound_installer,
@ -1170,7 +1174,7 @@ mod tests {
#[test] #[test]
fn test_status_returns_unknown_handle() { fn test_status_returns_unknown_handle() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let am = let am =
AssetManager::new(vec![], AssetsPayloadSource::empty(), gfx_installer, sound_installer); AssetManager::new(vec![], AssetsPayloadSource::empty(), gfx_installer, sound_installer);
@ -1181,11 +1185,11 @@ mod tests {
#[test] #[test]
fn test_commit_and_cancel_return_explicit_statuses() { fn test_commit_and_cancel_return_explicit_statuses() {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>; let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>; let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let data = test_tile_asset_data(); let data = test_glyph_asset_data();
let am = AssetManager::new( let am = AssetManager::new(
vec![test_tile_asset_entry("test_tiles", 16, 16)], vec![test_glyph_asset_entry("test_tiles", 16, 16)],
AssetsPayloadSource::from_bytes(data), AssetsPayloadSource::from_bytes(data),
gfx_installer, gfx_installer,
sound_installer, sound_installer,

View File

@ -1,10 +1,10 @@
use crate::memory_banks::TileBankPoolAccess; use crate::memory_banks::GlyphBankPoolAccess;
use prometeu_hal::GfxBridge; use prometeu_hal::GfxBridge;
use prometeu_hal::color::Color; use prometeu_hal::color::Color;
use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph::Glyph;
use prometeu_hal::glyph_bank::{GlyphBank, TileSize};
use prometeu_hal::sprite::Sprite; use prometeu_hal::sprite::Sprite;
use prometeu_hal::tile::Tile; use prometeu_hal::tile::Tile;
use prometeu_hal::tile_bank::{TileBank, TileSize};
use prometeu_hal::tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; use prometeu_hal::tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
use std::sync::Arc; use std::sync::Arc;
@ -57,12 +57,12 @@ pub struct Gfx {
/// Back buffer: the "Work RAM" where new frames are composed. /// Back buffer: the "Work RAM" where new frames are composed.
back: Vec<u16>, back: Vec<u16>,
/// 4 scrollable background layers. Each can have its own scroll (X, Y) and TileBank. /// 4 scrollable background layers. Each can have its own scroll (X, Y) and GlyphBank.
pub layers: [ScrollableTileLayer; 4], pub layers: [ScrollableTileLayer; 4],
/// 1 fixed layer for User Interface (HUD). It doesn't scroll. /// 1 fixed layer for User Interface (HUD). It doesn't scroll.
pub hud: HudTileLayer, pub hud: HudTileLayer,
/// Shared access to graphical memory banks (tiles and palettes). /// Shared access to graphical memory banks (tiles and palettes).
pub tile_banks: Arc<dyn TileBankPoolAccess>, pub glyph_banks: Arc<dyn GlyphBankPoolAccess>,
/// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory). /// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory).
pub sprites: [Sprite; 512], pub sprites: [Sprite; 512],
@ -274,7 +274,7 @@ impl GfxBridge for Gfx {
impl Gfx { impl Gfx {
/// Initializes the graphics system with a specific resolution and shared memory banks. /// Initializes the graphics system with a specific resolution and shared memory banks.
pub fn new(w: usize, h: usize, tile_banks: Arc<dyn TileBankPoolAccess>) -> Self { pub fn new(w: usize, h: usize, glyph_banks: Arc<dyn GlyphBankPoolAccess>) -> Self {
const EMPTY_GLYPH: Glyph = Glyph { glyph_id: 0, palette_id: 0 }; const EMPTY_GLYPH: Glyph = Glyph { glyph_id: 0, palette_id: 0 };
const EMPTY_SPRITE: Sprite = Sprite { const EMPTY_SPRITE: Sprite = Sprite {
@ -303,7 +303,7 @@ impl Gfx {
back: vec![0; len], back: vec![0; len],
layers, layers,
hud: HudTileLayer::new(64, 32), hud: HudTileLayer::new(64, 32),
tile_banks, glyph_banks,
sprites: [EMPTY_SPRITE; 512], sprites: [EMPTY_SPRITE; 512],
scene_fade_level: 31, scene_fade_level: 31,
scene_fade_color: Color::BLACK, scene_fade_color: Color::BLACK,
@ -583,14 +583,14 @@ impl Gfx {
self.h, self.h,
&self.priority_buckets[0], &self.priority_buckets[0],
&self.sprites, &self.sprites,
&*self.tile_banks, &*self.glyph_banks,
); );
// 2. Main layers and prioritized sprites. // 2. Main layers and prioritized sprites.
// Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ... // Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ...
for i in 0..self.layers.len() { for i in 0..self.layers.len() {
let bank_id = self.layers[i].bank_id as usize; let bank_id = self.layers[i].bank_id as usize;
if let Some(bank) = self.tile_banks.tile_bank_slot(bank_id) { if let Some(bank) = self.glyph_banks.glyph_bank_slot(bank_id) {
Self::draw_tile_map( Self::draw_tile_map(
&mut self.back, &mut self.back,
self.w, self.w,
@ -609,7 +609,7 @@ impl Gfx {
self.h, self.h,
&self.priority_buckets[i + 1], &self.priority_buckets[i + 1],
&self.sprites, &self.sprites,
&*self.tile_banks, &*self.glyph_banks,
); );
} }
@ -617,7 +617,7 @@ impl Gfx {
Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color); Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color);
// 5. HUD: The fixed interface layer, always drawn on top of the world. // 5. HUD: The fixed interface layer, always drawn on top of the world.
Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks); Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.glyph_banks);
// 6. HUD Fade: Independent fade effect for the UI. // 6. HUD Fade: Independent fade effect for the UI.
Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color); Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color);
@ -633,7 +633,7 @@ impl Gfx {
let scroll_x = self.layers[layer_idx].scroll_x; let scroll_x = self.layers[layer_idx].scroll_x;
let scroll_y = self.layers[layer_idx].scroll_y; let scroll_y = self.layers[layer_idx].scroll_y;
let bank = match self.tile_banks.tile_bank_slot(bank_id) { let bank = match self.glyph_banks.glyph_bank_slot(bank_id) {
Some(b) => b, Some(b) => b,
_ => return, _ => return,
}; };
@ -651,7 +651,7 @@ impl Gfx {
/// Renders the HUD (fixed position, no scroll). /// Renders the HUD (fixed position, no scroll).
pub fn render_hud(&mut self) { pub fn render_hud(&mut self) {
Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks); Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.glyph_banks);
} }
fn render_hud_with_pool( fn render_hud_with_pool(
@ -659,10 +659,10 @@ impl Gfx {
w: usize, w: usize,
h: usize, h: usize,
hud: &HudTileLayer, hud: &HudTileLayer,
tile_banks: &dyn TileBankPoolAccess, glyph_banks: &dyn GlyphBankPoolAccess,
) { ) {
let bank_id = hud.bank_id as usize; let bank_id = hud.bank_id as usize;
let bank = match tile_banks.tile_bank_slot(bank_id) { let bank = match glyph_banks.glyph_bank_slot(bank_id) {
Some(b) => b, Some(b) => b,
_ => return, _ => return,
}; };
@ -676,7 +676,7 @@ impl Gfx {
screen_w: usize, screen_w: usize,
screen_h: usize, screen_h: usize,
map: &TileMap, map: &TileMap,
bank: &TileBank, bank: &GlyphBank,
scroll_x: i32, scroll_x: i32,
scroll_y: i32, scroll_y: i32,
) { ) {
@ -739,7 +739,7 @@ impl Gfx {
x: i32, x: i32,
y: i32, y: i32,
tile: Tile, tile: Tile,
bank: &TileBank, bank: &GlyphBank,
) { ) {
let size = bank.tile_size as usize; let size = bank.tile_size as usize;
@ -781,12 +781,12 @@ impl Gfx {
screen_h: usize, screen_h: usize,
bucket: &[usize], bucket: &[usize],
sprites: &[Sprite], sprites: &[Sprite],
tile_banks: &dyn TileBankPoolAccess, glyph_banks: &dyn GlyphBankPoolAccess,
) { ) {
for &idx in bucket { for &idx in bucket {
let s = &sprites[idx]; let s = &sprites[idx];
let bank_id = s.bank_id as usize; let bank_id = s.bank_id as usize;
if let Some(bank) = tile_banks.tile_bank_slot(bank_id) { if let Some(bank) = glyph_banks.glyph_bank_slot(bank_id) {
Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, &bank); Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, &bank);
} }
} }
@ -797,7 +797,7 @@ impl Gfx {
screen_w: usize, screen_w: usize,
screen_h: usize, screen_h: usize,
sprite: &Sprite, sprite: &Sprite,
bank: &TileBank, bank: &GlyphBank,
) { ) {
// ... (same bounds/clipping calculation we already had) ... // ... (same bounds/clipping calculation we already had) ...
let size = bank.tile_size as usize; let size = bank.tile_size as usize;

View File

@ -2,8 +2,8 @@ use crate::asset::AssetManager;
use crate::audio::Audio; use crate::audio::Audio;
use crate::gfx::Gfx; use crate::gfx::Gfx;
use crate::memory_banks::{ use crate::memory_banks::{
MemoryBanks, SoundBankPoolAccess, SoundBankPoolInstaller, TileBankPoolAccess, GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SoundBankPoolAccess,
TileBankPoolInstaller, SoundBankPoolInstaller,
}; };
use crate::pad::Pad; use crate::pad::Pad;
use crate::touch::Touch; use crate::touch::Touch;
@ -32,7 +32,7 @@ pub struct Hardware {
pub pad: Pad, pub pad: Pad,
/// The absolute pointer input device (Mouse/Touchscreen). /// The absolute pointer input device (Mouse/Touchscreen).
pub touch: Touch, pub touch: Touch,
/// The Asset Management system (DMA). Handles loading data into VRAM (TileBanks) and ARAM (SoundBanks). /// The Asset Management system (DMA). Handles loading data into VRAM (GlyphBanks) and ARAM (SoundBanks).
pub assets: AssetManager, pub assets: AssetManager,
} }
@ -92,7 +92,7 @@ impl Hardware {
gfx: Gfx::new( gfx: Gfx::new(
Self::W, Self::W,
Self::H, Self::H,
Arc::clone(&memory_banks) as Arc<dyn TileBankPoolAccess>, Arc::clone(&memory_banks) as Arc<dyn GlyphBankPoolAccess>,
), ),
audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>), audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>),
pad: Pad::default(), pad: Pad::default(),
@ -100,7 +100,7 @@ impl Hardware {
assets: AssetManager::new( assets: AssetManager::new(
vec![], vec![],
AssetsPayloadSource::empty(), AssetsPayloadSource::empty(),
Arc::clone(&memory_banks) as Arc<dyn TileBankPoolInstaller>, Arc::clone(&memory_banks) as Arc<dyn GlyphBankPoolInstaller>,
Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolInstaller>, Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolInstaller>,
), ),
} }

View File

@ -1,19 +1,19 @@
use prometeu_hal::glyph_bank::GlyphBank;
use prometeu_hal::sound_bank::SoundBank; use prometeu_hal::sound_bank::SoundBank;
use prometeu_hal::tile_bank::TileBank;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
/// Non-generic interface for peripherals to access graphical tile banks. /// Non-generic interface for peripherals to access graphical glyph banks.
pub trait TileBankPoolAccess: Send + Sync { pub trait GlyphBankPoolAccess: Send + Sync {
/// Returns a reference to the resident TileBank in the specified slot, if any. /// Returns a reference to the resident GlyphBank in the specified slot, if any.
fn tile_bank_slot(&self, slot: usize) -> Option<Arc<TileBank>>; fn glyph_bank_slot(&self, slot: usize) -> Option<Arc<GlyphBank>>;
/// Returns the total number of slots available in this bank. /// Returns the total number of slots available in this bank.
fn tile_bank_slot_count(&self) -> usize; fn glyph_bank_slot_count(&self) -> usize;
} }
/// Non-generic interface for the AssetManager to install graphical tile banks. /// Non-generic interface for the AssetManager to install graphical glyph banks.
pub trait TileBankPoolInstaller: Send + Sync { pub trait GlyphBankPoolInstaller: Send + Sync {
/// Atomically swaps the resident TileBank in the specified slot. /// Atomically swaps the resident GlyphBank in the specified slot.
fn install_tile_bank(&self, slot: usize, bank: Arc<TileBank>); fn install_glyph_bank(&self, slot: usize, bank: Arc<GlyphBank>);
} }
/// Non-generic interface for peripherals to access sound banks. /// Non-generic interface for peripherals to access sound banks.
@ -36,7 +36,7 @@ pub trait SoundBankPoolInstaller: Send + Sync {
/// Peripherals consume this state via narrow, non-generic traits. /// Peripherals consume this state via narrow, non-generic traits.
/// AssetManager coordinates residency and installs assets into these slots. /// AssetManager coordinates residency and installs assets into these slots.
pub struct MemoryBanks { pub struct MemoryBanks {
tile_bank_pool: Arc<RwLock<[Option<Arc<TileBank>>; 16]>>, glyph_bank_pool: Arc<RwLock<[Option<Arc<GlyphBank>>; 16]>>,
sound_bank_pool: Arc<RwLock<[Option<Arc<SoundBank>>; 16]>>, sound_bank_pool: Arc<RwLock<[Option<Arc<SoundBank>>; 16]>>,
} }
@ -50,26 +50,26 @@ impl MemoryBanks {
/// Creates a new set of memory banks with empty slots. /// Creates a new set of memory banks with empty slots.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
tile_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))), glyph_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
sound_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))), sound_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
} }
} }
} }
impl TileBankPoolAccess for MemoryBanks { impl GlyphBankPoolAccess for MemoryBanks {
fn tile_bank_slot(&self, slot: usize) -> Option<Arc<TileBank>> { fn glyph_bank_slot(&self, slot: usize) -> Option<Arc<GlyphBank>> {
let pool = self.tile_bank_pool.read().unwrap(); let pool = self.glyph_bank_pool.read().unwrap();
pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone)) pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone))
} }
fn tile_bank_slot_count(&self) -> usize { fn glyph_bank_slot_count(&self) -> usize {
16 16
} }
} }
impl TileBankPoolInstaller for MemoryBanks { impl GlyphBankPoolInstaller for MemoryBanks {
fn install_tile_bank(&self, slot: usize, bank: Arc<TileBank>) { fn install_glyph_bank(&self, slot: usize, bank: Arc<GlyphBank>) {
let mut pool = self.tile_bank_pool.write().unwrap(); let mut pool = self.glyph_bank_pool.write().unwrap();
if slot < 16 { if slot < 16 {
pool[slot] = Some(bank); pool[slot] = Some(bank);
} }

View File

@ -6,7 +6,7 @@ pub type AssetId = i32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum BankType { pub enum BankType {
TILES, GLYPH,
SOUNDS, SOUNDS,
// TILEMAPS, // TILEMAPS,
// BLOBS, // BLOBS,
@ -31,7 +31,7 @@ pub struct AssetEntry {
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TilesMetadata { pub struct GlyphsMetadata {
pub tile_size: u32, pub tile_size: u32,
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
@ -46,15 +46,15 @@ pub struct SoundsMetadata {
} }
impl AssetEntry { impl AssetEntry {
pub fn metadata_as_tiles(&self) -> Result<TilesMetadata, String> { pub fn metadata_as_glyphs(&self) -> Result<GlyphsMetadata, String> {
if self.bank_type != BankType::TILES { if self.bank_type != BankType::GLYPH {
return Err(format!( return Err(format!(
"Asset {} is not a TILES bank (type: {:?})", "Asset {} is not a GLYPH bank (type: {:?})",
self.asset_id, self.bank_type self.asset_id, self.bank_type
)); ));
} }
serde_json::from_value(self.metadata.clone()) serde_json::from_value(self.metadata.clone())
.map_err(|e| format!("Invalid TILES metadata for asset {}: {}", self.asset_id, e)) .map_err(|e| format!("Invalid GLYPH metadata for asset {}: {}", self.asset_id, e))
} }
pub fn metadata_as_sounds(&self) -> Result<SoundsMetadata, String> { pub fn metadata_as_sounds(&self) -> Result<SoundsMetadata, String> {
@ -130,7 +130,7 @@ pub struct SlotRef {
impl SlotRef { impl SlotRef {
pub fn gfx(index: usize) -> Self { pub fn gfx(index: usize) -> Self {
Self { asset_type: BankType::TILES, index } Self { asset_type: BankType::GLYPH, index }
} }
pub fn audio(index: usize) -> Self { pub fn audio(index: usize) -> Self {

View File

@ -200,7 +200,7 @@ mod tests {
use super::*; use super::*;
use crate::asset::{AssetCodec, AssetEntry, BankType, PreloadEntry}; use crate::asset::{AssetCodec, AssetEntry, BankType, PreloadEntry};
use crate::cartridge::{ASSETS_PA_MAGIC, ASSETS_PA_SCHEMA_VERSION, AssetsPackPrelude}; use crate::cartridge::{ASSETS_PA_MAGIC, ASSETS_PA_SCHEMA_VERSION, AssetsPackPrelude};
use crate::tile_bank::TILE_BANK_PALETTE_COUNT_V1; use crate::glyph_bank::GLYPH_BANK_PALETTE_COUNT_V1;
use serde_json::json; use serde_json::json;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
@ -363,17 +363,17 @@ mod tests {
AssetEntry { AssetEntry {
asset_id: 7, asset_id: 7,
asset_name: "tiles".to_string(), asset_name: "tiles".to_string(),
bank_type: BankType::TILES, bank_type: BankType::GLYPH,
offset, offset,
size, size,
decoded_size: 16 * 16 + (TILE_BANK_PALETTE_COUNT_V1 as u64 * 16 * 2), decoded_size: 16 * 16 + (GLYPH_BANK_PALETTE_COUNT_V1 as u64 * 16 * 2),
codec: AssetCodec::None, codec: AssetCodec::None,
metadata: json!({ metadata: json!({
"tile_size": 16, "tile_size": 16,
"width": 16, "width": 16,
"height": 16, "height": 16,
"palette_count": TILE_BANK_PALETTE_COUNT_V1, "palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": TILE_BANK_PALETTE_COUNT_V1 "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
}), }),
} }
} }
@ -446,17 +446,17 @@ mod tests {
AssetEntry { AssetEntry {
asset_id: 8, asset_id: 8,
asset_name: "other_tiles".to_string(), asset_name: "other_tiles".to_string(),
bank_type: BankType::TILES, bank_type: BankType::GLYPH,
offset: 4, offset: 4,
size: 4, size: 4,
decoded_size: 16 * 16 + (TILE_BANK_PALETTE_COUNT_V1 as u64 * 16 * 2), decoded_size: 16 * 16 + (GLYPH_BANK_PALETTE_COUNT_V1 as u64 * 16 * 2),
codec: AssetCodec::None, codec: AssetCodec::None,
metadata: json!({ metadata: json!({
"tile_size": 16, "tile_size": 16,
"width": 16, "width": 16,
"height": 16, "height": 16,
"palette_count": TILE_BANK_PALETTE_COUNT_V1, "palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": TILE_BANK_PALETTE_COUNT_V1 "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
}), }),
}, },
]; ];
@ -507,7 +507,7 @@ mod tests {
"asset_table": [{ "asset_table": [{
"asset_id": 7, "asset_id": 7,
"asset_name": "tiles", "asset_name": "tiles",
"bank_type": "TILES", "bank_type": "GLYPH",
"offset": 0, "offset": 0,
"size": 4, "size": 4,
"decoded_size": 768, "decoded_size": 768,
@ -516,8 +516,8 @@ mod tests {
"tile_size": 16, "tile_size": 16,
"width": 16, "width": 16,
"height": 16, "height": 16,
"palette_count": TILE_BANK_PALETTE_COUNT_V1, "palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": TILE_BANK_PALETTE_COUNT_V1 "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
} }
}], }],
"preload": [] "preload": []
@ -549,7 +549,7 @@ mod tests {
"asset_table": [{ "asset_table": [{
"asset_id": 7, "asset_id": 7,
"asset_name": "tiles", "asset_name": "tiles",
"bank_type": "TILES", "bank_type": "GLYPH",
"offset": 0, "offset": 0,
"size": 4, "size": 4,
"decoded_size": 768, "decoded_size": 768,
@ -558,8 +558,8 @@ mod tests {
"tile_size": 16, "tile_size": 16,
"width": 16, "width": 16,
"height": 16, "height": 16,
"palette_count": TILE_BANK_PALETTE_COUNT_V1, "palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": TILE_BANK_PALETTE_COUNT_V1 "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
} }
}], }],
"preload": [] "preload": []

View File

@ -1,7 +1,7 @@
use crate::color::Color; use crate::color::Color;
pub const TILE_BANK_PALETTE_COUNT_V1: usize = 64; pub const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64;
pub const TILE_BANK_COLORS_PER_PALETTE: usize = 16; pub const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16;
/// Standard sizes for square tiles. /// Standard sizes for square tiles.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
@ -16,12 +16,12 @@ pub enum TileSize {
/// A container for graphical assets. /// A container for graphical assets.
/// ///
/// A TileBank stores the decoded runtime representation of a tile-bank asset. /// A GlyphBank stores the decoded runtime representation of a glyph-bank asset.
/// ///
/// Serialized `assets.pa` payloads keep pixel indices packed as `4bpp` nibbles. /// Serialized `assets.pa` payloads keep pixel indices packed as `4bpp` nibbles.
/// After decode, the runtime expands them to one `u8` palette index per pixel /// After decode, the runtime expands them to one `u8` palette index per pixel
/// while preserving the same `0..15` logical range. /// while preserving the same `0..15` logical range.
pub struct TileBank { pub struct GlyphBank {
/// Dimension of each individual tile in the bank. /// Dimension of each individual tile in the bank.
pub tile_size: TileSize, pub tile_size: TileSize,
/// Width of the full bank sheet in pixels. /// Width of the full bank sheet in pixels.
@ -34,18 +34,18 @@ pub struct TileBank {
/// Index 0 is always reserved for transparency. /// Index 0 is always reserved for transparency.
pub pixel_indices: Vec<u8>, pub pixel_indices: Vec<u8>,
/// Runtime-facing v1 palette table: 64 palettes of 16 RGB565 colors each. /// Runtime-facing v1 palette table: 64 palettes of 16 RGB565 colors each.
pub palettes: [[Color; TILE_BANK_COLORS_PER_PALETTE]; TILE_BANK_PALETTE_COUNT_V1], pub palettes: [[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1],
} }
impl TileBank { impl GlyphBank {
/// Creates an empty tile bank with the specified dimensions. /// Creates an empty glyph bank with the specified dimensions.
pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self { pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self {
Self { Self {
tile_size, tile_size,
width, width,
height, height,
pixel_indices: vec![0; width * height], // Index 0 = Transparent pixel_indices: vec![0; width * height], // Index 0 = Transparent
palettes: [[Color::BLACK; TILE_BANK_COLORS_PER_PALETTE]; TILE_BANK_PALETTE_COUNT_V1], palettes: [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1],
} }
} }

View File

@ -8,6 +8,7 @@ pub mod color;
pub mod debugger_protocol; pub mod debugger_protocol;
pub mod gfx_bridge; pub mod gfx_bridge;
pub mod glyph; pub mod glyph;
pub mod glyph_bank;
pub mod hardware_bridge; pub mod hardware_bridge;
pub mod host_context; pub mod host_context;
pub mod host_return; pub mod host_return;
@ -22,7 +23,6 @@ pub mod sprite;
pub mod syscalls; pub mod syscalls;
pub mod telemetry; pub mod telemetry;
pub mod tile; pub mod tile;
pub mod tile_bank;
pub mod tile_layer; pub mod tile_layer;
pub mod touch_bridge; pub mod touch_bridge;
pub mod vm_fault; pub mod vm_fault;

View File

@ -1,6 +1,6 @@
use crate::glyph_bank::TileSize;
use crate::glyph_bank::TileSize::Size8;
use crate::tile::Tile; use crate::tile::Tile;
use crate::tile_bank::TileSize;
use crate::tile_bank::TileSize::Size8;
pub struct TileMap { pub struct TileMap {
pub width: usize, pub width: usize,

View File

@ -481,7 +481,7 @@ impl NativeInterface for VirtualMachineRuntime {
} }
Syscall::BankInfo => { Syscall::BankInfo => {
let asset_type = match expect_int(args, 0)? as u32 { let asset_type = match expect_int(args, 0)? as u32 {
0 => BankType::TILES, 0 => BankType::GLYPH,
1 => BankType::SOUNDS, 1 => BankType::SOUNDS,
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
}; };
@ -492,7 +492,7 @@ impl NativeInterface for VirtualMachineRuntime {
} }
Syscall::BankSlotInfo => { Syscall::BankSlotInfo => {
let asset_type = match expect_int(args, 0)? as u32 { let asset_type = match expect_int(args, 0)? as u32 {
0 => BankType::TILES, 0 => BankType::GLYPH,
1 => BankType::SOUNDS, 1 => BankType::SOUNDS,
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
}; };

View File

@ -12,8 +12,8 @@ use prometeu_hal::asset::{
AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus, AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus,
}; };
use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge}; use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge};
use prometeu_hal::glyph_bank::GLYPH_BANK_PALETTE_COUNT_V1;
use prometeu_hal::syscalls::caps; use prometeu_hal::syscalls::caps;
use prometeu_hal::tile_bank::TILE_BANK_PALETTE_COUNT_V1;
use prometeu_vm::VmInitError; use prometeu_vm::VmInitError;
use std::collections::HashMap; use std::collections::HashMap;
@ -94,37 +94,37 @@ fn serialized_single_function_module_with_consts(
.serialize() .serialize()
} }
fn test_tile_payload_size(width: usize, height: usize) -> usize { fn test_glyph_payload_size(width: usize, height: usize) -> usize {
(width * height).div_ceil(2) + (TILE_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u16>()) (width * height).div_ceil(2) + (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u16>())
} }
fn test_tile_decoded_size(width: usize, height: usize) -> usize { fn test_glyph_decoded_size(width: usize, height: usize) -> usize {
width * height + (TILE_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u16>()) width * height + (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::<u16>())
} }
fn test_tile_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry { fn test_glyph_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry {
AssetEntry { AssetEntry {
asset_id: 7, asset_id: 7,
asset_name: asset_name.to_string(), asset_name: asset_name.to_string(),
bank_type: BankType::TILES, bank_type: BankType::GLYPH,
offset: 0, offset: 0,
size: data_len as u64, size: data_len as u64,
decoded_size: test_tile_decoded_size(16, 16) as u64, decoded_size: test_glyph_decoded_size(16, 16) as u64,
codec: AssetCodec::None, codec: AssetCodec::None,
metadata: serde_json::json!({ metadata: serde_json::json!({
"tile_size": 16, "tile_size": 16,
"width": 16, "width": 16,
"height": 16, "height": 16,
"palette_count": TILE_BANK_PALETTE_COUNT_V1, "palette_count": GLYPH_BANK_PALETTE_COUNT_V1,
"palette_authored": TILE_BANK_PALETTE_COUNT_V1 "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1
}), }),
} }
} }
fn test_tile_asset_data() -> Vec<u8> { fn test_glyph_asset_data() -> Vec<u8> {
let mut data = let mut data =
vec![0x11u8; test_tile_payload_size(16, 16) - (TILE_BANK_PALETTE_COUNT_V1 * 16 * 2)]; vec![0x11u8; test_glyph_payload_size(16, 16) - (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * 2)];
data.extend_from_slice(&[0u8; TILE_BANK_PALETTE_COUNT_V1 * 16 * 2]); data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_COUNT_V1 * 16 * 2]);
data data
} }
@ -439,10 +439,10 @@ fn tick_gfx_set_sprite_invalid_range_returns_status_not_crash() {
}], }],
); );
let cartridge = cartridge_with_program(program, caps::GFX); let cartridge = cartridge_with_program(program, caps::GFX);
let asset_data = test_tile_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( hardware.assets.initialize_for_cartridge(
vec![test_tile_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }], vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
); );
@ -637,9 +637,9 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
let mut vm = VirtualMachine::default(); let mut vm = VirtualMachine::default();
let mut hardware = Hardware::new(); let mut hardware = Hardware::new();
let signals = InputSignals::default(); let signals = InputSignals::default();
let asset_data = test_tile_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( hardware.assets.initialize_for_cartridge(
vec![test_tile_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![], vec![],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
); );
@ -721,9 +721,9 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
); );
let cartridge = cartridge_with_program(program, caps::ASSET); let cartridge = cartridge_with_program(program, caps::ASSET);
let asset_data = test_tile_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( hardware.assets.initialize_for_cartridge(
vec![test_tile_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![], vec![],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
); );
@ -782,9 +782,9 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
); );
let cartridge = cartridge_with_program(program, caps::ASSET); let cartridge = cartridge_with_program(program, caps::ASSET);
let asset_data = test_tile_asset_data(); let asset_data = test_glyph_asset_data();
hardware.assets.initialize_for_cartridge( hardware.assets.initialize_for_cartridge(
vec![test_tile_asset_entry("tile_asset", asset_data.len())], vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
vec![], vec![],
AssetsPayloadSource::from_bytes(asset_data), AssetsPayloadSource::from_bytes(asset_data),
); );

View File

@ -166,7 +166,7 @@ impl VirtualMachineRuntime {
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
let gfx_stats = hw.assets().bank_info(BankType::TILES); let gfx_stats = hw.assets().bank_info(BankType::GLYPH);
self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes;
self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes;
self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32; self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32;

View File

@ -67,7 +67,7 @@ pub fn generate() -> Result<()> {
const_pool: vec![ const_pool: vec![
ConstantPoolEntry::String("stress".into()), ConstantPoolEntry::String("stress".into()),
ConstantPoolEntry::String("frame".into()), ConstantPoolEntry::String("frame".into()),
ConstantPoolEntry::String("missing_tile_bank".into()), ConstantPoolEntry::String("missing_glyph_bank".into()),
], ],
functions, functions,
code: rom, code: rom,

View File

@ -1,6 +1,7 @@
{"type":"meta","next_id":{"DSC":22,"AGD":20,"DEC":6,"PLN":5,"LSN":25,"CLSN":1}} {"type":"meta","next_id":{"DSC":23,"AGD":21,"DEC":7,"PLN":6,"LSN":25,"CLSN":1}}
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]} {"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
{"type":"discussion","id":"DSC-0022","status":"open","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[{"id":"AGD-0020","file":"AGD-0020-tile-bank-vs-glyph-bank-domain-naming.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0006","file":"DEC-0006-glyph-bank-domain-naming-contract.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_agenda":"AGD-0020"}],"plans":[{"id":"PLN-0005","file":"PLN-0005-glyph-bank-domain-naming-execution.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10","ref_decisions":["DEC-0006"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} {"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
{"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}

View File

@ -123,7 +123,7 @@ The current asset model works better if you do not treat every bank as equally g
Some banks are specialized: Some banks are specialized:
- `TILES` - `GLYPH`
- `SOUNDS` - `SOUNDS`
For these, the bank contract already carries most of the important format rules. That means: For these, the bank contract already carries most of the important format rules. That means:
@ -163,7 +163,7 @@ It does not say:
One of the most important asset-management lessons in the current runtime is that `codec = NONE` does not mean "bitwise identical to in-memory layout". One of the most important asset-management lessons in the current runtime is that `codec = NONE` does not mean "bitwise identical to in-memory layout".
The tile-bank path is the concrete example: The glyph-bank path is the concrete example:
- there is no additional generic codec layer for the asset; - there is no additional generic codec layer for the asset;
- the serialized payload stores indexed pixels as packed `4bpp`; - the serialized payload stores indexed pixels as packed `4bpp`;
@ -182,13 +182,13 @@ That is why `AssetEntry.size` and `AssetEntry.decoded_size` must be thought of a
If those two numbers are treated as interchangeable, telemetry, budgets, and validation all become misleading. If those two numbers are treated as interchangeable, telemetry, budgets, and validation all become misleading.
## Tile Banks Teach The Real Boundary ## Glyph Banks Teach The Real Boundary
Tile banks are useful because they show the real separation of concerns: Glyph banks are useful because they show the real separation of concerns:
- `assets.pa` defines the serialized envelope and metadata needed to reconstruct the bank; - `assets.pa` defines the serialized envelope and metadata needed to reconstruct the bank;
- the runtime validates that metadata against the expected v1 contract; - the runtime validates that metadata against the expected v1 contract;
- the resident `TileBank` is a runtime object, not a direct view over the cold bytes. - the resident `GlyphBank` is a runtime object, not a direct view over the cold bytes.
This is the right PROMETEU mental model: This is the right PROMETEU mental model:

View File

@ -18,7 +18,7 @@ PROMETEU treats graphics as an explicit peripheral, not as a modern GPU.
The right mental model is a retro 2D machine with: The right mental model is a retro 2D machine with:
- framebuffer; - framebuffer;
- tile banks; - glyph banks;
- tile layers; - tile layers;
- sprites ordered by draw order; - sprites ordered by draw order;
- deterministic composition per frame. - deterministic composition per frame.
@ -67,7 +67,7 @@ That enables:
- HUD themes; - HUD themes;
- day and night cycles. - day and night cycles.
In the current tile-bank v1 baseline, this palette model is intentionally bounded: In the current glyph-bank v1 baseline, this palette model is intentionally bounded:
- each bank carries `64` palettes; - each bank carries `64` palettes;
- each palette carries `16` colors; - each palette carries `16` colors;
@ -75,7 +75,7 @@ In the current tile-bank v1 baseline, this palette model is intentionally bounde
That limit is not incidental bookkeeping. It is part of how art packaging, runtime validation, and rendering stay aligned. That limit is not incidental bookkeeping. It is part of how art packaging, runtime validation, and rendering stay aligned.
## Tile Banks Are Decoded Runtime Objects ## Glyph Banks Are Decoded Runtime Objects
The most useful intuition is to separate three layers: The most useful intuition is to separate three layers:
@ -83,13 +83,13 @@ The most useful intuition is to separate three layers:
- serialized cart payload; - serialized cart payload;
- resident runtime bank. - resident runtime bank.
For tile banks in v1: For glyph banks in v1:
- authored pixels are logical indices `0..15`; - authored pixels are logical indices `0..15`;
- serialized payload stores those indices as packed `4bpp`; - serialized payload stores those indices as packed `4bpp`;
- runtime memory expands them to one `u8` index per pixel after decode. - runtime memory expands them to one `u8` index per pixel after decode.
So when reading the graphics model, do not imagine the renderer reading packed nibbles directly from cartridge storage. The renderer consumes a decoded `TileBank` object whose memory shape is optimized for runtime lookup, not for transport density. So when reading the graphics model, do not imagine the renderer reading packed nibbles directly from cartridge storage. The renderer consumes a decoded `GlyphBank` object whose memory shape is optimized for runtime lookup, not for transport density.
## Use Cases ## Use Cases

View File

@ -1,7 +1,7 @@
# LSN-0022: Tilemap Empty Cell Semantics and Glyph Convergence # LSN-0022: Tilemap Empty Cell Semantics and Glyph Convergence
## Context ## Context
During the initial runtime design, `tile_id = 0` was used as a sentinel value for "empty" or "missing" tiles in tilemaps. However, as the asset banking and packer systems evolved, it became clear that `0` should be a valid index for any asset bank, including tile banks. This conflict was formally tracked in `AGD-0015`. During the initial runtime design, `tile_id = 0` was used as a sentinel value for "empty" or "missing" tiles in tilemaps. However, as the asset banking and packer systems evolved, it became clear that `0` should be a valid index for any asset bank, including glyph banks. This conflict was formally tracked in `AGD-0015`.
## Lessons Learned ## Lessons Learned

View File

@ -16,8 +16,8 @@ O uso de `serde_json::Value` diretamente nos loaders do runtime introduz riscos
Para mitigar isso, implementamos o padrão de **Typed Metadata Helpers**: Para mitigar isso, implementamos o padrão de **Typed Metadata Helpers**:
1. **Structs Dedicadas**: Criamos structs Rust (ex: `TilesMetadata`, `SoundsMetadata`) que representam o contrato exato de cada banco. 1. **Structs Dedicadas**: Criamos structs Rust (ex: `GlyphsMetadata`, `SoundsMetadata`) que representam o contrato exato de cada banco.
2. **Conversion Methods**: Adicionamos métodos ao `AssetEntry` (ex: `metadata_as_tiles()`) que utilizam `serde_json::from_value` para realizar o "cast" do JSON dinâmico para a struct tipada. 2. **Conversion Methods**: Adicionamos métodos ao `AssetEntry` (ex: `metadata_as_glyphs()`) que utilizam `serde_json::from_value` para realizar o "cast" do JSON dinâmico para a struct tipada.
3. **Fail-Fast**: A falha no parsing dos metadados deve ser tratada como erro de carregamento do asset, garantindo que o motor não opere com metadados corrompidos ou incompletos. 3. **Fail-Fast**: A falha no parsing dos metadados deve ser tratada como erro de carregamento do asset, garantindo que o motor não opere com metadados corrompidos ou incompletos.
### Benefícios ### Benefícios

View File

@ -0,0 +1,221 @@
---
id: AGD-0020
ticket: tile-bank-vs-glyph-bank-domain-naming
title: Agenda - Tile Bank vs Glyph Bank Domain Naming
status: accepted
created: 2026-04-09
resolved: 2026-04-10
decision: DEC-0006
tags: [gfx, runtime, naming, domain-model]
---
# Agenda - Tile Bank vs Glyph Bank Domain Naming
## Contexto
Hoje o runtime usa `TileBank`, `tile_bank.rs`, `TileBankPool*` e termos derivados para nomear o banco grafico consumido pelo renderer e pelo pipeline de assets.
Ao mesmo tempo, existe a vontade de elevar `Tile` para um conceito mais amplo do dominio, nao restrito ao sheet grafico concreto. Nessa leitura:
- `Tile` passa a ser uma ideia mais geral da grade/elemento logico;
- o que hoje e o artefato grafico concreto chamado `TileBank` deveria passar a se chamar `GlyphBank`;
- a intencao inicial nao e mudar formato, memoria, payload ou algoritmo, e sim alinhar a linguagem do projeto.
Esse tema ja encosta em documentacao e lessons existentes, porque o vocabulario atual mistura:
- tile como unidade de composicao visual;
- tile bank como sheet grafico concreto;
- referencias esparsas a glyph em lições e agendas.
## Problema
Se o projeto mudar o centro semantico de `tile` e `glyph` sem uma decisao explicita, o repositorio tende a ficar com vocabulário hibrido:
- tipos antigos com nome legado;
- docs novas com nome novo;
- renderer e assets falando uma lingua;
- lessons e discussoes falando outra.
O problema principal nao e tecnico-algoritmico. E semantico e operacional: qual vocabulario o projeto quer tornar canonico para o banco grafico concreto, e qual parte dessa mudanca e apenas rename mecanico versus mudanca real de modelo.
## Pontos Criticos
1. Escopo da mudanca.
Precisamos separar rename de dominio de qualquer mudanca de formato ou comportamento.
2. Contrato externo.
Precisamos fechar quais contratos externos tambem entram no rename. Ja existe direcao para migrar `BankType::TILES` para `BankType::GLYPH`.
3. Consistencia de linguagem.
A mudanca so vale a pena se atingir codigo, tests, docs e lessons de forma coordenada.
4. Custo de churn.
Mesmo sem mudar comportamento, o rename atravessa muitos modulos (`hal`, `drivers`, renderer, pools, mensagens de erro, tests e docs).
5. Fronteira conceitual.
Precisamos definir o que `Tile` passa a significar depois do rename, para evitar trocar um overload semantico por outro.
## Opcoes
### Opcao A - Manter `TileBank` como esta
- **Abordagem:** preservar `TileBank` como nome canonico do banco grafico concreto e aceitar que `tile` continue carregando tanto o lado logico quanto o lado visual.
- **Pro:** zero churn nominal imediato e nenhuma migracao de docs/codigo.
- **Con:** o overload conceitual de `tile` permanece e pode continuar poluindo a linguagem de dominio.
- **Tradeoff:** economiza trabalho agora ao custo de clareza futura.
### Opcao B - Renomear `TileBank` para `GlyphBank` como refactor semantico
- **Abordagem:** tratar a mudanca como rename consistente de tipos, modulos, docs, testes e mensagens, sem alterar formato de payload, layout em memoria ou renderer.
- **Pro:** melhora a linguagem do projeto sem reabrir a arquitetura grafica.
- **Con:** exige disciplina para manter a promessa de “rename only” e nao misturar isso com redesign.
- **Tradeoff:** churn mecanico relativamente alto para ganhar clareza conceitual.
### Opcao C - Fazer rename parcial
- **Abordagem:** adotar `glyph` apenas em docs novas ou em algumas camadas, preservando nomes antigos em APIs e modulos centrais.
- **Pro:** menor custo inicial.
- **Con:** cria o pior estado intermediario: dois vocabulários concorrentes para o mesmo conceito.
- **Tradeoff:** parece barato, mas deixa a linguagem do projeto menos confiavel.
## Sugestao / Recomendacao
Seguir inicialmente com a **Opcao B**, desde que a discussao confirme que a mudanca e de nomenclatura e nao de semantica operacional.
A recomendacao provisoria e:
- `GlyphBank` se torna o nome do artefato grafico concreto que hoje chamamos de `TileBank`;
- `Tile` fica livre para representar um conceito mais geral do dominio;
- `BankType::TILES` passa a `BankType::GLYPH`;
- a migracao deve ser consistente em codigo, tests, docs e lessons;
- `TileLayer` e derivados nao entram automaticamente no rename, porque ja pertencem a outra fronteira de dominio e precisam de triagem separada;
- a discussao deve explicitar quais superfícies mudam juntas para impedir vocabulario hibrido sem reabrir a arquitetura grafica.
## Perguntas em Aberto
- Confirmar a superficie exata do rename para evitar misturar banco grafico concreto com conceitos de layer/mapa.
## Discussao
### Direcao fechada ate aqui
1. **Natureza da mudanca**
A mudanca e `rename-only`. Nao ha intencao de alterar formato, algoritmo, layout em memoria ou comportamento.
2. **Contrato externo**
`BankType::TILES` nao deve permanecer. O contrato deve migrar para `BankType::GLYPH`.
3. **Escopo documental**
A migracao deve ser completa, inclusive em documentacao e lessons historicas.
4. **Colisao semantica**
`glyph` nao colide com outro artefato canonico relevante nesta etapa.
### Fronteira importante
Nem todo `Tile*` deve migrar automaticamente para `Glyph*`.
Existe uma fronteira entre:
- nomes que descrevem o banco grafico concreto e seu circuito de carga/uso; e
- nomes que ja descrevem dominio de layer, mapa, grade ou composicao.
Por isso, a proxima resposta que falta precisa ser dada por superficie concreta, e nao por regra global simplista.
### Lista para voto `sim` / `nao`
Responda `sim` ou `nao` para cada grupo abaixo.
1. **Banco concreto e modulo base**
Inclui `TileBank`, `tile_bank.rs`, comentarios e docs especificos desse tipo.
2. **Enum e contrato de asset**
Inclui `BankType::TILES -> BankType::GLYPH`, mensagens de erro, metadata helpers e referencias equivalentes no path de assets.
3. **Pools e interfaces de memoria**
Inclui `TileBankPoolAccess`, `TileBankPoolInstaller`, `tile_bank_slot`, `install_tile_bank`, campos internos como `tile_bank_pool`.
4. **Asset manager e loader path**
Inclui funcoes como `decode_tile_bank_*`, `perform_load_tile_bank`, variaveis locais, testes e mensagens associadas ao load do banco grafico.
5. **Renderer e hardware references ao banco**
Inclui usos em `gfx.rs` e `hardware.rs` que referenciam o banco concreto, por exemplo campos `tile_banks`, docs sobre VRAM `TileBanks`, parametros `bank: &TileBank`.
6. **Modulo e tipos de layer/mapa**
Inclui `tile_layer.rs`, `TileMap`, `TileLayer`, `ScrollableTileLayer`, `HudTileLayer`.
Este grupo e o mais sensivel porque pode nao ser rename de banco concreto, e sim outro dominio.
7. **TileSize**
Inclui `TileSize` e referencias ao tamanho de tile como unidade geometrica.
Este grupo pode continuar como `TileSize` mesmo se o banco virar `GlyphBank`.
8. **Strings, fixtures e test names**
Inclui nomes de testes, helper names, snapshots, mensagens e dados de teste que ainda falam em tile bank.
9. **Lessons e docs historicas**
Inclui lessons ja publicadas e material de discussao/mental model que hoje falam em `TileBank`.
### Votos registrados
1. **Banco concreto e modulo base**: `sim`
`TileBank`, `tile_bank.rs`, modulo base e documentacao associada entram na migracao.
2. **Enum e contrato de asset**: `sim`
`BankType::TILES` e o contrato textual equivalente entram na migracao para `GLYPH`.
3. **Pools e interfaces de memoria**: `sim`
`TileBankPool*`, `tile_bank_slot` e derivados entram na migracao.
4. **Asset manager e loader path**: `sim`
Helpers, decode path, mensagens e testes associados ao banco grafico concreto entram na migracao.
5. **Renderer e hardware references ao banco**: `sim`
Referencias ao banco concreto em renderer e hardware entram na migracao.
6. **Modulo e tipos de layer/mapa**: `nao`
`TileLayer`, `TileMap`, `ScrollableTileLayer` e `HudTileLayer` ficam fora desta rodada.
7. **TileSize**: `nao`
`TileSize` permanece como conceito geometrico e nao entra automaticamente no rename.
8. **Strings, fixtures e test names**: `sim`
Helpers, fixtures, mensagens e nomes de testes entram na migracao para evitar residuos do vocabulario antigo.
9. **Lessons e docs historicas**: `sim`
A migracao documental e historica entra no escopo, seguindo a mesma fronteira desta agenda: renomear o banco grafico concreto sem arrastar automaticamente o dominio de layer/mapa.
## Resolucao Provisoria
Ha consenso provisoriamente estabelecido sobre os seguintes pontos:
- a mudanca e `rename-only`;
- o vocabulario canonico do banco grafico concreto passa de `TileBank` para `GlyphBank`;
- `BankType::TILES` deve migrar para `BankType::GLYPH`;
- pools, contrato de asset, loader path, renderer, hardware references, fixtures e nomes de teste entram na migracao;
- `TileLayer`/`TileMap` e derivados ficam fora desta rodada;
- `TileSize` fica fora desta rodada;
- docs e lessons historicas entram no escopo, respeitando essa mesma fronteira.
O principal ponto restante para encerrar a agenda e confirmar se essa resolucao ja e suficiente para virar decisao normativa, ou se ainda falta detalhar como a migracao documental deve tratar referencias historicas em texto corrido quando `tile` continuar correto para layer/mapa.
## Resolucao
A agenda fica encerrada com a seguinte orientacao:
- a mudanca e `rename-only`;
- o banco grafico concreto passa a se chamar `GlyphBank`;
- `BankType::TILES` passa a `BankType::GLYPH`;
- pools, contrato de asset, loader path, renderer, hardware references, fixtures, nomes de teste, docs e lessons entram na migracao;
- `TileLayer`, `TileMap` e derivados ficam fora;
- `TileSize` fica fora;
- docs e lessons antigas devem migrar referencias ao banco grafico concreto para `GlyphBank`, mantendo `tile` quando o assunto for layer, mapa, grade ou geometria.
## Criterio para Encerrar
Esta agenda pode ser encerrada quando houver consenso escrito sobre:
- se a mudanca e rename-only ou nao;
- qual vocabulario passa a ser canonico;
- quais contratos externos entram no rename;
- quais grupos concretos entram ou nao na migracao;
- como evitar que `TileLayer`/`TileMap` sejam arrastados automaticamente sem decisao propria.

View File

@ -0,0 +1,93 @@
---
id: DEC-0006
ticket: tile-bank-vs-glyph-bank-domain-naming
title: Glyph Bank Domain Naming Contract
status: accepted
created: 2026-04-10
accepted: 2026-04-10
agenda: AGD-0020
plans: [PLN-0005]
tags: [gfx, runtime, naming, domain-model]
---
## Status
Accepted on 2026-04-10.
## Contexto
The runtime currently uses `TileBank` and related names for the concrete graphical bank consumed by the asset pipeline and renderer. At the same time, the project wants `Tile` to remain available as a broader domain concept for grid, layer, map, and geometric tile semantics.
This decision closes the naming contract by separating:
- the concrete graphical bank artifact;
- the logical tile/layer/map domain;
- the migration boundary for code, tests, docs, and historical lessons.
The change is explicitly rename-only. It does not alter format, runtime behavior, memory layout, payload structure, or renderer algorithms.
## Decisao
1. The concrete graphical bank currently named `TileBank` MUST be renamed to `GlyphBank`.
2. This rename MUST be treated as nomenclature-only. It MUST NOT change payload format, runtime behavior, memory layout, codec behavior, metadata structure, or rendering semantics.
3. `BankType::TILES` MUST be renamed to `BankType::GLYPH`.
4. The asset contract and runtime code that refer to the concrete graphical bank MUST migrate to `Glyph*` naming consistently.
5. The following groups MUST be included in the rename:
- concrete bank type and module;
- asset contract and asset-facing terminology;
- memory pools and bank installation/access interfaces;
- asset manager decode/load path;
- renderer and hardware references to the concrete bank;
- fixtures, test names, helper names, and user-facing/runtime-facing strings for the concrete bank;
- documentation and historical lessons, subject to the editorial boundary below.
6. The following groups MUST NOT be renamed as part of this decision:
- `TileLayer`, `TileMap`, `ScrollableTileLayer`, `HudTileLayer`, and related layer/map structures;
- `TileSize` and geometric tile-size concepts.
7. Documentation and historical lessons MUST be updated with the following editorial rule:
- references to the concrete graphical bank artifact MUST migrate to `GlyphBank` and equivalent `Glyph*` naming;
- references to layer, map, grid, or geometric tile concepts MUST remain `tile` when that is the correct domain meaning.
8. The project MUST NOT adopt a partial mixed vocabulary where the same concrete bank artifact is simultaneously described as both `TileBank` and `GlyphBank` in active code or maintained documentation.
9. `glyph` is treated as a new canonical artifact name for this stage and does not conflict with an existing canonical artifact that would block adoption.
## Rationale
- The current name overloads `tile` across both logical map/layer concepts and the concrete graphical bank artifact.
- Renaming only the concrete bank artifact improves domain clarity without reopening graphics architecture.
- Excluding `TileLayer`, `TileMap`, and `TileSize` preserves established semantics where `tile` still means the logical or geometric concept.
- A full migration across code, tests, docs, and lessons avoids the unstable mixed-language state that tends to follow partial renames.
## Invariantes / Contrato
- `GlyphBank` is the canonical name for the concrete graphical bank artifact.
- `tile` remains canonical for layer/map/geometric concepts unless a later decision explicitly changes that.
- `BankType::GLYPH` is the canonical asset-bank enum variant for the concrete graphical bank.
- This decision is a rename boundary, not a behavior-change boundary.
- Documentation must follow artifact meaning, not mechanical string replacement.
## Impactos
- `prometeu-hal` will need type/module/enum renames for the concrete graphical bank path.
- `prometeu-drivers` will need renderer, memory-pool, hardware, and asset-manager naming migration for bank-specific references.
- Tests, fixtures, and helper names need coordinated updates to avoid mixed terminology.
- Docs and lessons need targeted rewriting rather than blind search-and-replace, because `tile` remains correct in map/layer contexts.
## Referencias
- AGD-0020: Tile Bank vs Glyph Bank Domain Naming
- LSN-0022: Tilemap Empty Cell Convergence
- `crates/console/prometeu-hal/src/tile_bank.rs`
- `crates/console/prometeu-hal/src/tile_layer.rs`
- `crates/console/prometeu-drivers/src/gfx.rs`
- `crates/console/prometeu-drivers/src/memory_banks.rs`
- `crates/console/prometeu-drivers/src/asset.rs`
## Propagacao Necessaria
- Write an execution plan before code and documentation migration.
- Rename the concrete bank surface consistently across runtime crates.
- Preserve `tile` naming in layer/map/geometric surfaces excluded by this decision.
- Update docs and lessons according to artifact meaning, not blanket replacement.
## Revision Log
- 2026-04-10: Initial accepted decision from AGD-0020.

View File

@ -0,0 +1,161 @@
---
id: PLN-0005
ticket: tile-bank-vs-glyph-bank-domain-naming
title: Glyph Bank Domain Naming Execution
status: accepted
created: 2026-04-10
completed:
tags: [gfx, runtime, naming, domain-model]
---
## Briefing
Implement DEC-0006 by renaming the concrete graphical bank artifact from `TileBank` to `GlyphBank` across runtime code, tests, documentation, and historical lessons, while preserving behavior and keeping the excluded tile-domain surfaces untouched. This is a rename-only migration. It must not alter payload format, rendering semantics, codec behavior, metadata structure, or memory layout.
## Decisions de Origem
- DEC-0006: Glyph Bank Domain Naming Contract
## Alvo
Land a consistent runtime-and-docs migration that:
- renames the concrete bank artifact and its module/type surface to `GlyphBank`;
- renames `BankType::TILES` to `BankType::GLYPH`;
- renames pool and loader/runtime bank-specific APIs accordingly;
- preserves `TileLayer`, `TileMap`, `ScrollableTileLayer`, `HudTileLayer`, and `TileSize`;
- rewrites maintained docs and lessons according to artifact meaning rather than blanket token replacement.
## Escopo
- `prometeu-hal` concrete bank type/module/enum renames.
- `prometeu-drivers` renderer, memory-bank, hardware, and asset-manager bank-specific renames.
- `prometeu-system` callsites and dispatch paths that refer to the asset-bank enum variant.
- Tests, helpers, fixtures, and strings that still describe the concrete bank as `TileBank`.
- Discussion lessons and maintained discussion artifacts that refer to the concrete graphical bank artifact.
## Fora de Escopo
- Any format, algorithm, codec, metadata, or layout change.
- Renaming `TileLayer`, `TileMap`, `ScrollableTileLayer`, `HudTileLayer`, or related layer/map structures.
- Renaming `TileSize` or geometric tile-size concepts.
- Redesigning tile/glyph semantics beyond the naming boundary established by DEC-0006.
## Plano de Execucao
### Step 1 - Rename the HAL concrete bank surface
**What:**
Rename the concrete bank module and type surface in `prometeu-hal` from `TileBank` to `GlyphBank`.
**How:**
Rename `tile_bank.rs` to `glyph_bank.rs`, update `lib.rs` exports/import paths, rename `TileBank` to `GlyphBank`, and rename concrete-bank-specific constants if they are artifact-specific rather than geometric. Keep `TileSize` unchanged even if it continues to live near the bank implementation or must be re-exported from the renamed module in a compatibility-preserving layout inside this refactor.
**File(s):**
- `crates/console/prometeu-hal/src/tile_bank.rs`
- `crates/console/prometeu-hal/src/lib.rs`
- any direct imports of `prometeu_hal::tile_bank::*` in runtime crates
### Step 2 - Rename the asset-bank contract
**What:**
Migrate the asset-bank enum and related asset-facing terminology from `TILES` to `GLYPH`.
**How:**
Rename `BankType::TILES` to `BankType::GLYPH`, update any JSON/serde expectations and validation paths, and rename asset-facing metadata helpers only where they describe the concrete bank artifact rather than the geometric tile model. Preserve behavior and fail-fast semantics unchanged.
**File(s):**
- `crates/console/prometeu-hal/src/asset.rs`
- `crates/console/prometeu-hal/src/cartridge_loader.rs`
- `crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs`
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
- `crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs`
### Step 3 - Rename memory pools and concrete-bank runtime APIs
**What:**
Rename bank-specific pool interfaces and storage paths that refer to the concrete graphical bank.
**How:**
Rename `TileBankPoolAccess`, `TileBankPoolInstaller`, `tile_bank_slot`, `install_tile_bank`, `tile_bank_pool`, and related fields/traits/vars to `Glyph*` equivalents. Keep semantics and slot behavior unchanged.
**File(s):**
- `crates/console/prometeu-drivers/src/memory_banks.rs`
- `crates/console/prometeu-drivers/src/hardware.rs`
- `crates/console/prometeu-drivers/src/gfx.rs`
- `crates/console/prometeu-drivers/src/asset.rs`
### Step 4 - Rename concrete-bank decode/load path and tests
**What:**
Update the asset manager and concrete-bank decode path to the new artifact name.
**How:**
Rename functions such as `decode_tile_bank_*`, `perform_load_tile_bank`, and related test helpers/messages to `GlyphBank` naming. Keep payload interpretation, palette handling, and runtime materialization identical to current behavior. Do not rename `TileSize`.
**File(s):**
- `crates/console/prometeu-drivers/src/asset.rs`
- affected tests in `crates/console/prometeu-drivers/src/asset.rs`
- any helper or stress/test utility strings such as `missing_tile_bank`
### Step 5 - Apply renderer and hardware editorial cleanup
**What:**
Rename only the references that describe the concrete bank artifact inside renderer and hardware documentation/comments.
**How:**
Update comments, docs, field names, and user-facing/runtime-facing strings so that references to the bank artifact become `GlyphBank`/`glyph bank`, while preserving `tile` terminology for layers, maps, and geometric concepts. Review `gfx.rs` carefully because it contains both domains.
**File(s):**
- `crates/console/prometeu-drivers/src/gfx.rs`
- `crates/console/prometeu-drivers/src/hardware.rs`
- any crate-level docs/comments on the bank artifact path
### Step 6 - Rewrite maintained lessons and discussion docs by meaning
**What:**
Migrate maintained lessons and relevant discussion text to the new canonical artifact name.
**How:**
Update published lessons and maintained discussion artifacts so that references to the concrete graphical bank become `GlyphBank`, but references to tile layers, tilemaps, grid semantics, or tile geometry remain `tile`. Use manual review, not blanket replacement.
**File(s):**
- `discussion/lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md`
- `discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md`
- `discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md`
- any maintained agenda/decision/plan artifacts still intentionally retained and referencing the concrete bank artifact
## Criterios de Aceite
- The canonical concrete bank type/module name in runtime code is `GlyphBank`.
- The canonical asset-bank enum variant is `BankType::GLYPH`.
- Pool interfaces and concrete-bank runtime helpers use `Glyph*` naming consistently.
- `TileLayer`, `TileMap`, `ScrollableTileLayer`, `HudTileLayer`, and `TileSize` remain unchanged.
- No behavior, payload format, or renderer semantics change as part of this migration.
- Tests, helper names, and runtime-facing strings do not keep active mixed `TileBank`/`GlyphBank` naming for the same artifact.
- Maintained lessons and docs use `GlyphBank` for the concrete bank artifact while preserving `tile` where it refers to layer/map/geometric concepts.
## Tests / Validacao
### Unit Tests
- Update and run unit tests that directly construct or decode the concrete graphical bank path.
- Confirm renamed bank/pool/helper symbols compile without compatibility shims.
### Integration Tests
- Run `prometeu-hal`, `prometeu-drivers`, and `prometeu-system` test targets affected by the rename.
- Verify asset loading still succeeds for the renamed `BankType::GLYPH` path.
### Manual Verification
- Review representative docs and lessons to confirm `GlyphBank` only replaces the concrete bank artifact meaning.
- Review `gfx.rs` and `tile_layer.rs` boundaries to confirm tile-domain structures were not accidentally renamed.
- Search the repository for residual `TileBank` references and classify any remaining occurrence as either acceptable historical residue outside scope or a migration miss.
## Riscos
- The repository uses `tile` in two different domains, so blind search-and-replace is likely to over-rename excluded surfaces.
- Renaming the enum variant from `TILES` to `GLYPH` touches serialization contracts and can break test fixtures or external packer assumptions if not coordinated.
- Moving `tile_bank.rs` to `glyph_bank.rs` may cause broad import churn across crates and tests.
- Historical lessons can easily drift into editorial inconsistency if the rewrite is done mechanically instead of semantically.