From a415c172c7d28ceb3581018f378068555db8d6c3 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:46:50 +0100 Subject: [PATCH] implements PLN-0168 variable glyph bank residency --- crates/console/prometeu-drivers/src/asset.rs | 4 +- .../prometeu-drivers/src/frame_composer.rs | 5 +- crates/console/prometeu-drivers/src/gfx.rs | 18 +++- .../console/prometeu-drivers/src/hardware.rs | 2 +- .../prometeu-drivers/src/memory_banks.rs | 2 +- .../prometeu-hal/src/cartridge_loader.rs | 22 ++--- crates/console/prometeu-hal/src/glyph_bank.rs | 88 +++++++++++++++++-- .../src/services/vm_runtime/tests.rs | 22 +++-- discussion/index.ndjson | 2 +- ...yphbank-variable-palette-resident-model.md | 3 +- 10 files changed, 129 insertions(+), 39 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index e846d8ec..493eccd5 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -1212,7 +1212,7 @@ impl AssetManager { &buffer[packed_pixel_bytes..packed_pixel_bytes + GLYPH_BANK_PALETTE_BYTES_V1]; let mut palettes = - [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]; + vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]; for (p, pal) in palettes.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() { let offset = (p * 16 + c) * 4; @@ -1247,7 +1247,7 @@ impl AssetManager { .map_err(|_| "Buffer too small for GLYPHBANK".to_string())?; let mut palettes = - [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]; + vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]; for (p, pal) in palettes.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() { let offset = (p * 16 + c) * 4; diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs index 80e3bd5a..11abbc6c 100644 --- a/crates/console/prometeu-drivers/src/frame_composer.rs +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -557,8 +557,9 @@ mod tests { fn make_glyph_bank(tile_size: TileSize, palette_id: u8, color: Color) -> GlyphBank { let size = tile_size as usize; - let mut bank = GlyphBank::new(tile_size, size, size); - bank.palettes[palette_id as usize][1] = color; + let mut bank = + GlyphBank::with_palette_count(tile_size, size, size, palette_id as usize + 1); + bank.palette_mut(palette_id).unwrap()[1] = color; for pixel in &mut bank.pixel_indices { *pixel = 1; } diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index 6b86cee6..bcd69f66 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -812,7 +812,10 @@ impl Gfx { let fetch_x = if tile.entry.flip_x() { size - 1 - local_x } else { local_x }; let fetch_y = if tile.entry.flip_y() { size - 1 - local_y } else { local_y }; let px_index = tile.bank.get_pixel_index(tile.entry.glyph_id, fetch_x, fetch_y); - let color = tile.bank.resolve_color(tile.entry.palette_id, px_index); + let color = tile + .bank + .resolve_color(tile.entry.palette_id, px_index) + .unwrap_or(Color::TRANSPARENT); if color.alpha() == 0 { continue; } @@ -862,7 +865,9 @@ impl Gfx { let fetch_y = if sprite.flip_y { size - 1 - local_y } else { local_y }; let px_index = bank.get_pixel_index(sprite.glyph.glyph_id, fetch_x, fetch_y); - let color = bank.resolve_color(sprite.glyph.palette_id, px_index); + let color = bank + .resolve_color(sprite.glyph.palette_id, px_index) + .unwrap_or(Color::TRANSPARENT); if color.alpha() == 0 { continue; } @@ -1010,9 +1015,14 @@ mod tests { fn make_glyph_bank(tile_size: TileSize, palette_colors: &[(u8, u8, Color)]) -> GlyphBank { let size = tile_size as usize; - let mut bank = GlyphBank::new(tile_size, size, size); + let palette_count = palette_colors + .iter() + .map(|(palette_id, _, _)| *palette_id as usize + 1) + .max() + .unwrap_or(1); + let mut bank = GlyphBank::with_palette_count(tile_size, size, size, palette_count); for (palette_id, color_index, color) in palette_colors { - bank.palettes[*palette_id as usize][*color_index as usize] = *color; + bank.palette_mut(*palette_id).unwrap()[*color_index as usize] = *color; } bank } diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index cd4d3cf9..dce0cd0b 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -261,7 +261,7 @@ mod tests { fn make_glyph_bank() -> GlyphBank { let mut bank = GlyphBank::new(TileSize::Size8, 8, 8); - bank.palettes[0][1] = Color::RED; + bank.palette_mut(0).unwrap()[1] = Color::RED; for pixel in &mut bank.pixel_indices { *pixel = 1; } diff --git a/crates/console/prometeu-drivers/src/memory_banks.rs b/crates/console/prometeu-drivers/src/memory_banks.rs index 1eef36e6..aa694f71 100644 --- a/crates/console/prometeu-drivers/src/memory_banks.rs +++ b/crates/console/prometeu-drivers/src/memory_banks.rs @@ -196,7 +196,7 @@ mod tests { fn make_glyph_bank() -> GlyphBank { let mut bank = GlyphBank::new(TileSize::Size8, 8, 8); - bank.palettes[0][1] = Color::WHITE; + bank.palette_mut(0).unwrap()[1] = Color::WHITE; bank } diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index 2fe4f836..302da027 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -203,7 +203,7 @@ mod tests { use super::*; use crate::asset::{AssetCodec, AssetEntry, BankType, PreloadEntry}; use crate::cartridge::{ASSETS_PA_MAGIC, ASSETS_PA_SCHEMA_VERSION, AssetsPackPrelude}; - use crate::glyph_bank::GLYPH_BANK_PALETTE_COUNT_V1; + use crate::glyph_bank::GLYPH_BANK_MAX_PALETTE_COUNT_V1; use serde_json::json; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -369,14 +369,14 @@ mod tests { bank_type: BankType::GLYPH, offset, size, - decoded_size: 16 * 16 + (GLYPH_BANK_PALETTE_COUNT_V1 as u64 * 16 * 4), + decoded_size: 16 * 16 + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 as u64 * 16 * 4), codec: AssetCodec::None, metadata: json!({ "tile_size": 16, "width": 16, "height": 16, - "palette_count": GLYPH_BANK_PALETTE_COUNT_V1, - "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1 + "palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1, + "palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1 }), } } @@ -452,14 +452,14 @@ mod tests { bank_type: BankType::GLYPH, offset: 4, size: 4, - decoded_size: 16 * 16 + (GLYPH_BANK_PALETTE_COUNT_V1 as u64 * 16 * 4), + decoded_size: 16 * 16 + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 as u64 * 16 * 4), codec: AssetCodec::None, metadata: json!({ "tile_size": 16, "width": 16, "height": 16, - "palette_count": GLYPH_BANK_PALETTE_COUNT_V1, - "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1 + "palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1, + "palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1 }), }, ]; @@ -519,8 +519,8 @@ mod tests { "tile_size": 16, "width": 16, "height": 16, - "palette_count": GLYPH_BANK_PALETTE_COUNT_V1, - "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1 + "palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1, + "palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1 } }], "preload": [] @@ -561,8 +561,8 @@ mod tests { "tile_size": 16, "width": 16, "height": 16, - "palette_count": GLYPH_BANK_PALETTE_COUNT_V1, - "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1 + "palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1, + "palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1 } }], "preload": [] diff --git a/crates/console/prometeu-hal/src/glyph_bank.rs b/crates/console/prometeu-hal/src/glyph_bank.rs index fe92e131..2c72d8ed 100644 --- a/crates/console/prometeu-hal/src/glyph_bank.rs +++ b/crates/console/prometeu-hal/src/glyph_bank.rs @@ -1,7 +1,7 @@ use crate::color::Color; use serde::{Deserialize, Serialize}; -pub const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64; +pub const GLYPH_BANK_MAX_PALETTE_COUNT_V1: usize = 64; pub const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16; /// Standard sizes for square tiles. @@ -35,22 +35,59 @@ pub struct GlyphBank { /// Palette indices are ordinary indices; transparency is resolved through /// the RGBA alpha channel of the palette entry. pub pixel_indices: Vec, - /// Runtime-facing v1 palette table: 64 palettes of 16 RGBA8888 colors each. - pub palettes: [[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1], + /// Runtime-facing v1 palette table. + /// + /// Palette identity is the direct index in this vector. A resident glyph + /// bank carries exactly the palettes materialized from its payload. + pub palettes: Vec<[Color; GLYPH_BANK_COLORS_PER_PALETTE]>, } impl GlyphBank { - /// Creates an empty glyph bank with the specified dimensions. + /// Creates an empty glyph bank with one palette. pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self { + Self::with_palette_count(tile_size, width, height, 1) + } + + /// Creates an empty glyph bank with the specified resident palette count. + pub fn with_palette_count( + tile_size: TileSize, + width: usize, + height: usize, + palette_count: usize, + ) -> Self { + assert!( + (1..=GLYPH_BANK_MAX_PALETTE_COUNT_V1).contains(&palette_count), + "glyph bank palette_count must be in 1..={}", + GLYPH_BANK_MAX_PALETTE_COUNT_V1 + ); + Self { tile_size, width, height, pixel_indices: vec![0; width * height], - palettes: [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1], + palettes: vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count], } } + /// Returns the number of resident palettes. + pub fn palette_count(&self) -> usize { + self.palettes.len() + } + + /// Returns true when the palette id is valid for this resident bank. + pub fn contains_palette(&self, palette_id: u8) -> bool { + (palette_id as usize) < self.palette_count() + } + + /// Returns a mutable palette slot when the palette exists. + pub fn palette_mut( + &mut self, + palette_id: u8, + ) -> Option<&mut [Color; GLYPH_BANK_COLORS_PER_PALETTE]> { + self.palettes.get_mut(palette_id as usize) + } + /// Resolves a global tile ID and local pixel coordinates to a palette index. /// tile_id: the tile index in the bank /// local_x, local_y: the pixel position inside the tile (0 to tile_size-1) @@ -71,11 +108,48 @@ impl GlyphBank { } /// Maps a 4-bit index to a real RGBA8888 Color using the specified palette. - pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color { + pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Option { self.palettes .get(palette_id as usize) .and_then(|palette| palette.get(pixel_index as usize)) .copied() - .unwrap_or(Color::TRANSPARENT) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_glyph_bank_uses_single_resident_palette() { + let bank = GlyphBank::new(TileSize::Size8, 8, 8); + + assert_eq!(bank.palette_count(), 1); + assert!(bank.contains_palette(0)); + assert!(!bank.contains_palette(1)); + } + + #[test] + fn glyph_bank_can_materialize_v1_max_palette_count() { + let bank = + GlyphBank::with_palette_count(TileSize::Size8, 8, 8, GLYPH_BANK_MAX_PALETTE_COUNT_V1); + + assert_eq!(bank.palette_count(), GLYPH_BANK_MAX_PALETTE_COUNT_V1); + assert!(bank.contains_palette((GLYPH_BANK_MAX_PALETTE_COUNT_V1 - 1) as u8)); + } + + #[test] + fn invalid_palette_lookup_is_distinct_from_transparent_color() { + let mut bank = GlyphBank::new(TileSize::Size8, 8, 8); + bank.palette_mut(0).unwrap()[1] = Color::TRANSPARENT; + + assert_eq!(bank.resolve_color(0, 1), Some(Color::TRANSPARENT)); + assert_eq!(bank.resolve_color(1, 1), None); + } + + #[test] + #[should_panic(expected = "glyph bank palette_count must be in 1..=64")] + fn glyph_bank_rejects_zero_resident_palettes() { + let _ = GlyphBank::with_palette_count(TileSize::Size8, 8, 8, 0); } } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index a10b47c1..7fedd15a 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -15,7 +15,7 @@ use prometeu_hal::asset::{ use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge}; use prometeu_hal::color::Color; use prometeu_hal::glyph::Glyph; -use prometeu_hal::glyph_bank::{GLYPH_BANK_PALETTE_COUNT_V1, GlyphBank, TileSize}; +use prometeu_hal::glyph_bank::{GLYPH_BANK_MAX_PALETTE_COUNT_V1, GlyphBank, TileSize}; use prometeu_hal::scene_bank::SceneBank; use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::syscalls::caps; @@ -113,11 +113,12 @@ fn serialized_single_function_module_with_consts( } fn test_glyph_payload_size(width: usize, height: usize) -> usize { - (width * height).div_ceil(2) + (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::()) + (width * height).div_ceil(2) + + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * std::mem::size_of::()) } fn test_glyph_decoded_size(width: usize, height: usize) -> usize { - width * height + (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * std::mem::size_of::()) + width * height + (GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * std::mem::size_of::()) } fn test_glyph_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry { @@ -133,23 +134,26 @@ fn test_glyph_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry { "tile_size": 16, "width": 16, "height": 16, - "palette_count": GLYPH_BANK_PALETTE_COUNT_V1, - "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1 + "palette_count": GLYPH_BANK_MAX_PALETTE_COUNT_V1, + "palette_authored": GLYPH_BANK_MAX_PALETTE_COUNT_V1 }), } } fn test_glyph_asset_data() -> Vec { let mut data = - vec![0x11u8; test_glyph_payload_size(16, 16) - (GLYPH_BANK_PALETTE_COUNT_V1 * 16 * 4)]; - data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_COUNT_V1 * 16 * 4]); + vec![ + 0x11u8; + test_glyph_payload_size(16, 16) - (GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * 4) + ]; + data.extend_from_slice(&[0u8; GLYPH_BANK_MAX_PALETTE_COUNT_V1 * 16 * 4]); data } fn runtime_test_glyph_bank(tile_size: TileSize, palette_id: u8, color: Color) -> GlyphBank { let size = tile_size as usize; - let mut bank = GlyphBank::new(tile_size, size, size); - bank.palettes[palette_id as usize][1] = color; + let mut bank = GlyphBank::with_palette_count(tile_size, size, size, palette_id as usize + 1); + bank.palette_mut(palette_id).unwrap()[1] = color; for pixel in &mut bank.pixel_indices { *pixel = 1; } diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 3c90ca1c..5a6c9ef3 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -44,4 +44,4 @@ {"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0036","status":"done","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-22","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0045","file":"discussion/lessons/DSC-0036-prometeu-hub-ui-direction/LSN-0045-hub-ui-slices-should-prove-os-boundaries.md","status":"done","created_at":"2026-05-22","updated_at":"2026-05-22"}]} {"type":"discussion","id":"DSC-0037","status":"done","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0046","file":"discussion/lessons/DSC-0037-rgba8888-framebuffer-and-pixel-format-direction/LSN-0046-pixel-format-contracts-must-move-as-one-surface.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23"}]} -{"type":"discussion","id":"DSC-0046","status":"in_progress","ticket":"runtime-owned-variable-glyph-bank-palette-protocol","title":"Runtime-Owned Variable Glyph Bank Palette Protocol","created_at":"2026-07-14","updated_at":"2026-07-14","tags":["runtime","gfx","assets","glyph-bank","palette-serialization","protocol"],"agendas":[{"id":"AGD-0049","file":"AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14"}],"decisions":[{"id":"DEC-0041","file":"DEC-0041-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14","ref_agenda":"AGD-0049"}],"plans":[{"id":"PLN-0167","file":"PLN-0167-spec-contract-update-for-variable-glyph-palettes.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0168","file":"PLN-0168-glyphbank-variable-palette-resident-model.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0169","file":"PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0170","file":"PLN-0170-composer-palette-reference-failure-semantics.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0171","file":"PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0172","file":"PLN-0172-runtime-spec-handoff-to-packer-and-studio.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0046","status":"in_progress","ticket":"runtime-owned-variable-glyph-bank-palette-protocol","title":"Runtime-Owned Variable Glyph Bank Palette Protocol","created_at":"2026-07-14","updated_at":"2026-07-14","tags":["runtime","gfx","assets","glyph-bank","palette-serialization","protocol"],"agendas":[{"id":"AGD-0049","file":"AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14"}],"decisions":[{"id":"DEC-0041","file":"DEC-0041-variable-glyph-bank-palette-protocol.md","status":"accepted","created_at":"2026-07-14","updated_at":"2026-07-14","ref_agenda":"AGD-0049"}],"plans":[{"id":"PLN-0167","file":"PLN-0167-spec-contract-update-for-variable-glyph-palettes.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0168","file":"PLN-0168-glyphbank-variable-palette-resident-model.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0169","file":"PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0170","file":"PLN-0170-composer-palette-reference-failure-semantics.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0171","file":"PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]},{"id":"PLN-0172","file":"PLN-0172-runtime-spec-handoff-to-packer-and-studio.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md b/discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md index 2e3bc9d8..198d7f0c 100644 --- a/discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md +++ b/discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md @@ -2,7 +2,8 @@ id: PLN-0168 ticket: runtime-owned-variable-glyph-bank-palette-protocol title: GlyphBank Variable Palette Resident Model -status: open +status: done +completed: 2026-07-14 created: 2026-07-14 ref_decisions: [DEC-0041] tags: [runtime, gfx, assets, glyph-bank, hal]