From dc279fe72be7477afd1fbc4f0a208930518fac2a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:40:49 +0100 Subject: [PATCH 1/9] Runtime-Owned Variable Glyph Bank Palette Protocol --- discussion/.index.lock | 0 discussion/index.ndjson | 3 +- ...ed-variable-glyph-bank-palette-protocol.md | 220 ++++++++++++++++++ ...41-variable-glyph-bank-palette-protocol.md | 202 ++++++++++++++++ ...ract-update-for-variable-glyph-palettes.md | 115 +++++++++ ...yphbank-variable-palette-resident-model.md | 118 ++++++++++ ...-validation-for-variable-glyph-palettes.md | 112 +++++++++ ...ser-palette-reference-failure-semantics.md | 125 ++++++++++ ...palette-tests-fixtures-and-residue-scan.md | 114 +++++++++ ...ntime-spec-handoff-to-packer-and-studio.md | 112 +++++++++ 10 files changed, 1120 insertions(+), 1 deletion(-) create mode 100644 discussion/.index.lock create mode 100644 discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md create mode 100644 discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md create mode 100644 discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md create mode 100644 discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md create mode 100644 discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md create mode 100644 discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md create mode 100644 discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md create mode 100644 discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md diff --git a/discussion/.index.lock b/discussion/.index.lock new file mode 100644 index 00000000..e69de29b diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 9da61ed1..7d1cafa1 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,4 @@ -{"type":"meta","next_id":{"DSC":46,"AGD":49,"DEC":41,"PLN":167,"LSN":55,"CLSN":1}} +{"type":"meta","next_id":{"DSC":47,"AGD":50,"DEC":42,"PLN":173,"LSN":55,"CLSN":1}} {"type":"discussion","id":"DSC-0044","status":"done","ticket":"hub-suspended-game-kill-affordance","title":"Hub Suspended Game Kill Affordance","created_at":"2026-07-05","updated_at":"2026-07-05","tags":["hub","lifecycle","game","ui"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0054","file":"discussion/lessons/DSC-0044-hub-suspended-game-kill-affordance/LSN-0054-manual-hub-kill-must-share-game-termination-cleanup.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]} {"type":"discussion","id":"DSC-0043","status":"done","ticket":"system-os-cartridge-switch-orchestrator","title":"SystemOS Cartridge Switch Orchestrator","created_at":"2026-07-03","updated_at":"2026-07-05","tags":["runtime","os","lifecycle","game","cartridge","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0043-system-os-cartridge-switch-orchestrator/LSN-0053-game-switching-is-lifecycle-replacement-not-loader-work.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]} {"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."} @@ -44,3 +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":"open","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":[]} diff --git a/discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md b/discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md new file mode 100644 index 00000000..3b87a324 --- /dev/null +++ b/discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md @@ -0,0 +1,220 @@ +--- +id: AGD-0049 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Runtime-Owned Variable Glyph Bank Palette Protocol +status: accepted +created: 2026-07-14 +resolved: 2026-07-14 +decision: +tags: + - runtime + - gfx + - assets + - glyph-bank + - palette-serialization + - protocol +--- + +## Contexto + +A agenda `../studio/discussion/workflow/agendas/AGD-0005-variable-tile-bank-palette-serialization.md` +levantou o desperdicio do payload atual de `GLYPH/indexed_v1`: todo glyph bank +carrega `64 * 16 * 4 = 4096` bytes de paletas RGBA8888, mesmo quando o asset +usa poucas paletas. + +Esse problema toca o `packer`, mas a decisao do protocolo pertence ao +`runtime`. A especificacao final deve ser definida aqui, no contrato runtime, +e o `packer` deve seguir a spec quando ela estiver pronta. A agenda de `studio` +serve como entrada factual e motivacao, nao como autoridade normativa sobre o +payload, a memoria residente ou a semantica de `palette_id`. + +Estado atual no runtime: + +- `docs/specs/runtime/15-asset-management.md` documenta `palette_count = 64`; +- `docs/specs/runtime/04-gfx-peripheral.md` documenta `palette_id` em `0..63`; +- `crates/console/prometeu-hal/src/glyph_bank.rs` materializa paletas como + `[[Color; 16]; 64]`; +- `crates/console/prometeu-drivers/src/asset.rs` rejeita metadata cujo + `palette_count` nao seja `64`; +- o decode de glyph bank le um bloco fixo de `64 * 16 * 4` bytes em + `assets.pa`; +- testes de asset e VM ainda calculam payload e decoded size com o bloco fixo. + +Decisoes anteriores relevantes: + +- `DSC-0022` estabeleceu `GlyphBank` como nome canonico do artefato grafico; +- `DSC-0037` estabeleceu RGBA8888 como contrato runtime, com alpha como dado de + cor e sem indice magico reservado. + +## Problema + +O runtime precisa decidir se `GLYPH/indexed_v1` continua sendo um contrato de +64 paletas fixas ou se passa a aceitar uma quantidade variavel de paletas +serializadas. + +Essa decisao nao e apenas uma otimizacao de tamanho de cartucho. Ela muda: + +- o significado de `palette_count`; +- a validacao de `palette_id`; +- a formula de `size` e `decoded_size`; +- a representacao residente de `GlyphBank`; +- o comportamento quando uma cena referencia uma paleta nao carregada; +- a fronteira entre contrato de asset, HAL, composer, testes e tooling. + +## Pontos Criticos + +- Autoridade: o runtime decide o protocolo publicado; o `packer` segue. +- Compatibilidade: o projeto ainda esta em v1, entao nao ha obrigacao + presumida de preservar o padding fixo antigo. +- Identidade: `palette_id` pode continuar sendo uma identidade direta ou pode + virar indice em uma tabela compactada, mas nao pode ter dois significados. +- Materializacao: economizar bytes no payload nao implica automaticamente + economizar memoria residente, mas manter uma tabela fixa pode preservar + semantica simples para renderizacao e cenas. +- Falhas: se `palette_id` apontar para uma paleta ausente, o runtime precisa + definir se isso falha no load, na composicao, ou se resolve para cor default. +- Propagacao: qualquer decisao precisa mover specs, decode, HAL, fixtures e + testes juntos, como ocorreu na migracao RGBA8888. + +## Opcoes + +### Opcao A - Manter 64 paletas fixas no protocolo runtime + +- **Abordagem:** Preservar `palette_count = 64`, bloco fixo de 4096 bytes e + tabela residente fixa. +- **Pro:** Mantem decode, composicao e limites de `palette_id` simples. +- **Contra:** O runtime continua publicando um custo obrigatorio de payload que + nao representa a maioria dos assets. +- **Manutencao:** Boa para estabilidade local, fraca para alinhamento entre + metadata autoral e payload real. + +### Opcao B - Payload variavel, memoria residente fixa de 64 slots + +- **Abordagem:** Fazer `palette_count` representar o numero de paletas + serializadas, mas expandir para uma tabela residente fixa de 64 paletas no + load. Paletas nao serializadas ficam em valor default, e `palette_id` continua + sendo identidade direta `0..63`. +- **Pro:** Reduz payload mantendo o contrato de composicao e cena simples. +- **Contra:** Requer decidir se referencias a paletas nao serializadas sao + invalidas no load, invalidas na composicao ou simplesmente resolvem para a + paleta default. +- **Manutencao:** Boa se a spec declarar que `palette_count` mede somente o + prefixo serializado e que o runtime ainda materializa capacidade fixa. + +### Opcao C - Payload variavel e memoria residente variavel + +- **Abordagem:** Fazer `palette_count` controlar tanto o payload quanto a + quantidade residente de paletas. `palette_id >= palette_count` passa a ser + invalido para aquele glyph bank. +- **Pro:** O contrato fica mais fiel ao asset carregado e reduz memoria + residente por banco. +- **Contra:** A composicao precisa carregar limite por banco e transformar + validacao de `palette_id` em regra dependente do asset. +- **Manutencao:** Forte se o runtime quiser que o banco carregado seja a unica + fonte de verdade; mais invasiva no HAL/composer. + +### Opcao D - Paletas compactadas com remapeamento de identidade + +- **Abordagem:** Serializar somente paletas usadas em uma tabela densa e + introduzir um mapa entre identidade autoral/esparsa e indice residente. +- **Pro:** Pode minimizar payload mesmo quando indices autorais sao esparsos. +- **Contra:** Introduz um segundo contrato de identidade e exige metadata extra + ou reescrita de cenas/assets dependentes. +- **Manutencao:** Fraca para v1 unless haja uma necessidade clara de preservar + indices esparsos sem preencher lacunas. + +## Sugestao / Recomendacao + +Adotar a **Opcao C**. + +`palette_count` deve ser o contrato real do glyph bank carregado: se o payload +serializa `N` paletas, o runtime materializa `N` paletas e `palette_id >= N` +e invalido para aquele banco. + +A **Opcao B** fica descartada porque manter memoria residente fixa preservaria +uma segunda nocao de capacidade que nao corresponde ao asset carregado. A +**Opcao D** tambem fica descartada porque introduzir remapeamento de identidade +transformaria um protocolo simples em uma camada adicional de metadata. + +## Perguntas em Aberto + +- [x] `palette_count` deve definir tambem a quantidade residente de paletas, ou + apenas o prefixo serializado no payload? + - Resolucao: define tambem a quantidade residente. +- [x] `palette_id` deve ser validado contra `palette_count` por glyph bank ou + continuar limitado globalmente a `0..63`? + - Resolucao: validar contra `palette_count` por glyph bank, mantendo um + limite maximo global separado. +- [x] Quando uma cena referencia uma paleta ausente, o erro deve ocorrer no + load do glyph bank, no load/decode da scene, ou na composicao? + - Resolucao: o glyph bank carrega se o proprio payload e valido. A referencia + ausente falha quando uma scene/sprite tenta usar `palette_id >= + palette_count` contra o banco real; o runtime deve tratar isso como erro + explicito, nao como fallback silencioso para transparente/default. +- [x] O limite maximo de paletas por glyph bank continua sendo `64` em v1? + - Resolucao: sim. A variabilidade deve reduzir `N`, nao abrir uma + quantidade ilimitada de paletas em v1. +- [x] `decoded_size` deve contar apenas paletas materializadas ou manter alguma + nocao de capacidade residente? + - Resolucao: contar apenas paletas materializadas: + `width * height + palette_count * 16 * 4`. +- [x] A mudanca deve manter o nome `GLYPH/indexed_v1` como correcao + incompativel de v1, ou o runtime exige uma nova versao de payload? + - Resolucao: manter `GLYPH/indexed_v1` como correcao incompativel, sem + compatibilidade com o padding fixo antigo. + +## Criterio para Encerrar + +A agenda pode virar decisao quando o runtime escolher: + +- o significado normativo de `palette_count`; +- a relacao entre payload serializado e memoria residente; +- a regra de validade de `palette_id`; +- o ponto de falha para paletas referenciadas mas ausentes; +- a estrategia de versao para `GLYPH/indexed_v1`; +- a lista de specs, crates e testes que precisam ser propagados no plano. + +## Discussion + +Entrada do usuario em 2026-07-14: o runtime deve decidir o protocolo com base +no que for melhor para o runtime; o `packer` deve seguir a spec quando pronta. + +Entrada do usuario em 2026-07-14: preferencia pela Opcao C, se possivel. + +Analise atual: a Opcao C e viavel, mas transforma `palette_id` em validacao +dependente do glyph bank carregado. O codigo atual ja centraliza parte desse +risco em `GlyphBank::resolve_color`, que hoje retorna transparente quando a +paleta nao existe. Para a Opcao C virar contrato robusto, a decisao nao deve +depender de fallback silencioso: precisa escolher um ponto explicito de falha +para `palette_id >= palette_count`. + +Entrada do usuario em 2026-07-14: aceitar a recomendacao e seguir com a Opcao +C. + +## Resolution + +Fechar a agenda em favor da **Opcao C - Payload variavel e memoria residente +variavel**. + +Contrato a levar para decisao: + +- o runtime e a autoridade do protocolo `GLYPH/indexed_v1`; +- o `packer` deve seguir a spec runtime quando ela estiver publicada; +- `palette_count` significa o numero de paletas RGBA8888 serializadas e + materializadas no glyph bank carregado; +- `palette_count` deve estar no intervalo `1..=64`; +- cada paleta continua tendo 16 cores RGBA8888; +- `palette_id` e uma identidade direta dentro do glyph bank carregado, valida + somente quando `palette_id < palette_count`; +- o runtime nao deve remapear paletas esparsas para outra identidade; +- `decoded_size` deve contar apenas as paletas materializadas: + `width * height + palette_count * 16 * 4`; +- a mudanca permanece em `GLYPH/indexed_v1` como correcao incompativel de v1; +- payloads antigos com padding fixo e `palette_count = 64` so continuam + validos se tambem satisfizerem o novo contrato por coincidencia, nao por uma + regra de compatibilidade separada; +- referencias a paletas ausentes devem falhar explicitamente quando + scene/sprite/composer tentarem usar `palette_id >= palette_count` contra o + banco real; +- o plano posterior deve propagar specs, `GlyphBank`, decode de assets, + validacao de scene/sprite/composer, fixtures e testes. diff --git a/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md b/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md new file mode 100644 index 00000000..53e06b57 --- /dev/null +++ b/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md @@ -0,0 +1,202 @@ +--- +id: DEC-0041 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Variable Glyph Bank Palette Protocol +status: accepted +created: 2026-07-14 +accepted: 2026-07-14 +ref_agenda: AGD-0049 +tags: [runtime, gfx, assets, glyph-bank, palette-serialization, protocol] +--- + +## Status + +Accepted. + +## Contexto + +`AGD-0049` resolved that the runtime must own the `GLYPH/indexed_v1` palette +serialization protocol. `studio` and `packer` may motivate the change, but +they do not define the runtime wire contract. Once this decision is accepted, +the packer must follow the published runtime spec. + +The current runtime still contains fixed-palette assumptions: + +- `docs/specs/runtime/15-asset-management.md` says `palette_count = 64` and + validates `palette_count` as exactly `64`; +- `docs/specs/runtime/04-gfx-peripheral.md` documents `palette_id` as a + runtime-facing palette index; +- `crates/console/prometeu-hal/src/glyph_bank.rs` stores palettes as + `[[Color; 16]; 64]`; +- `crates/console/prometeu-drivers/src/asset.rs` rejects any glyph metadata + whose `palette_count` is not `64`; +- glyph decode reads a fixed `64 * 16 * 4` byte palette block; +- tests and fixtures calculate glyph payload and decoded size with the fixed + palette block. + +Prior decisions remain in force: + +- `DSC-0022` established `GlyphBank` as the canonical artifact name; +- `DSC-0037` established RGBA8888 as the runtime color contract, with alpha as + color data and no reserved magic palette index. + +## Decisao + +The runtime SHALL adopt variable glyph-bank palette serialization for +`GLYPH/indexed_v1`. + +For `BankType::GLYPH` in v1: + +- `palette_count` MUST mean the number of RGBA8888 palettes serialized in the + payload and materialized in the resident `GlyphBank`; +- `palette_count` MUST be in the inclusive range `1..=64`; +- each palette MUST contain exactly `16` RGBA8888 colors in canonical `R, G, B, + A` byte order; +- the serialized payload MUST contain only `palette_count` palettes, not a + fixed 64-palette block; +- resident runtime memory MUST materialize exactly `palette_count` palettes for + the bank; +- `palette_id` MUST be interpreted as a direct palette identity within the + loaded glyph bank; +- `palette_id` MUST be valid only when `palette_id < palette_count` for the + loaded glyph bank being referenced; +- the runtime MUST NOT remap sparse authored palette identities into a separate + dense identity space; +- missing palettes MUST NOT silently resolve to transparent, black, or any + other default color in canonical scene/sprite composition; +- references to `palette_id >= palette_count` MUST fail explicitly when + scene/sprite/composer logic attempts to use that palette against the loaded + bank; +- the format name remains `GLYPH/indexed_v1`; this is an incompatible v1 + correction, not a new v2 format. + +The v1 payload size formulas SHALL be: + +```text +serialized_pixel_bytes = ceil(width * height / 2) +palette_bytes = palette_count * 16 * 4 +size = serialized_pixel_bytes + palette_bytes +decoded_size = (width * height) + palette_bytes +``` + +`width * height` is the number of logical indexed pixels. Serialized pixels +remain packed `u4`; decoded runtime pixels may remain expanded to one `u8` +index per pixel. + +## Rationale + +This makes `palette_count` a real runtime contract instead of a decorative +metadata field. If a glyph bank carries `N` palettes, runtime storage, payload +validation, `decoded_size`, and palette lookup all agree on `N`. + +Keeping a fixed 64-slot resident table while trimming only the payload would +preserve two meanings for palette capacity: serialized count and resident +capacity. That would keep the ambiguity this decision is intended to remove. + +Adding sparse-to-dense palette remapping is also rejected. It would introduce a +second identity layer and force scenes, sprites, packer output, and runtime +lookup to coordinate additional metadata. V1 should keep `palette_id` as a +direct identity inside the loaded glyph bank. + +Keeping the name `GLYPH/indexed_v1` is acceptable because the project is still +in v1 and there is no required compatibility owner for the old fixed-padding +payload. The runtime should correct the v1 contract now instead of publishing a +second format solely to remove padding. + +## Invariantes / Contrato + +- The runtime is the protocol authority for `GLYPH/indexed_v1`. +- The packer is a producer of runtime-conforming payloads, not the source of + truth for the protocol. +- RGBA8888 remains the only valid glyph palette color encoding. +- Palette index `0` remains an ordinary palette index. +- Color index `0` remains an ordinary color index inside a palette. +- Transparency is represented by the RGBA alpha channel. +- `palette_count` is both serialized palette count and resident palette count. +- `palette_count` has a maximum of `64` in v1. +- `palette_id` validity is bank-dependent: `palette_id < palette_count`. +- No compatibility mode for the old fixed 64-palette padding is introduced. +- Old payloads with `palette_count = 64` remain valid only if they satisfy the + new contract directly, not because of a special legacy branch. + +## Impactos + +### Specs + +- `docs/specs/runtime/15-asset-management.md` must remove the exact + `palette_count = 64` requirement and define `palette_count` as `1..=64`. +- The same spec must align `size` and `decoded_size` formulas with variable + `palette_count`. +- `docs/specs/runtime/04-gfx-peripheral.md` must define `palette_id` validity + as dependent on the loaded glyph bank's `palette_count`, not as a standalone + global `0..63` acceptance rule. +- Any public wording that suggests fixed resident `64` palettes per glyph bank + must be updated or marked historical. + +### Runtime Code + +- `GlyphBank` must stop exposing a fixed `[[Color; 16]; 64]` resident palette + table as the canonical representation. +- Glyph decode must accept `palette_count` in `1..=64`. +- Glyph decode must read exactly `palette_count * 16 * 4` palette bytes. +- Glyph decode must validate `size` and `decoded_size` using the variable + formulas. +- Palette lookup must expose enough information for scene/sprite/composer logic + to fail invalid `palette_id` explicitly instead of silently resolving a + default color. + +### Scene, Sprite, and Composer + +- Scene and sprite composition must treat `palette_id >= palette_count` for the + referenced glyph bank as an explicit invalid reference. +- The implementation plan must choose the concrete status/fault path for this + invalid reference using existing runtime error semantics where possible. +- Canonical composition must not continue by substituting transparent/default + colors for invalid palette references. + +### Firmware / Host / Tooling + +- Firmware/system surfaces that expose asset metadata or debug information must + report the variable `palette_count`. +- Packer/studio fixtures must emit runtime-conforming `GLYPH/indexed_v1` + payloads after the runtime spec is updated. +- Tooling must not generate sparse-to-dense remapping metadata for v1 unless a + later decision introduces that feature. + +### Tests + +- Tests must cover minimum and maximum valid palette counts: `1` and `64`. +- Tests must reject `palette_count = 0` and `palette_count > 64`. +- Tests must validate serialized and decoded size formulas for non-64 counts. +- Tests must verify palette bytes are read in RGBA order for variable counts. +- Tests must cover invalid `palette_id >= palette_count` behavior for scene or + sprite composition. +- Residue scans must check for fixed `64 * 16 * 4` payload assumptions that are + still active contract text or code. + +## Referencias + +- Agenda: `AGD-0049` +- Runtime naming precedent: `DSC-0022` +- RGBA8888 contract precedent: `DSC-0037` +- Spec target: `docs/specs/runtime/15-asset-management.md` +- Spec target: `docs/specs/runtime/04-gfx-peripheral.md` +- Code target: `crates/console/prometeu-hal/src/glyph_bank.rs` +- Code target: `crates/console/prometeu-drivers/src/asset.rs` + +## Propagacao Necessaria + +This decision must be followed by an executable plan before spec or code +changes. + +The plan must separate: + +- spec edits; +- runtime decode/materialization changes; +- scene/sprite/composer invalid-palette handling; +- tests and fixtures; +- downstream packer/studio alignment after the runtime spec is updated. + +## Revision Log + +- 2026-07-14: Initial decision draft from `AGD-0049`. diff --git a/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md b/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md new file mode 100644 index 00000000..36126eeb --- /dev/null +++ b/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md @@ -0,0 +1,115 @@ +--- +id: PLN-0167 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Spec Contract Update for Variable Glyph Palettes +status: open +created: 2026-07-14 +ref_decisions: [DEC-0041] +tags: [runtime, gfx, assets, glyph-bank, palette-serialization, protocol, specs] +--- + +## Briefing + +`DEC-0041` accepts variable glyph-bank palette serialization for +`GLYPH/indexed_v1`. The runtime specs must become the canonical source before +code and tooling are changed. + +## Decisions de Origem + +- `DEC-0041` - Variable Glyph Bank Palette Protocol + +## Alvo + +Publish the runtime contract for variable glyph-bank palettes in the canonical +runtime specs. + +## Escopo + +- Update `docs/specs/runtime/15-asset-management.md`. +- Update `docs/specs/runtime/04-gfx-peripheral.md`. +- Remove or reword active spec text that requires exactly 64 serialized or + resident palettes per glyph bank. +- Define `palette_count` as serialized and resident palette count. +- Define `palette_count` as `1..=64`. +- Define `palette_id` validity as `palette_id < palette_count` for the loaded + glyph bank. +- Preserve RGBA8888, alpha-as-data, and ordinary index `0` semantics. + +## Fora de Escopo + +- Runtime code changes. +- Packer or studio code changes. +- New `GLYPH/indexed_v2` format text. +- Sparse-to-dense palette remapping. + +## Plano de Execucao + +### Step 1 - Update asset metadata contract + +**What:** Change the `GLYPH` v1 metadata contract. + +**How:** In `docs/specs/runtime/15-asset-management.md`, replace the exact +`palette_count = 64` requirement with `palette_count` in `1..=64`. State that +the field is both serialized palette count and resident palette count. + +**Files:** `docs/specs/runtime/15-asset-management.md` + +### Step 2 - Update payload and decoded-size formulas + +**What:** Make payload size formulas variable. + +**How:** Define `palette_bytes = palette_count * 16 * 4`, then define +`size = ceil(width * height / 2) + palette_bytes` and +`decoded_size = width * height + palette_bytes`. + +**Files:** `docs/specs/runtime/15-asset-management.md` + +### Step 3 - Update GFX palette reference semantics + +**What:** Document bank-dependent `palette_id` validity. + +**How:** In `docs/specs/runtime/04-gfx-peripheral.md`, make scene and sprite +composition text state that `palette_id` is valid only when it is lower than +the referenced glyph bank's `palette_count`. + +**Files:** `docs/specs/runtime/04-gfx-peripheral.md` + +### Step 4 - Document explicit failure + +**What:** Remove fallback ambiguity for missing palettes. + +**How:** State that canonical composition must not substitute transparent, +black, or default colors for `palette_id >= palette_count`. + +**Files:** `docs/specs/runtime/04-gfx-peripheral.md`, +`docs/specs/runtime/15-asset-management.md` + +### Step 5 - Run spec residue scan + +**What:** Verify no active spec contradicts `DEC-0041`. + +**How:** Search specs for fixed palette phrases and validate remaining hits are +historical or explicitly bounded maximums. + +**Files:** `docs/specs/runtime/*.md` + +## Criterios de Aceite + +- [ ] `15-asset-management.md` defines `palette_count` as `1..=64`. +- [ ] `15-asset-management.md` uses variable size formulas. +- [ ] `04-gfx-peripheral.md` defines bank-dependent `palette_id` validity. +- [ ] Specs do not describe fixed 64 serialized palettes as the active + contract. +- [ ] No v2 format is introduced. + +## Tests / Validacao + +- Run `rg -n "palette_count|64 \\* 16|4096|palette_id" docs/specs/runtime`. +- Run `discussion validate`. + +## Riscos + +- The specs already contain mixed fixed and variable wording; partial edits may + preserve contradiction. +- `palette_id` failure semantics may overlap with existing scene dependency + fatal-failure text and must be worded consistently. 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 new file mode 100644 index 00000000..2e3bc9d8 --- /dev/null +++ b/discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md @@ -0,0 +1,118 @@ +--- +id: PLN-0168 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: GlyphBank Variable Palette Resident Model +status: open +created: 2026-07-14 +ref_decisions: [DEC-0041] +tags: [runtime, gfx, assets, glyph-bank, hal] +--- + +## Briefing + +`DEC-0041` requires resident `GlyphBank` memory to materialize exactly +`palette_count` palettes. The current HAL model exposes a fixed +`[[Color; 16]; 64]` table and must be changed before decode and composition can +enforce bank-dependent palette validity cleanly. + +## Decisions de Origem + +- `DEC-0041` - Variable Glyph Bank Palette Protocol + +## Alvo + +Replace the fixed resident palette table with a variable resident palette model +that preserves direct `palette_id` identity. + +## Escopo + +- Update `crates/console/prometeu-hal/src/glyph_bank.rs`. +- Keep `GLYPH_BANK_COLORS_PER_PALETTE = 16`. +- Replace the fixed palette table with a variable representation. +- Expose palette count and palette validation helpers. +- Ensure `resolve_color` no longer hides invalid palette references in + canonical paths. +- Update local HAL tests and call sites that construct `GlyphBank` directly. + +## Fora de Escopo + +- Asset payload decode changes. +- Composer status/fault policy. +- Packer/studio changes. +- Sparse palette remapping. + +## Plano de Execucao + +### Step 1 - Introduce variable palette storage + +**What:** Replace fixed palette storage. + +**How:** Change `GlyphBank::palettes` from +`[[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]` to a +variable collection such as `Vec<[Color; GLYPH_BANK_COLORS_PER_PALETTE]>`. +Keep a separate maximum constant for v1 if useful. + +**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` + +### Step 2 - Update constructors + +**What:** Make constructors explicit about palette count. + +**How:** Add or update constructors so tests and loaders can create a bank with +a chosen palette count. Default constructors used only for empty test banks +must choose a valid count and not imply fixed 64 capacity. + +**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` + +### Step 3 - Add validation helpers + +**What:** Provide canonical palette validity APIs. + +**How:** Add methods such as `palette_count()`, `contains_palette(palette_id)`, +and a fallible color lookup that distinguishes invalid palette references from +transparent colors. + +**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` + +### Step 4 - Update direct palette mutations in tests + +**What:** Fix tests that write directly into fixed array slots. + +**How:** Replace direct `bank.palettes[id][color] = value` call sites with +helpers or ensure the test bank was created with enough palettes first. + +**Files:** `crates/console/prometeu-drivers/src/frame_composer.rs`, +`crates/console/prometeu-drivers/src/gfx.rs`, +`crates/console/prometeu-system/src/services/vm_runtime/tests.rs` + +### Step 5 - Keep direct identity + +**What:** Preserve `palette_id` identity. + +**How:** Do not introduce maps, compaction tables, or translated palette ids. +Index `N` in the vector is palette identity `N`. + +**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` + +## Criterios de Aceite + +- [ ] Resident `GlyphBank` stores exactly the loaded palette count. +- [ ] `GlyphBank` exposes bank-dependent palette validity. +- [ ] Invalid palette lookup can be detected separately from a transparent + color. +- [ ] No sparse-to-dense remapping structure is added. +- [ ] Direct test constructors no longer rely on implicit 64 palette capacity. + +## Tests / Validacao + +- Run HAL and driver unit tests that cover glyph bank construction and color + lookup. +- Add focused tests for `palette_count = 1` and `palette_count = 64`. +- Add a test proving invalid palette lookup is not silently equivalent to + transparent color. + +## Riscos + +- Many tests directly mutate `palettes[id]`; the migration can be noisy. +- Existing renderer paths may rely on `resolve_color` returning transparent for + invalid ids, which conflicts with `DEC-0041`. diff --git a/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md b/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md new file mode 100644 index 00000000..c44e6f66 --- /dev/null +++ b/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md @@ -0,0 +1,112 @@ +--- +id: PLN-0169 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Asset Decode Validation for Variable Glyph Palettes +status: open +created: 2026-07-14 +ref_decisions: [DEC-0041] +tags: [runtime, assets, glyph-bank, decode, validation] +--- + +## Briefing + +`DEC-0041` changes glyph-bank decode from a fixed 4096-byte palette block to a +variable `palette_count * 16 * 4` block. The asset manager is the runtime gate +that must reject malformed glyph payloads before residency. + +## Decisions de Origem + +- `DEC-0041` - Variable Glyph Bank Palette Protocol + +## Alvo + +Update glyph asset layout validation and decode to accept `palette_count` in +`1..=64` and materialize exactly that many palettes. + +## Escopo + +- Update `crates/console/prometeu-drivers/src/asset.rs`. +- Replace exact `palette_count == 64` validation. +- Replace fixed `GLYPH_BANK_PALETTE_BYTES_V1` usage in decode. +- Validate `size` and `decoded_size` using variable formulas. +- Read exactly `palette_count * 16 * 4` bytes. +- Preserve RGBA byte order. + +## Fora de Escopo + +- HAL storage changes except as required by `PLN-0168`. +- Composer invalid-reference behavior. +- Packer output changes. + +## Plano de Execucao + +### Step 1 - Change layout return data + +**What:** Carry palette count through layout validation. + +**How:** Update `decode_glyph_bank_layout` to return `palette_count` and +palette byte count along with tile size, dimensions, and serialized pixel byte +count. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs` + +### Step 2 - Replace exact palette-count validation + +**What:** Accept only the DEC-0041 range. + +**How:** Reject `palette_count = 0` and `palette_count > 64`; accept all values +in `1..=64`. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs` + +### Step 3 - Use variable size formulas + +**What:** Align entry validation with the spec. + +**How:** Compute `palette_bytes = palette_count * 16 * size_of::()`, +`serialized_size = packed_pixels + palette_bytes`, and +`decoded_size = logical_pixels + palette_bytes`. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs` + +### Step 4 - Decode variable palette data + +**What:** Read only materialized palettes. + +**How:** In buffer and reader decode paths, slice/read exactly `palette_bytes`. +Populate the new variable `GlyphBank` palette representation in order. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs` + +### Step 5 - Remove fixed-byte assumptions + +**What:** Retire the fixed payload block from active decode. + +**How:** Replace helper functions and tests that assume +`GLYPH_BANK_PALETTE_BYTES_V1` is always in the payload. Keep a maximum constant +only if it is named as a maximum, not a payload size. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs` + +## Criterios de Aceite + +- [ ] Decode accepts valid `palette_count` values from `1` through `64`. +- [ ] Decode rejects `palette_count = 0`. +- [ ] Decode rejects `palette_count > 64`. +- [ ] `size` and `decoded_size` validation use variable palette bytes. +- [ ] Buffer and reader decode paths behave consistently. +- [ ] RGBA channel order remains unchanged. + +## Tests / Validacao + +- Add unit tests for `palette_count = 1`, an intermediate count, and `64`. +- Add rejection tests for `0`, `65`, short palette data, oversized metadata + size, and mismatched `decoded_size`. +- Run the crate tests that cover asset manager glyph decode. + +## Riscos + +- Existing tests may use generated glyph payload helpers with fixed 64-palette + size. +- Reader and buffer paths can drift if only one path receives the variable-size + change. diff --git a/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md b/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md new file mode 100644 index 00000000..736d0df8 --- /dev/null +++ b/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md @@ -0,0 +1,125 @@ +--- +id: PLN-0170 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Composer Palette Reference Failure Semantics +status: open +created: 2026-07-14 +ref_decisions: [DEC-0041] +tags: [runtime, gfx, composer, scene, sprite, validation] +--- + +## Briefing + +`DEC-0041` requires invalid palette references to fail explicitly when +scene/sprite/composer logic uses `palette_id >= palette_count` against a loaded +glyph bank. Current color resolution can silently return transparent for +missing palettes, which is no longer canonical behavior. + +## Decisions de Origem + +- `DEC-0041` - Variable Glyph Bank Palette Protocol + +## Alvo + +Define and implement explicit runtime failure behavior for invalid palette +references in canonical composition paths. + +## Escopo + +- Inspect scene binding, scene composition, sprite emission, and render + resolution paths. +- Choose existing status/fault behavior where it fits the current ABI. +- Prevent canonical composition from substituting transparent/default color for + invalid palette references. +- Update tests for scene and sprite invalid palette references. + +## Fora de Escopo + +- Adding new public ABI status values unless existing statuses cannot represent + the error. +- Packer or scene authoring validation. +- Sparse palette remapping. + +## Plano de Execucao + +### Step 1 - Map current composition paths + +**What:** Identify where palette ids are consumed. + +**How:** Trace `composer.emit_sprite`, scene binding/composition, frame +composer packet creation, and software GFX resolution from `Glyph` to +`GlyphBank::resolve_color`. + +**Files:** `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`, +`crates/console/prometeu-drivers/src/frame_composer.rs`, +`crates/console/prometeu-drivers/src/gfx.rs`, +`crates/console/prometeu-hal/src/glyph_bank.rs` + +### Step 2 - Select explicit failure behavior + +**What:** Choose the runtime-visible failure route. + +**How:** Use existing semantics where possible: status-returning sprite calls +should use an existing invalid status if it accurately describes the failure; +scene dependency failures that occur during binding/composition should follow +the existing fatal dependency-failure model documented in the GFX spec. + +**Files:** `docs/specs/runtime/04-gfx-peripheral.md`, +`crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`, +`crates/console/prometeu-drivers/src/frame_composer.rs` + +### Step 3 - Validate sprite references + +**What:** Prevent invalid sprite palette references. + +**How:** When `emit_sprite` has access to the target glyph bank, reject +`palette_id >= palette_count` before the sprite is accepted for canonical +composition. + +**Files:** `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`, +`crates/console/prometeu-drivers/src/frame_composer.rs`, +`crates/console/prometeu-drivers/src/hardware.rs` + +### Step 4 - Validate scene references + +**What:** Prevent invalid scene palette references. + +**How:** During scene bind or scene composition, validate tile palette ids +against each referenced loaded glyph bank. Fail explicitly if a scene layer +references a palette not present in its glyph dependency. + +**Files:** `crates/console/prometeu-drivers/src/gfx.rs`, +`crates/console/prometeu-hal/src/scene_viewport_cache.rs`, +`crates/console/prometeu-hal/src/scene_viewport_resolver.rs` + +### Step 5 - Remove silent fallback from canonical paths + +**What:** Stop hiding invalid palette ids as transparent. + +**How:** Use fallible palette lookup in canonical render paths. Transparent is +valid only when produced by an actual RGBA palette entry with alpha `0`. + +**Files:** `crates/console/prometeu-drivers/src/gfx.rs`, +`crates/console/prometeu-hal/src/glyph_bank.rs` + +## Criterios de Aceite + +- [ ] Invalid sprite `palette_id` is rejected before canonical composition. +- [ ] Invalid scene tile `palette_id` fails explicitly against the loaded bank. +- [ ] Canonical render paths do not use invalid palette lookup as transparent. +- [ ] Valid transparent RGBA palette entries still render as transparent. +- [ ] The selected status/fault behavior is documented in the spec. + +## Tests / Validacao + +- Add sprite test for `palette_id == palette_count`. +- Add scene test for a tile palette id above the referenced bank's count. +- Add regression test proving alpha `0` in a valid palette still works. +- Run VM runtime and GFX driver tests. + +## Riscos + +- Some paths may not have easy access to the loaded glyph bank when accepting a + sprite packet. +- Existing ABI statuses may be less precise than a new status, but adding a new + status has wider compatibility cost. diff --git a/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md b/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md new file mode 100644 index 00000000..2d75faca --- /dev/null +++ b/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md @@ -0,0 +1,114 @@ +--- +id: PLN-0171 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Variable Glyph Palette Tests Fixtures and Residue Scan +status: open +created: 2026-07-14 +ref_decisions: [DEC-0041] +tags: [runtime, tests, fixtures, glyph-bank, palette-serialization] +--- + +## Briefing + +`DEC-0041` changes a cross-cutting asset contract. Tests and fixtures must be +updated as a dedicated pass so fixed 64-palette assumptions do not survive in +helpers, generated data, or residue. + +## Decisions de Origem + +- `DEC-0041` - Variable Glyph Bank Palette Protocol + +## Alvo + +Convert runtime tests and fixtures to exercise variable glyph palette counts +and prove fixed-padding assumptions are gone from active behavior. + +## Escopo + +- Update test helpers that build glyph payloads. +- Add minimum, maximum, and intermediate palette-count tests. +- Add invalid palette-count tests. +- Add invalid palette-reference tests once `PLN-0170` is implemented. +- Run residue scans for fixed palette payload assumptions. + +## Fora de Escopo + +- Production decode implementation. +- Production composer implementation. +- Packer/studio fixture generation. + +## Plano de Execucao + +### Step 1 - Inventory glyph payload helpers + +**What:** Find all runtime test helpers with fixed glyph palette sizes. + +**How:** Search for `GLYPH_BANK_PALETTE_COUNT_V1`, +`GLYPH_BANK_PALETTE_BYTES_V1`, `64 * 16 * 4`, `4096`, and helper names such as +`test_glyph_asset_data`. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs`, +`crates/console/prometeu-system/src/services/vm_runtime/tests.rs`, +`crates/console/prometeu-system/src/services/vm_runtime/tests_asset_bank.rs` + +### Step 2 - Create variable payload builders + +**What:** Make tests express palette count deliberately. + +**How:** Replace fixed helpers with helpers that accept `palette_count`, build +exactly `palette_count * 16 * 4` palette bytes, and compute matching `size` and +`decoded_size`. + +**Files:** Runtime test modules that construct glyph assets. + +### Step 3 - Add decode boundary coverage + +**What:** Prove valid and invalid counts. + +**How:** Add tests for `palette_count = 1`, an intermediate value, `64`, `0`, +and `65`. + +**Files:** `crates/console/prometeu-drivers/src/asset.rs` + +### Step 4 - Add composition coverage + +**What:** Prove invalid palette references fail. + +**How:** After `PLN-0170`, add scene and sprite tests where +`palette_id == palette_count` and confirm explicit failure behavior. + +**Files:** `crates/console/prometeu-drivers/src/gfx.rs`, +`crates/console/prometeu-drivers/src/frame_composer.rs`, +`crates/console/prometeu-system/src/services/vm_runtime/tests.rs` + +### Step 5 - Run residue scan + +**What:** Catch leftover active fixed-palette assumptions. + +**How:** Scan code, tests, and specs for fixed palette byte formulas. Keep only +maximum-bound constants and historical documentation. + +**Files:** `crates/`, `docs/specs/runtime/`, `discussion/` + +## Criterios de Aceite + +- [ ] Tests no longer need a fixed 4096-byte palette payload for every glyph + bank. +- [ ] Decode tests cover `1`, intermediate counts, `64`, `0`, and `65`. +- [ ] Composition tests cover invalid palette references. +- [ ] Residue scan finds no active fixed serialized palette block assumption. +- [ ] Historical or maximum-bound uses of `64` are clearly named. + +## Tests / Validacao + +- Run targeted crate tests for asset decode, GFX, frame composer, and VM asset + bank flows. +- Run `rg -n "4096|64 \\* 16|GLYPH_BANK_PALETTE_BYTES_V1|palette_count"` + against `crates`, `docs/specs/runtime`, and active discussion artifacts. +- Run `discussion validate`. + +## Riscos + +- Residue scans can produce legitimate hits for the v1 maximum of `64`; those + must be classified instead of mechanically removed. +- Test helper churn can obscure the behavioral assertions if not kept focused. diff --git a/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md b/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md new file mode 100644 index 00000000..95ccf160 --- /dev/null +++ b/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md @@ -0,0 +1,112 @@ +--- +id: PLN-0172 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Runtime Spec Handoff to Packer and Studio +status: open +created: 2026-07-14 +ref_decisions: [DEC-0041] +tags: [runtime, packer, studio, handoff, glyph-bank, palette-serialization] +--- + +## Briefing + +`DEC-0041` makes runtime the authority for `GLYPH/indexed_v1`. After the +runtime spec and implementation are updated, packer and studio must align as +downstream producers of runtime-conforming assets. + +## Decisions de Origem + +- `DEC-0041` - Variable Glyph Bank Palette Protocol + +## Alvo + +Prepare a clear runtime-owned handoff for packer/studio without moving protocol +authority out of the runtime repository. + +## Escopo + +- Summarize the accepted runtime contract for downstream repositories. +- Identify exact spec sections packer/studio must follow. +- Identify fixture and payload requirements. +- Capture any compatibility note needed for old fixed-padding payloads. +- Update runtime discussion artifacts with handoff status when implementation + is complete. + +## Fora de Escopo + +- Editing `../studio` or packer code from this plan. +- Reopening `AGD-0005` in `../studio`. +- Defining a v2 payload. +- Adding producer-specific runtime exceptions. + +## Plano de Execucao + +### Step 1 - Wait for runtime spec publication + +**What:** Use runtime specs as handoff source. + +**How:** Do not send or encode downstream requirements until `PLN-0167` has +landed. The published runtime spec is the contract. + +**Files:** `docs/specs/runtime/15-asset-management.md`, +`docs/specs/runtime/04-gfx-peripheral.md` + +### Step 2 - Write downstream contract summary + +**What:** Produce a concise handoff note. + +**How:** Summarize `palette_count`, payload layout, size formulas, +`palette_id` validity, absence of remapping, and incompatibility with the old +padding contract. + +**Files:** Runtime discussion plan or follow-up note as appropriate. + +### Step 3 - Identify downstream fixture updates + +**What:** Define what packer/studio fixtures must prove. + +**How:** Require fixtures with `palette_count = 1`, an intermediate count, and +`64`, plus rejection or regeneration of fixed-padding assumptions where the +metadata does not match the payload. + +**Files:** Handoff note only; actual downstream files are outside this repo. + +### Step 4 - Preserve runtime authority + +**What:** Prevent downstream divergence. + +**How:** State that packer/studio metadata such as authored palette count is +informative unless the runtime spec defines it as effective metadata. + +**Files:** Handoff note; runtime spec references. + +### Step 5 - Close loop after downstream acknowledgment + +**What:** Track completion. + +**How:** Once packer/studio work is done elsewhere, update discussion lessons or +housekeeping artifacts from the runtime side without adding new normative text +to lessons. + +**Files:** `discussion/lessons/` only after implementation is complete. + +## Criterios de Aceite + +- [ ] Runtime specs are updated before downstream handoff. +- [ ] Handoff states that runtime owns the protocol. +- [ ] Handoff lists exact payload formulas and validity rules. +- [ ] Handoff states that no sparse-to-dense remapping exists in v1. +- [ ] Handoff states that old fixed-padding payloads are not compatibility + inputs unless they satisfy the new contract directly. + +## Tests / Validacao + +- Verify handoff references the final spec sections, not agenda text. +- Verify no downstream instruction contradicts `DEC-0041`. +- Run `discussion validate`. + +## Riscos + +- Downstream repositories may still treat `metadata.palette_authored` as + authoritative; this plan must make runtime-effective metadata explicit. +- Creating handoff text too early could freeze pre-implementation details. -- 2.47.2 From c89116b6f82c1d39aed6eb3e0765565ff75fa56a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:42:11 +0100 Subject: [PATCH 2/9] implements PLN-0167 variable glyph palette specs --- discussion/index.ndjson | 2 +- ...ract-update-for-variable-glyph-palettes.md | 3 ++- docs/specs/runtime/04-gfx-peripheral.md | 15 +++++++++---- docs/specs/runtime/15-asset-management.md | 22 +++++++++++++++---- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 7d1cafa1..3c90ca1c 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":"open","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":"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":[]} diff --git a/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md b/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md index 36126eeb..c048cf2b 100644 --- a/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md +++ b/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md @@ -2,7 +2,8 @@ id: PLN-0167 ticket: runtime-owned-variable-glyph-bank-palette-protocol title: Spec Contract Update for Variable Glyph Palettes -status: open +status: done +completed: 2026-07-14 created: 2026-07-14 ref_decisions: [DEC-0041] tags: [runtime, gfx, assets, glyph-bank, palette-serialization, protocol, specs] diff --git a/docs/specs/runtime/04-gfx-peripheral.md b/docs/specs/runtime/04-gfx-peripheral.md index d6801caa..b6f8105f 100644 --- a/docs/specs/runtime/04-gfx-peripheral.md +++ b/docs/specs/runtime/04-gfx-peripheral.md @@ -625,7 +625,8 @@ Each tilemap cell contains: Runtime-facing validity rule for v1: -- `palette_id` values are valid only in the range `0..63` +- `palette_id` values are valid only when `palette_id < palette_count` for the + resolved resident glyph bank #### Sprite @@ -640,7 +641,8 @@ Each sprite draw contains: Runtime-facing validity rule for v1: -- `palette_id` values are valid only in the range `0..63` +- `palette_id` values are valid only when `palette_id < palette_count` for the + referenced resident glyph bank --- @@ -649,7 +651,7 @@ Runtime-facing validity rule for v1: The pipeline works like this: 1. Read indexed pixel from tile (value 0..15) -2. Resolve: +2. Validate and resolve: - real_color = palette[palette_id][index] 3. Apply: - flip @@ -667,6 +669,9 @@ else: draw_or_blend(color) ``` +The `palette_id` lookup above is valid only after the runtime has established +that the referenced resident glyph bank contains that palette. + --- ### 19.7. Organization of Tile Banks @@ -766,6 +771,7 @@ Rules: - missing glyph dependencies referenced by a resident scene are not a passive `status:int` case; - if scene activation discovers that a layer dependency cannot be resolved to a committed glyph asset, the machine MUST fail fatally and emit a clear log; - if scene composition later discovers that a layer dependency can no longer be resolved, the machine MUST fail fatally and emit a clear log; +- if scene activation or composition discovers that a tile references `palette_id >= palette_count` for its resolved glyph bank, the machine MUST fail explicitly and MUST NOT substitute transparent, black, or default color; - runtime MUST NOT continue canonical scene composition after such a dependency failure. ### 20.2 `composer.emit_sprite` @@ -774,7 +780,7 @@ Rules: ABI: 1. `glyph_id: int` — glyph index within the bank -2. `palette_id: int` — palette index +2. `palette_id: int` — palette index within the referenced bank; valid only when `palette_id < palette_count` for that resident glyph bank 3. `x: int` — x coordinate 4. `y: int` — y coordinate 5. `layer: int` — composition layer reference @@ -797,4 +803,5 @@ Operational notes: - the canonical public sprite contract is frame-emission based; - no caller-provided sprite index exists in the v1 canonical ABI; - no `active` flag exists in the v1 canonical ABI; +- `palette_id >= palette_count` for the referenced glyph bank is an explicit invalid palette reference and MUST NOT be rendered through transparent, black, or default color substitution; - overflow remains non-fatal and must not escalate to trap in v1. diff --git a/docs/specs/runtime/15-asset-management.md b/docs/specs/runtime/15-asset-management.md index 699c499d..43e6982d 100644 --- a/docs/specs/runtime/15-asset-management.md +++ b/docs/specs/runtime/15-asset-management.md @@ -115,7 +115,7 @@ For `BankType::GLYPH`, the v1 runtime-facing contract is: - `codec = NONE` - serialized pixels use packed `u4` palette indices - serialized palettes use `RGBA8888` with canonical RGBA channel order -- `palette_count = 64` +- `palette_count` is the number of serialized and resident palettes and must be in `1..=64` - runtime materialization may expand pixel indices to one `u8` per pixel For `GLYPH`, `NONE` means there is no additional generic codec layer beyond the bank contract itself. @@ -136,7 +136,7 @@ Required effective metadata fields for `GLYPH` at the root level: - `tile_size`: tile edge in pixels; valid values are `8`, `16`, or `32` - `width`: total sheet width in pixels - `height`: total sheet height in pixels -- `palette_count`: number of serialized palettes for the bank +- `palette_count`: number of serialized palettes and resident runtime palettes for the bank Optional informative subtrees: @@ -145,7 +145,7 @@ Optional informative subtrees: Validation rules for `GLYPH` v1: -- `palette_count` must be `64` +- `palette_count` must be in the inclusive range `1..=64` - `width * height` defines the number of logical indexed pixels in the decoded sheet - extra metadata may exist, but the runtime contract must not depend on it to reconstruct the in-memory bank unless that data is defined at the root as an effective field. @@ -158,7 +158,16 @@ The tile-bank payload therefore separates serialized storage form from runtime m - serialized pixel plane: packed `4bpp` - decoded pixel plane: expanded `u8` indices, one entry per pixel -- palette table: `64 * 16` colors in RGBA8888 channel order +- palette table: `palette_count * 16` colors in RGBA8888 channel order + +For `GLYPH` v1: + +```text +serialized_pixel_bytes = ceil(width * height / 2) +palette_bytes = palette_count * 16 * 4 +size = serialized_pixel_bytes + palette_bytes +decoded_size = (width * height) + palette_bytes +``` For `GLYPH` v1: @@ -172,6 +181,11 @@ before they are loaded by the runtime. Palette indices are ordinary indices. Transparency is represented by the alpha channel of the resolved RGBA8888 palette entry, not by reserving index `0`. +`palette_id` is valid only when it is lower than the referenced resident glyph +bank's `palette_count`. Canonical runtime composition MUST NOT substitute +transparent, black, or any other default color for `palette_id >= +palette_count`. + ### 4.2 `SCENE` asset contract in v1 For `BankType::SCENE`, the v1 runtime-facing contract is: -- 2.47.2 From a415c172c7d28ceb3581018f378068555db8d6c3 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:46:50 +0100 Subject: [PATCH 3/9] 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] -- 2.47.2 From 94ffd241bdad6eb6ef3f152ef6b3892f092413c6 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:48:57 +0100 Subject: [PATCH 4/9] implements PLN-0169 variable glyph palette decode --- crates/console/prometeu-drivers/src/asset.rs | 171 +++++++++++++++--- discussion/index.ndjson | 2 +- ...-validation-for-variable-glyph-palettes.md | 3 +- 3 files changed, 144 insertions(+), 32 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index 493eccd5..eaf9b204 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -191,10 +191,10 @@ impl GlyphAssetSlotIndex { } } -const GLYPH_BANK_PALETTE_COUNT_V1: usize = 64; +const GLYPH_BANK_MAX_PALETTE_COUNT_V1: usize = 64; const GLYPH_BANK_COLORS_PER_PALETTE: usize = 16; -const GLYPH_BANK_PALETTE_BYTES_V1: usize = - GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * size_of::(); +const GLYPH_BANK_PALETTE_BYTES_PER_PALETTE: usize = + GLYPH_BANK_COLORS_PER_PALETTE * size_of::(); /// Resident metadata for a decoded/materialized asset inside a BankPolicy. #[derive(Debug)] @@ -599,7 +599,7 @@ impl AssetBridge for AssetManager { impl AssetManager { fn decode_glyph_bank_layout( entry: &AssetEntry, - ) -> Result<(TileSize, usize, usize, usize), String> { + ) -> Result<(TileSize, usize, usize, usize, usize, usize), String> { let meta = entry.metadata_as_glyph_bank()?; let tile_size = match meta.tile_size { @@ -609,7 +609,8 @@ impl AssetManager { _ => return Err(format!("Invalid tile_size: {}", meta.tile_size)), }; - if meta.palette_count as usize != GLYPH_BANK_PALETTE_COUNT_V1 { + let palette_count = meta.palette_count as usize; + if !(1..=GLYPH_BANK_MAX_PALETTE_COUNT_V1).contains(&palette_count) { return Err(format!("Invalid palette_count: {}", meta.palette_count)); } @@ -618,11 +619,14 @@ impl AssetManager { let logical_pixels = width.checked_mul(height).ok_or("GlyphBank dimensions overflow")?; let serialized_pixel_bytes = logical_pixels.div_ceil(2); + let palette_bytes = palette_count + .checked_mul(GLYPH_BANK_PALETTE_BYTES_PER_PALETTE) + .ok_or("GlyphBank palette size overflow")?; let serialized_size = serialized_pixel_bytes - .checked_add(GLYPH_BANK_PALETTE_BYTES_V1) + .checked_add(palette_bytes) .ok_or("GlyphBank serialized size overflow")?; let decoded_size = logical_pixels - .checked_add(GLYPH_BANK_PALETTE_BYTES_V1) + .checked_add(palette_bytes) .ok_or("GlyphBank decoded size overflow")?; if entry.size != serialized_size as u64 { @@ -639,7 +643,7 @@ impl AssetManager { )); } - Ok((tile_size, width, height, serialized_pixel_bytes)) + Ok((tile_size, width, height, serialized_pixel_bytes, palette_count, palette_bytes)) } fn unpack_glyph_bank_pixels(packed_pixels: &[u8], logical_pixels: usize) -> Vec { @@ -1200,8 +1204,9 @@ impl AssetManager { entry: &AssetEntry, buffer: &[u8], ) -> Result { - 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 { + let (tile_size, width, height, packed_pixel_bytes, palette_count, palette_bytes) = + Self::decode_glyph_bank_layout(entry)?; + if buffer.len() < packed_pixel_bytes + palette_bytes { return Err("Buffer too small for GLYPHBANK".to_string()); } @@ -1209,10 +1214,10 @@ impl AssetManager { let packed_pixels = &buffer[0..packed_pixel_bytes]; let pixel_indices = Self::unpack_glyph_bank_pixels(packed_pixels, logical_pixels); let palette_data = - &buffer[packed_pixel_bytes..packed_pixel_bytes + GLYPH_BANK_PALETTE_BYTES_V1]; + &buffer[packed_pixel_bytes..packed_pixel_bytes + palette_bytes]; let mut palettes = - vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]; + vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count]; for (p, pal) in palettes.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() { let offset = (p * 16 + c) * 4; @@ -1232,7 +1237,8 @@ impl AssetManager { entry: &AssetEntry, reader: &mut impl Read, ) -> Result { - let (tile_size, width, height, packed_pixel_bytes) = Self::decode_glyph_bank_layout(entry)?; + let (tile_size, width, height, packed_pixel_bytes, palette_count, palette_bytes) = + Self::decode_glyph_bank_layout(entry)?; let logical_pixels = width * height; let mut packed_pixels = vec![0_u8; packed_pixel_bytes]; reader @@ -1241,13 +1247,13 @@ impl AssetManager { let pixel_indices = Self::unpack_glyph_bank_pixels(&packed_pixels, logical_pixels); - let mut palette_data = [0_u8; GLYPH_BANK_PALETTE_BYTES_V1]; + let mut palette_data = vec![0_u8; palette_bytes]; reader .read_exact(&mut palette_data) .map_err(|_| "Buffer too small for GLYPHBANK".to_string())?; let mut palettes = - vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]; + vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count]; for (p, pal) in palettes.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() { let offset = (p * 16 + c) * 4; @@ -1883,35 +1889,56 @@ mod tests { use prometeu_hal::tile::Tile; use prometeu_hal::tilemap::TileMap; - fn expected_glyph_payload_size(width: usize, height: usize) -> usize { - (width * height).div_ceil(2) + GLYPH_BANK_PALETTE_BYTES_V1 + fn expected_glyph_payload_size(width: usize, height: usize, palette_count: usize) -> usize { + (width * height).div_ceil(2) + (palette_count * GLYPH_BANK_PALETTE_BYTES_PER_PALETTE) } - fn expected_glyph_decoded_size(width: usize, height: usize) -> usize { - width * height + GLYPH_BANK_PALETTE_BYTES_V1 + fn expected_glyph_decoded_size(width: usize, height: usize, palette_count: usize) -> usize { + width * height + (palette_count * GLYPH_BANK_PALETTE_BYTES_PER_PALETTE) } fn test_glyph_asset_data() -> Vec { + test_glyph_asset_data_with_palette_count(GLYPH_BANK_MAX_PALETTE_COUNT_V1) + } + + fn test_glyph_asset_data_with_palette_count(palette_count: usize) -> Vec { let mut data = vec![0x11u8; 128]; - data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_BYTES_V1]); + data.extend_from_slice(&vec![ + 0u8; + palette_count * GLYPH_BANK_PALETTE_BYTES_PER_PALETTE + ]); data } fn test_glyph_asset_entry(asset_name: &str, width: usize, height: usize) -> AssetEntry { + test_glyph_asset_entry_with_palette_count( + asset_name, + width, + height, + GLYPH_BANK_MAX_PALETTE_COUNT_V1, + ) + } + + fn test_glyph_asset_entry_with_palette_count( + asset_name: &str, + width: usize, + height: usize, + palette_count: usize, + ) -> AssetEntry { AssetEntry { asset_id: 0, asset_name: asset_name.to_string(), bank_type: BankType::GLYPH, offset: 0, - size: expected_glyph_payload_size(width, height) as u64, - decoded_size: expected_glyph_decoded_size(width, height) as u64, + size: expected_glyph_payload_size(width, height, palette_count) as u64, + decoded_size: expected_glyph_decoded_size(width, height, palette_count) as u64, codec: AssetCodec::None, metadata: serde_json::json!({ "tile_size": 16, "width": width, "height": height, - "palette_count": GLYPH_BANK_PALETTE_COUNT_V1, - "palette_authored": GLYPH_BANK_PALETTE_COUNT_V1 + "palette_count": palette_count, + "palette_authored": palette_count }), } } @@ -2034,22 +2061,69 @@ mod tests { #[test] fn test_decode_glyph_bank_unpacks_packed_pixels_and_reads_palette_colors() { - let entry = test_glyph_asset_entry("glyphs", 2, 2); + let entry = test_glyph_asset_entry_with_palette_count("glyphs", 2, 2, 1); let mut data = vec![0x10, 0x23]; - data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_BYTES_V1]); + data.extend_from_slice(&[0u8; GLYPH_BANK_PALETTE_BYTES_PER_PALETTE]); data[2..6].copy_from_slice(&[0x12, 0x34, 0x56, 0x78]); 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.palette_count(), 1); assert_eq!(bank.palettes[0][0], Color::from_raw(0x12345678)); } + #[test] + fn test_decode_glyph_bank_accepts_intermediate_palette_count() { + let entry = test_glyph_asset_entry_with_palette_count("glyphs", 16, 16, 7); + let data = test_glyph_asset_data_with_palette_count(7); + + let bank = + AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("glyph decode"); + + assert_eq!(bank.palette_count(), 7); + } + + #[test] + fn test_decode_glyph_bank_accepts_max_palette_count() { + let entry = test_glyph_asset_entry_with_palette_count( + "glyphs", + 16, + 16, + GLYPH_BANK_MAX_PALETTE_COUNT_V1, + ); + let data = test_glyph_asset_data_with_palette_count(GLYPH_BANK_MAX_PALETTE_COUNT_V1); + + let bank = + AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("glyph decode"); + + assert_eq!(bank.palette_count(), GLYPH_BANK_MAX_PALETTE_COUNT_V1); + } + + #[test] + fn test_decode_glyph_bank_reader_matches_buffer_for_variable_palette_count() { + let entry = test_glyph_asset_entry_with_palette_count("glyphs", 16, 16, 3); + let data = test_glyph_asset_data_with_palette_count(3); + + let from_buffer = + AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("buffer decode"); + let mut reader = std::io::Cursor::new(data); + let from_reader = + AssetManager::decode_glyph_bank_from_reader(&entry, &mut reader).expect("reader decode"); + + assert_eq!(from_buffer.palette_count(), from_reader.palette_count()); + assert_eq!(from_buffer.pixel_indices, from_reader.pixel_indices); + assert_eq!(from_buffer.palettes, from_reader.palettes); + } + #[test] fn test_decode_glyph_bank_rejects_short_packed_buffer() { let entry = test_glyph_asset_entry("glyphs", 16, 16); - let data = vec![0u8; expected_glyph_payload_size(16, 16) - 1]; + let data = vec![ + 0u8; + expected_glyph_payload_size(16, 16, GLYPH_BANK_MAX_PALETTE_COUNT_V1) - 1 + ]; let err = match AssetManager::decode_glyph_bank_from_buffer(&entry, &data) { Ok(_) => panic!("glyph decode should reject short buffer"), @@ -2060,9 +2134,9 @@ mod tests { } #[test] - fn test_decode_glyph_bank_requires_palette_count_64() { + fn test_decode_glyph_bank_rejects_palette_count_zero() { let mut entry = test_glyph_asset_entry("glyphs", 16, 16); - entry.metadata["palette_count"] = serde_json::json!(32); + entry.metadata["palette_count"] = serde_json::json!(0); let err = match AssetManager::decode_glyph_bank_from_buffer(&entry, &test_glyph_asset_data()) { @@ -2070,7 +2144,44 @@ mod tests { Err(err) => err, }; - assert_eq!(err, "Invalid palette_count: 32"); + assert_eq!(err, "Invalid palette_count: 0"); + } + + #[test] + fn test_decode_glyph_bank_rejects_palette_count_above_v1_max() { + let mut entry = test_glyph_asset_entry("glyphs", 16, 16); + entry.metadata["palette_count"] = serde_json::json!(65); + + let err = + match AssetManager::decode_glyph_bank_from_buffer(&entry, &test_glyph_asset_data()) { + Ok(_) => panic!("glyph decode should reject invalid palette_count"), + Err(err) => err, + }; + + assert_eq!(err, "Invalid palette_count: 65"); + } + + #[test] + fn test_decode_glyph_bank_rejects_mismatched_variable_decoded_size() { + let mut entry = test_glyph_asset_entry_with_palette_count("glyphs", 16, 16, 3); + entry.decoded_size += 1; + + let err = match AssetManager::decode_glyph_bank_from_buffer( + &entry, + &test_glyph_asset_data_with_palette_count(3), + ) { + Ok(_) => panic!("glyph decode should reject invalid decoded_size"), + Err(err) => err, + }; + + assert_eq!( + err, + format!( + "Invalid GLYPHBANK decoded_size: expected {}, got {}", + expected_glyph_decoded_size(16, 16, 3), + expected_glyph_decoded_size(16, 16, 3) + 1 + ) + ); } #[test] diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 5a6c9ef3..82002b15 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":"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":[]} +{"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":"done","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-0169-asset-decode-validation-for-variable-glyph-palettes.md b/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md index c44e6f66..5976ca85 100644 --- a/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md +++ b/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md @@ -2,7 +2,8 @@ id: PLN-0169 ticket: runtime-owned-variable-glyph-bank-palette-protocol title: Asset Decode Validation for Variable Glyph Palettes -status: open +status: done +completed: 2026-07-14 created: 2026-07-14 ref_decisions: [DEC-0041] tags: [runtime, assets, glyph-bank, decode, validation] -- 2.47.2 From 4c0f1cb9ad26696ae11c960842676df57e4c2b70 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:51:44 +0100 Subject: [PATCH 5/9] implements PLN-0170 palette reference failures --- crates/console/prometeu-drivers/src/gfx.rs | 63 ++++++++++++++++--- .../console/prometeu-drivers/src/hardware.rs | 30 ++++++++- .../src/services/vm_runtime/tests.rs | 47 ++++++++++++++ discussion/index.ndjson | 2 +- ...ser-palette-reference-failure-semantics.md | 3 +- docs/specs/runtime/04-gfx-peripheral.md | 6 +- 6 files changed, 139 insertions(+), 12 deletions(-) diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index bcd69f66..e984a3d1 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -812,10 +812,14 @@ 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) - .unwrap_or(Color::TRANSPARENT); + let color = tile.bank.resolve_color(tile.entry.palette_id, px_index).unwrap_or_else( + || { + panic!( + "SCENE composition fatal: palette_id {} is not resident for glyph asset {}", + tile.entry.palette_id, tile.entry.glyph_asset_id + ) + }, + ); if color.alpha() == 0 { continue; } @@ -865,9 +869,14 @@ 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) - .unwrap_or(Color::TRANSPARENT); + let color = bank.resolve_color(sprite.glyph.palette_id, px_index).unwrap_or_else( + || { + panic!( + "SPRITE composition fatal: palette_id {} is not resident for glyph bank {}", + sprite.glyph.palette_id, sprite.bank_id + ) + }, + ); if color.alpha() == 0 { continue; } @@ -1162,6 +1171,26 @@ mod tests { assert_eq!(back[0], Color::RED.raw()); } + #[test] + #[should_panic(expected = "SPRITE composition fatal: palette_id 1 is not resident")] + fn sprite_draw_panics_for_invalid_palette_reference() { + let bank = make_filled_glyph_bank(TileSize::Size8, 0, &[(0, 0, Color::GREEN)]); + let mut back = vec![Color::BLACK.raw(); 8 * 8]; + let sprite = Sprite { + glyph: Glyph { glyph_id: 0, palette_id: 1 }, + x: 0, + y: 0, + layer: 0, + flip_x: false, + flip_y: false, + bank_id: 0, + active: true, + priority: 0, + }; + + Gfx::draw_sprite_pixel_by_pixel(&mut back, 8, 8, &sprite, &bank); + } + #[test] fn test_cached_tile_draws_opaque_color_index_zero() { let bank = make_filled_glyph_bank(TileSize::Size8, 0, &[(0, 0, Color::GREEN)]); @@ -1204,6 +1233,26 @@ mod tests { assert_eq!(target.back[0], Color::RED.raw()); } + #[test] + #[should_panic(expected = "SCENE composition fatal: palette_id 1 is not resident")] + fn cached_tile_draw_panics_for_invalid_palette_reference() { + let bank = make_filled_glyph_bank(TileSize::Size8, 0, &[(0, 0, Color::GREEN)]); + let mut back = vec![Color::BLACK.raw(); 8 * 8]; + let mut target = RenderTarget { back: &mut back, screen_w: 8, screen_h: 8 }; + let entry = CachedTileEntry { + active: true, + glyph_id: 0, + palette_id: 1, + flags: 0, + glyph_asset_id: 0, + }; + + Gfx::draw_cached_tile_pixels( + &mut target, + CachedTileDraw { x: 0, y: 0, entry, bank: &bank, tile_size: TileSize::Size8 }, + ); + } + #[test] fn test_draw_rect() { let banks = Arc::new(MemoryBanks::new()); diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index dce0cd0b..bb375591 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -118,7 +118,11 @@ impl Game2DFrameComposer for Hardware { } fn emit_sprite(&mut self, sprite: Sprite) -> prometeu_hal::ComposerOpStatus { - if self.gfx.glyph_banks.glyph_bank_slot(sprite.bank_id as usize).is_none() { + let Some(bank) = self.gfx.glyph_banks.glyph_bank_slot(sprite.bank_id as usize) else { + return prometeu_hal::ComposerOpStatus::BankInvalid; + }; + + if !bank.contains_palette(sprite.glyph.palette_id) { return prometeu_hal::ComposerOpStatus::BankInvalid; } @@ -433,4 +437,28 @@ mod tests { assert_eq!(hardware.gfx.front_buffer()[0], Color::RED.raw()); } + + #[test] + fn emit_sprite_rejects_palette_id_outside_loaded_glyph_bank() { + let banks = Arc::new(MemoryBanks::new()); + banks.install_glyph_bank(0, Arc::new(make_glyph_bank())); + let mut hardware = Hardware::new_with_memory_banks(banks); + + let status = Game2DFrameComposer::emit_sprite( + &mut hardware, + Sprite { + glyph: Glyph { glyph_id: 0, palette_id: 1 }, + x: 0, + y: 0, + layer: 0, + bank_id: 0, + active: false, + flip_x: false, + flip_y: false, + priority: 0, + }, + ); + + assert_eq!(status, prometeu_hal::ComposerOpStatus::BankInvalid); + } } 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 7fedd15a..4e81ca75 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -1279,6 +1279,53 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() { assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::BankInvalid as i64)]); } +#[test] +fn tick_composer_emit_sprite_invalid_palette_returns_bank_invalid() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; + let mut vm = VirtualMachine::default(); + let banks = Arc::new(MemoryBanks::new()); + banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 0, Color::BLUE))); + let mut platform = TestPlatform::new_with_memory_banks(banks); + let signals = InputSignals::default(); + let code = assemble( + "PUSH_I32 0\nPUSH_I32 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT", + ) + .expect("assemble"); + let program = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "composer".into(), + name: "emit_sprite".into(), + version: 1, + arg_slots: 9, + ret_slots: 1, + }], + ); + let cartridge = cartridge_with_program(program, caps::GFX); + + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut platform, + ); + assert!(report.is_none(), "invalid palette must not crash VM execution"); + assert!(vm.is_halted()); + assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::BankInvalid as i64)]); +} + #[test] fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() { let mut runtime = VirtualMachineRuntime::new(None); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 82002b15..08a69a3c 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":"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":"done","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":"done","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":"done","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-0170-composer-palette-reference-failure-semantics.md b/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md index 736d0df8..4a74f106 100644 --- a/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md +++ b/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md @@ -2,7 +2,8 @@ id: PLN-0170 ticket: runtime-owned-variable-glyph-bank-palette-protocol title: Composer Palette Reference Failure Semantics -status: open +status: done +completed: 2026-07-14 created: 2026-07-14 ref_decisions: [DEC-0041] tags: [runtime, gfx, composer, scene, sprite, validation] diff --git a/docs/specs/runtime/04-gfx-peripheral.md b/docs/specs/runtime/04-gfx-peripheral.md index b6f8105f..a64ada99 100644 --- a/docs/specs/runtime/04-gfx-peripheral.md +++ b/docs/specs/runtime/04-gfx-peripheral.md @@ -771,7 +771,8 @@ Rules: - missing glyph dependencies referenced by a resident scene are not a passive `status:int` case; - if scene activation discovers that a layer dependency cannot be resolved to a committed glyph asset, the machine MUST fail fatally and emit a clear log; - if scene composition later discovers that a layer dependency can no longer be resolved, the machine MUST fail fatally and emit a clear log; -- if scene activation or composition discovers that a tile references `palette_id >= palette_count` for its resolved glyph bank, the machine MUST fail explicitly and MUST NOT substitute transparent, black, or default color; +- if scene activation or composition discovers that a tile references `palette_id >= palette_count` for its resolved glyph bank, the machine MUST fail fatally and emit a clear log; +- invalid scene palette references are scene dependency failures and MUST NOT be rendered through transparent, black, or default color substitution; - runtime MUST NOT continue canonical scene composition after such a dependency failure. ### 20.2 `composer.emit_sprite` @@ -803,5 +804,6 @@ Operational notes: - the canonical public sprite contract is frame-emission based; - no caller-provided sprite index exists in the v1 canonical ABI; - no `active` flag exists in the v1 canonical ABI; -- `palette_id >= palette_count` for the referenced glyph bank is an explicit invalid palette reference and MUST NOT be rendered through transparent, black, or default color substitution; +- `palette_id >= palette_count` for the referenced glyph bank MUST return `BANK_INVALID`; +- invalid sprite palette references MUST NOT be rendered through transparent, black, or default color substitution; - overflow remains non-fatal and must not escalate to trap in v1. -- 2.47.2 From fbe4d0cac05752f940ecf4b6d261694230395b7a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:53:31 +0100 Subject: [PATCH 6/9] implements PLN-0171 variable glyph palette test coverage --- crates/console/prometeu-drivers/src/asset.rs | 23 +++++++++++++++++++ crates/tools/pbxgen-stress/src/lib.rs | 13 ++++++----- discussion/index.ndjson | 2 +- ...palette-tests-fixtures-and-residue-scan.md | 3 ++- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index eaf9b204..5a1e9771 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -2184,6 +2184,29 @@ mod tests { ); } + #[test] + fn test_decode_glyph_bank_rejects_mismatched_variable_serialized_size() { + let mut entry = test_glyph_asset_entry_with_palette_count("glyphs", 16, 16, 3); + entry.size += 1; + + let err = match AssetManager::decode_glyph_bank_from_buffer( + &entry, + &test_glyph_asset_data_with_palette_count(3), + ) { + Ok(_) => panic!("glyph decode should reject invalid serialized size"), + Err(err) => err, + }; + + assert_eq!( + err, + format!( + "Invalid GLYPHBANK serialized size: expected {}, got {}", + expected_glyph_payload_size(16, 16, 3), + expected_glyph_payload_size(16, 16, 3) + 1 + ) + ); + } + #[test] fn test_op_mode_for_glyphs_none_stages_in_memory() { let entry = test_glyph_asset_entry("glyphs", 16, 16); diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index 11b8bee1..62c9d56f 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -14,7 +14,7 @@ use prometeu_hal::cartridge::{ use prometeu_hal::color::Color; use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph_bank::{ - TileSize, GLYPH_BANK_COLORS_PER_PALETTE, GLYPH_BANK_PALETTE_COUNT_V1, + TileSize, GLYPH_BANK_COLORS_PER_PALETTE, GLYPH_BANK_MAX_PALETTE_COUNT_V1, }; use prometeu_hal::scene_bank::SceneBank; use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; @@ -275,15 +275,16 @@ fn build_glyph_asset() -> (AssetEntry, Vec) { bank_type: BankType::GLYPH, offset: 0, size: payload.len() as u64, - decoded_size: (8 * 8 + GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 4) + decoded_size: (8 * 8 + + GLYPH_BANK_MAX_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 4) as u64, codec: AssetCodec::None, metadata: serde_json::json!({ "tile_size": 8, "width": 8, "height": 8, - "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 }), }; @@ -292,8 +293,8 @@ fn build_glyph_asset() -> (AssetEntry, Vec) { fn build_palette_bytes() -> Vec { let mut bytes = - Vec::with_capacity(GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 4); - for palette_id in 0..GLYPH_BANK_PALETTE_COUNT_V1 { + Vec::with_capacity(GLYPH_BANK_MAX_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 4); + for palette_id in 0..GLYPH_BANK_MAX_PALETTE_COUNT_V1 { for color_index in 0..GLYPH_BANK_COLORS_PER_PALETTE { let color = if color_index == 1 { stress_color(palette_id) } else { Color::BLACK }; bytes.extend_from_slice(&color.raw().to_be_bytes()); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 08a69a3c..58b108ed 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":"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":"done","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":"done","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":"done","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":"done","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":"done","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-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md b/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md index 2d75faca..2aa5a2d8 100644 --- a/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md +++ b/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md @@ -2,7 +2,8 @@ id: PLN-0171 ticket: runtime-owned-variable-glyph-bank-palette-protocol title: Variable Glyph Palette Tests Fixtures and Residue Scan -status: open +status: done +completed: 2026-07-14 created: 2026-07-14 ref_decisions: [DEC-0041] tags: [runtime, tests, fixtures, glyph-bank, palette-serialization] -- 2.47.2 From e2a5a67621a193971f906cb2976486befd2df8ac Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 15:54:28 +0100 Subject: [PATCH 7/9] implements PLN-0172 runtime glyph palette handoff --- discussion/index.ndjson | 2 +- ...ntime-spec-handoff-to-packer-and-studio.md | 50 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 58b108ed..fd1c724a 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":"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":"done","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":"done","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":"done","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":"done","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":"done","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":"done","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":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md b/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md index 95ccf160..e6eaf69f 100644 --- a/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md +++ b/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md @@ -2,7 +2,8 @@ id: PLN-0172 ticket: runtime-owned-variable-glyph-bank-palette-protocol title: Runtime Spec Handoff to Packer and Studio -status: open +status: done +completed: 2026-07-14 created: 2026-07-14 ref_decisions: [DEC-0041] tags: [runtime, packer, studio, handoff, glyph-bank, palette-serialization] @@ -110,3 +111,50 @@ to lessons. - Downstream repositories may still treat `metadata.palette_authored` as authoritative; this plan must make runtime-effective metadata explicit. - Creating handoff text too early could freeze pre-implementation details. + +## Handoff Note + +Runtime is the protocol authority for `GLYPH/indexed_v1`. Downstream packer and +studio work must follow the published runtime specs, not the historical studio +agenda. + +Canonical runtime references: + +- `docs/specs/runtime/15-asset-management.md`, section `4.1 GLYPH asset + contract in v1` +- `docs/specs/runtime/04-gfx-peripheral.md`, section `19.4 Palette + Association` +- `docs/specs/runtime/04-gfx-peripheral.md`, section `20.1.b Scene dependency + fatal boundary` +- `docs/specs/runtime/04-gfx-peripheral.md`, section `20.2 + composer.emit_sprite` + +Downstream producers must emit: + +- `codec = NONE` +- packed `u4` indexed pixels +- RGBA8888 palette bytes in `R, G, B, A` order +- root effective metadata fields `tile_size`, `width`, `height`, and + `palette_count` +- `palette_count` in `1..=64` +- exactly `palette_count * 16 * 4` palette bytes +- `size = ceil(width * height / 2) + palette_count * 16 * 4` +- `decoded_size = width * height + palette_count * 16 * 4` + +Downstream producers must not emit: + +- sparse-to-dense palette remapping metadata for v1 +- a `GLYPH/indexed_v2` payload for this change +- fixed 64-palette padding unless `palette_count = 64` and the payload + directly satisfies the runtime v1 contract +- effective runtime behavior based on `palette_authored`; that field is + informative unless a later runtime spec promotes it to an effective root + metadata field + +Required downstream fixture coverage: + +- one glyph bank with `palette_count = 1` +- one glyph bank with an intermediate `palette_count` +- one glyph bank with `palette_count = 64` +- payload-size and decoded-size assertions for each fixture +- a negative fixture or validation case for metadata/payload mismatch -- 2.47.2 From 7cced0709b1b56230ffd9b684b39fdd10a28dace Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 16:08:17 +0100 Subject: [PATCH 8/9] housekeep DSC-0046 variable glyph palette protocol --- discussion/index.ndjson | 4 +- ...must-align-payload-residency-and-lookup.md | 97 ++++++++ ...ed-variable-glyph-bank-palette-protocol.md | 220 ------------------ ...41-variable-glyph-bank-palette-protocol.md | 202 ---------------- ...ract-update-for-variable-glyph-palettes.md | 116 --------- ...yphbank-variable-palette-resident-model.md | 119 ---------- ...-validation-for-variable-glyph-palettes.md | 113 --------- ...ser-palette-reference-failure-semantics.md | 126 ---------- ...palette-tests-fixtures-and-residue-scan.md | 115 --------- ...ntime-spec-handoff-to-packer-and-studio.md | 160 ------------- 10 files changed, 99 insertions(+), 1173 deletions(-) create mode 100644 discussion/lessons/DSC-0046-runtime-owned-variable-glyph-bank-palette-protocol/LSN-0055-runtime-owned-asset-protocols-must-align-payload-residency-and-lookup.md delete mode 100644 discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md delete mode 100644 discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md delete mode 100644 discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md delete mode 100644 discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md delete mode 100644 discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md delete mode 100644 discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md delete mode 100644 discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md delete mode 100644 discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md diff --git a/discussion/index.ndjson b/discussion/index.ndjson index fd1c724a..5ada6974 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,4 +1,4 @@ -{"type":"meta","next_id":{"DSC":47,"AGD":50,"DEC":42,"PLN":173,"LSN":55,"CLSN":1}} +{"type":"meta","next_id":{"DSC":47,"AGD":50,"DEC":42,"PLN":173,"LSN":56,"CLSN":1}} {"type":"discussion","id":"DSC-0044","status":"done","ticket":"hub-suspended-game-kill-affordance","title":"Hub Suspended Game Kill Affordance","created_at":"2026-07-05","updated_at":"2026-07-05","tags":["hub","lifecycle","game","ui"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0054","file":"discussion/lessons/DSC-0044-hub-suspended-game-kill-affordance/LSN-0054-manual-hub-kill-must-share-game-termination-cleanup.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]} {"type":"discussion","id":"DSC-0043","status":"done","ticket":"system-os-cartridge-switch-orchestrator","title":"SystemOS Cartridge Switch Orchestrator","created_at":"2026-07-03","updated_at":"2026-07-05","tags":["runtime","os","lifecycle","game","cartridge","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0043-system-os-cartridge-switch-orchestrator/LSN-0053-game-switching-is-lifecycle-replacement-not-loader-work.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]} {"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."} @@ -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":"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":"done","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":"done","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":"done","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":"done","created_at":"2026-07-14","updated_at":"2026-07-14","ref_decisions":["DEC-0041"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0046","status":"done","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":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0055","file":"discussion/lessons/DSC-0046-runtime-owned-variable-glyph-bank-palette-protocol/LSN-0055-runtime-owned-asset-protocols-must-align-payload-residency-and-lookup.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]} diff --git a/discussion/lessons/DSC-0046-runtime-owned-variable-glyph-bank-palette-protocol/LSN-0055-runtime-owned-asset-protocols-must-align-payload-residency-and-lookup.md b/discussion/lessons/DSC-0046-runtime-owned-variable-glyph-bank-palette-protocol/LSN-0055-runtime-owned-asset-protocols-must-align-payload-residency-and-lookup.md new file mode 100644 index 00000000..57708be9 --- /dev/null +++ b/discussion/lessons/DSC-0046-runtime-owned-variable-glyph-bank-palette-protocol/LSN-0055-runtime-owned-asset-protocols-must-align-payload-residency-and-lookup.md @@ -0,0 +1,97 @@ +--- +id: LSN-0055 +ticket: runtime-owned-variable-glyph-bank-palette-protocol +title: Runtime-Owned Asset Protocols Must Align Payload, Residency, and Lookup +created: 2026-07-14 +tags: [runtime, assets, glyph-bank, palette-serialization, protocol] +decision: DEC-0041 +--- + +## Context + +The runtime changed the `GLYPH/indexed_v1` glyph-bank palette contract from a +fixed 64-palette payload and resident table to variable palette serialization +and variable resident palette storage. + +The initiating pressure came from packer/studio: fixed RGBA8888 palette padding +made small glyph banks pay for `64 * 16 * 4` palette bytes even when they used +far fewer palettes. The important architectural point was that the runtime, not +the packer, had to decide the protocol. The packer can only emit payloads that +conform to the runtime contract once the runtime spec is published. + +## Key Decisions + +### Variable Glyph Bank Palette Protocol + +**What:** +`palette_count` became the number of serialized palettes and the number of +resident palettes in the loaded `GlyphBank`. The valid range is `1..=64`. +`palette_id` remains a direct palette identity and is valid only when +`palette_id < palette_count` for the referenced resident bank. + +**Why:** +Keeping serialized count variable while retaining a fixed 64-slot resident +table would preserve two meanings for palette capacity. The runtime would save +payload bytes but still carry a split contract between payload shape, resident +shape, and lookup validity. Making all three agree gives the runtime one +source of truth. + +**Trade-offs:** +Palette validity is now bank-dependent. Scene and sprite composition must check +the referenced loaded bank instead of relying on a global `0..63` rule. This +adds validation work, but it prevents invalid references from silently becoming +transparent or default colors. + +## Patterns and Algorithms + +Let the runtime own runtime-facing asset protocols. Tooling can discover pain, +but the runtime spec must define effective metadata, payload shape, resident +shape, and failure semantics. + +Make count fields material. A count such as `palette_count` should not describe +only what the producer authored or only what the payload happens to include. If +runtime lookup depends on it, the count must also define resident state and +validation boundaries. + +Keep identity direct unless a real remapping problem exists. Sparse-to-dense +palette remapping was rejected because it would add a second identity layer +across scenes, sprites, packer output, and runtime lookup. V1 keeps palette id +`N` as palette slot `N` in the resident bank. + +Separate transparent color from invalid lookup. RGBA alpha is valid color data. +An invalid palette reference must be observable as invalid; it must not be +collapsed into `Color::TRANSPARENT`. + +Use residue scans after protocol migrations. Search for fixed byte formulas, +old constants, fixture helpers, and spec phrases. Legitimate hits should be +renamed as maximum-bound concepts, not left as accidental active contracts. + +## Pitfalls + +Partially variable specs are worse than fixed specs. The asset spec already had +variable size formulas in some places but still required `palette_count = 64` +elsewhere. That contradiction made it unclear which rule was canonical. + +Resident data structures can preserve obsolete protocol assumptions after the +payload changes. A fixed array in `GlyphBank` would have kept the old model +alive even if decode accepted variable payloads. + +Fallback rendering hides contract errors. Returning transparent for an invalid +palette id makes bad assets and bad scene references look like intentional +alpha, which is exactly the wrong failure mode for a runtime protocol. + +Fixture generators are part of the contract surface. Stress cartridges and +test payload builders must express whether they are using a variable count or +the v1 maximum; otherwise they reintroduce fixed-padding assumptions. + +## Takeaways + +- Runtime-facing asset protocols should align payload size, resident memory, + and lookup validity. +- `palette_count` is effective runtime metadata, not producer commentary. +- Direct identity is simpler than remapping until a concrete remapping need + exists. +- Invalid palette references should fail explicitly; transparent remains a + valid RGBA color, not an error substitute. +- Protocol migrations need spec edits, code changes, fixture updates, and + residue scans in the same workflow. diff --git a/discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md b/discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md deleted file mode 100644 index 3b87a324..00000000 --- a/discussion/workflow/agendas/AGD-0049-runtime-owned-variable-glyph-bank-palette-protocol.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -id: AGD-0049 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Runtime-Owned Variable Glyph Bank Palette Protocol -status: accepted -created: 2026-07-14 -resolved: 2026-07-14 -decision: -tags: - - runtime - - gfx - - assets - - glyph-bank - - palette-serialization - - protocol ---- - -## Contexto - -A agenda `../studio/discussion/workflow/agendas/AGD-0005-variable-tile-bank-palette-serialization.md` -levantou o desperdicio do payload atual de `GLYPH/indexed_v1`: todo glyph bank -carrega `64 * 16 * 4 = 4096` bytes de paletas RGBA8888, mesmo quando o asset -usa poucas paletas. - -Esse problema toca o `packer`, mas a decisao do protocolo pertence ao -`runtime`. A especificacao final deve ser definida aqui, no contrato runtime, -e o `packer` deve seguir a spec quando ela estiver pronta. A agenda de `studio` -serve como entrada factual e motivacao, nao como autoridade normativa sobre o -payload, a memoria residente ou a semantica de `palette_id`. - -Estado atual no runtime: - -- `docs/specs/runtime/15-asset-management.md` documenta `palette_count = 64`; -- `docs/specs/runtime/04-gfx-peripheral.md` documenta `palette_id` em `0..63`; -- `crates/console/prometeu-hal/src/glyph_bank.rs` materializa paletas como - `[[Color; 16]; 64]`; -- `crates/console/prometeu-drivers/src/asset.rs` rejeita metadata cujo - `palette_count` nao seja `64`; -- o decode de glyph bank le um bloco fixo de `64 * 16 * 4` bytes em - `assets.pa`; -- testes de asset e VM ainda calculam payload e decoded size com o bloco fixo. - -Decisoes anteriores relevantes: - -- `DSC-0022` estabeleceu `GlyphBank` como nome canonico do artefato grafico; -- `DSC-0037` estabeleceu RGBA8888 como contrato runtime, com alpha como dado de - cor e sem indice magico reservado. - -## Problema - -O runtime precisa decidir se `GLYPH/indexed_v1` continua sendo um contrato de -64 paletas fixas ou se passa a aceitar uma quantidade variavel de paletas -serializadas. - -Essa decisao nao e apenas uma otimizacao de tamanho de cartucho. Ela muda: - -- o significado de `palette_count`; -- a validacao de `palette_id`; -- a formula de `size` e `decoded_size`; -- a representacao residente de `GlyphBank`; -- o comportamento quando uma cena referencia uma paleta nao carregada; -- a fronteira entre contrato de asset, HAL, composer, testes e tooling. - -## Pontos Criticos - -- Autoridade: o runtime decide o protocolo publicado; o `packer` segue. -- Compatibilidade: o projeto ainda esta em v1, entao nao ha obrigacao - presumida de preservar o padding fixo antigo. -- Identidade: `palette_id` pode continuar sendo uma identidade direta ou pode - virar indice em uma tabela compactada, mas nao pode ter dois significados. -- Materializacao: economizar bytes no payload nao implica automaticamente - economizar memoria residente, mas manter uma tabela fixa pode preservar - semantica simples para renderizacao e cenas. -- Falhas: se `palette_id` apontar para uma paleta ausente, o runtime precisa - definir se isso falha no load, na composicao, ou se resolve para cor default. -- Propagacao: qualquer decisao precisa mover specs, decode, HAL, fixtures e - testes juntos, como ocorreu na migracao RGBA8888. - -## Opcoes - -### Opcao A - Manter 64 paletas fixas no protocolo runtime - -- **Abordagem:** Preservar `palette_count = 64`, bloco fixo de 4096 bytes e - tabela residente fixa. -- **Pro:** Mantem decode, composicao e limites de `palette_id` simples. -- **Contra:** O runtime continua publicando um custo obrigatorio de payload que - nao representa a maioria dos assets. -- **Manutencao:** Boa para estabilidade local, fraca para alinhamento entre - metadata autoral e payload real. - -### Opcao B - Payload variavel, memoria residente fixa de 64 slots - -- **Abordagem:** Fazer `palette_count` representar o numero de paletas - serializadas, mas expandir para uma tabela residente fixa de 64 paletas no - load. Paletas nao serializadas ficam em valor default, e `palette_id` continua - sendo identidade direta `0..63`. -- **Pro:** Reduz payload mantendo o contrato de composicao e cena simples. -- **Contra:** Requer decidir se referencias a paletas nao serializadas sao - invalidas no load, invalidas na composicao ou simplesmente resolvem para a - paleta default. -- **Manutencao:** Boa se a spec declarar que `palette_count` mede somente o - prefixo serializado e que o runtime ainda materializa capacidade fixa. - -### Opcao C - Payload variavel e memoria residente variavel - -- **Abordagem:** Fazer `palette_count` controlar tanto o payload quanto a - quantidade residente de paletas. `palette_id >= palette_count` passa a ser - invalido para aquele glyph bank. -- **Pro:** O contrato fica mais fiel ao asset carregado e reduz memoria - residente por banco. -- **Contra:** A composicao precisa carregar limite por banco e transformar - validacao de `palette_id` em regra dependente do asset. -- **Manutencao:** Forte se o runtime quiser que o banco carregado seja a unica - fonte de verdade; mais invasiva no HAL/composer. - -### Opcao D - Paletas compactadas com remapeamento de identidade - -- **Abordagem:** Serializar somente paletas usadas em uma tabela densa e - introduzir um mapa entre identidade autoral/esparsa e indice residente. -- **Pro:** Pode minimizar payload mesmo quando indices autorais sao esparsos. -- **Contra:** Introduz um segundo contrato de identidade e exige metadata extra - ou reescrita de cenas/assets dependentes. -- **Manutencao:** Fraca para v1 unless haja uma necessidade clara de preservar - indices esparsos sem preencher lacunas. - -## Sugestao / Recomendacao - -Adotar a **Opcao C**. - -`palette_count` deve ser o contrato real do glyph bank carregado: se o payload -serializa `N` paletas, o runtime materializa `N` paletas e `palette_id >= N` -e invalido para aquele banco. - -A **Opcao B** fica descartada porque manter memoria residente fixa preservaria -uma segunda nocao de capacidade que nao corresponde ao asset carregado. A -**Opcao D** tambem fica descartada porque introduzir remapeamento de identidade -transformaria um protocolo simples em uma camada adicional de metadata. - -## Perguntas em Aberto - -- [x] `palette_count` deve definir tambem a quantidade residente de paletas, ou - apenas o prefixo serializado no payload? - - Resolucao: define tambem a quantidade residente. -- [x] `palette_id` deve ser validado contra `palette_count` por glyph bank ou - continuar limitado globalmente a `0..63`? - - Resolucao: validar contra `palette_count` por glyph bank, mantendo um - limite maximo global separado. -- [x] Quando uma cena referencia uma paleta ausente, o erro deve ocorrer no - load do glyph bank, no load/decode da scene, ou na composicao? - - Resolucao: o glyph bank carrega se o proprio payload e valido. A referencia - ausente falha quando uma scene/sprite tenta usar `palette_id >= - palette_count` contra o banco real; o runtime deve tratar isso como erro - explicito, nao como fallback silencioso para transparente/default. -- [x] O limite maximo de paletas por glyph bank continua sendo `64` em v1? - - Resolucao: sim. A variabilidade deve reduzir `N`, nao abrir uma - quantidade ilimitada de paletas em v1. -- [x] `decoded_size` deve contar apenas paletas materializadas ou manter alguma - nocao de capacidade residente? - - Resolucao: contar apenas paletas materializadas: - `width * height + palette_count * 16 * 4`. -- [x] A mudanca deve manter o nome `GLYPH/indexed_v1` como correcao - incompativel de v1, ou o runtime exige uma nova versao de payload? - - Resolucao: manter `GLYPH/indexed_v1` como correcao incompativel, sem - compatibilidade com o padding fixo antigo. - -## Criterio para Encerrar - -A agenda pode virar decisao quando o runtime escolher: - -- o significado normativo de `palette_count`; -- a relacao entre payload serializado e memoria residente; -- a regra de validade de `palette_id`; -- o ponto de falha para paletas referenciadas mas ausentes; -- a estrategia de versao para `GLYPH/indexed_v1`; -- a lista de specs, crates e testes que precisam ser propagados no plano. - -## Discussion - -Entrada do usuario em 2026-07-14: o runtime deve decidir o protocolo com base -no que for melhor para o runtime; o `packer` deve seguir a spec quando pronta. - -Entrada do usuario em 2026-07-14: preferencia pela Opcao C, se possivel. - -Analise atual: a Opcao C e viavel, mas transforma `palette_id` em validacao -dependente do glyph bank carregado. O codigo atual ja centraliza parte desse -risco em `GlyphBank::resolve_color`, que hoje retorna transparente quando a -paleta nao existe. Para a Opcao C virar contrato robusto, a decisao nao deve -depender de fallback silencioso: precisa escolher um ponto explicito de falha -para `palette_id >= palette_count`. - -Entrada do usuario em 2026-07-14: aceitar a recomendacao e seguir com a Opcao -C. - -## Resolution - -Fechar a agenda em favor da **Opcao C - Payload variavel e memoria residente -variavel**. - -Contrato a levar para decisao: - -- o runtime e a autoridade do protocolo `GLYPH/indexed_v1`; -- o `packer` deve seguir a spec runtime quando ela estiver publicada; -- `palette_count` significa o numero de paletas RGBA8888 serializadas e - materializadas no glyph bank carregado; -- `palette_count` deve estar no intervalo `1..=64`; -- cada paleta continua tendo 16 cores RGBA8888; -- `palette_id` e uma identidade direta dentro do glyph bank carregado, valida - somente quando `palette_id < palette_count`; -- o runtime nao deve remapear paletas esparsas para outra identidade; -- `decoded_size` deve contar apenas as paletas materializadas: - `width * height + palette_count * 16 * 4`; -- a mudanca permanece em `GLYPH/indexed_v1` como correcao incompativel de v1; -- payloads antigos com padding fixo e `palette_count = 64` so continuam - validos se tambem satisfizerem o novo contrato por coincidencia, nao por uma - regra de compatibilidade separada; -- referencias a paletas ausentes devem falhar explicitamente quando - scene/sprite/composer tentarem usar `palette_id >= palette_count` contra o - banco real; -- o plano posterior deve propagar specs, `GlyphBank`, decode de assets, - validacao de scene/sprite/composer, fixtures e testes. diff --git a/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md b/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md deleted file mode 100644 index 53e06b57..00000000 --- a/discussion/workflow/decisions/DEC-0041-variable-glyph-bank-palette-protocol.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -id: DEC-0041 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Variable Glyph Bank Palette Protocol -status: accepted -created: 2026-07-14 -accepted: 2026-07-14 -ref_agenda: AGD-0049 -tags: [runtime, gfx, assets, glyph-bank, palette-serialization, protocol] ---- - -## Status - -Accepted. - -## Contexto - -`AGD-0049` resolved that the runtime must own the `GLYPH/indexed_v1` palette -serialization protocol. `studio` and `packer` may motivate the change, but -they do not define the runtime wire contract. Once this decision is accepted, -the packer must follow the published runtime spec. - -The current runtime still contains fixed-palette assumptions: - -- `docs/specs/runtime/15-asset-management.md` says `palette_count = 64` and - validates `palette_count` as exactly `64`; -- `docs/specs/runtime/04-gfx-peripheral.md` documents `palette_id` as a - runtime-facing palette index; -- `crates/console/prometeu-hal/src/glyph_bank.rs` stores palettes as - `[[Color; 16]; 64]`; -- `crates/console/prometeu-drivers/src/asset.rs` rejects any glyph metadata - whose `palette_count` is not `64`; -- glyph decode reads a fixed `64 * 16 * 4` byte palette block; -- tests and fixtures calculate glyph payload and decoded size with the fixed - palette block. - -Prior decisions remain in force: - -- `DSC-0022` established `GlyphBank` as the canonical artifact name; -- `DSC-0037` established RGBA8888 as the runtime color contract, with alpha as - color data and no reserved magic palette index. - -## Decisao - -The runtime SHALL adopt variable glyph-bank palette serialization for -`GLYPH/indexed_v1`. - -For `BankType::GLYPH` in v1: - -- `palette_count` MUST mean the number of RGBA8888 palettes serialized in the - payload and materialized in the resident `GlyphBank`; -- `palette_count` MUST be in the inclusive range `1..=64`; -- each palette MUST contain exactly `16` RGBA8888 colors in canonical `R, G, B, - A` byte order; -- the serialized payload MUST contain only `palette_count` palettes, not a - fixed 64-palette block; -- resident runtime memory MUST materialize exactly `palette_count` palettes for - the bank; -- `palette_id` MUST be interpreted as a direct palette identity within the - loaded glyph bank; -- `palette_id` MUST be valid only when `palette_id < palette_count` for the - loaded glyph bank being referenced; -- the runtime MUST NOT remap sparse authored palette identities into a separate - dense identity space; -- missing palettes MUST NOT silently resolve to transparent, black, or any - other default color in canonical scene/sprite composition; -- references to `palette_id >= palette_count` MUST fail explicitly when - scene/sprite/composer logic attempts to use that palette against the loaded - bank; -- the format name remains `GLYPH/indexed_v1`; this is an incompatible v1 - correction, not a new v2 format. - -The v1 payload size formulas SHALL be: - -```text -serialized_pixel_bytes = ceil(width * height / 2) -palette_bytes = palette_count * 16 * 4 -size = serialized_pixel_bytes + palette_bytes -decoded_size = (width * height) + palette_bytes -``` - -`width * height` is the number of logical indexed pixels. Serialized pixels -remain packed `u4`; decoded runtime pixels may remain expanded to one `u8` -index per pixel. - -## Rationale - -This makes `palette_count` a real runtime contract instead of a decorative -metadata field. If a glyph bank carries `N` palettes, runtime storage, payload -validation, `decoded_size`, and palette lookup all agree on `N`. - -Keeping a fixed 64-slot resident table while trimming only the payload would -preserve two meanings for palette capacity: serialized count and resident -capacity. That would keep the ambiguity this decision is intended to remove. - -Adding sparse-to-dense palette remapping is also rejected. It would introduce a -second identity layer and force scenes, sprites, packer output, and runtime -lookup to coordinate additional metadata. V1 should keep `palette_id` as a -direct identity inside the loaded glyph bank. - -Keeping the name `GLYPH/indexed_v1` is acceptable because the project is still -in v1 and there is no required compatibility owner for the old fixed-padding -payload. The runtime should correct the v1 contract now instead of publishing a -second format solely to remove padding. - -## Invariantes / Contrato - -- The runtime is the protocol authority for `GLYPH/indexed_v1`. -- The packer is a producer of runtime-conforming payloads, not the source of - truth for the protocol. -- RGBA8888 remains the only valid glyph palette color encoding. -- Palette index `0` remains an ordinary palette index. -- Color index `0` remains an ordinary color index inside a palette. -- Transparency is represented by the RGBA alpha channel. -- `palette_count` is both serialized palette count and resident palette count. -- `palette_count` has a maximum of `64` in v1. -- `palette_id` validity is bank-dependent: `palette_id < palette_count`. -- No compatibility mode for the old fixed 64-palette padding is introduced. -- Old payloads with `palette_count = 64` remain valid only if they satisfy the - new contract directly, not because of a special legacy branch. - -## Impactos - -### Specs - -- `docs/specs/runtime/15-asset-management.md` must remove the exact - `palette_count = 64` requirement and define `palette_count` as `1..=64`. -- The same spec must align `size` and `decoded_size` formulas with variable - `palette_count`. -- `docs/specs/runtime/04-gfx-peripheral.md` must define `palette_id` validity - as dependent on the loaded glyph bank's `palette_count`, not as a standalone - global `0..63` acceptance rule. -- Any public wording that suggests fixed resident `64` palettes per glyph bank - must be updated or marked historical. - -### Runtime Code - -- `GlyphBank` must stop exposing a fixed `[[Color; 16]; 64]` resident palette - table as the canonical representation. -- Glyph decode must accept `palette_count` in `1..=64`. -- Glyph decode must read exactly `palette_count * 16 * 4` palette bytes. -- Glyph decode must validate `size` and `decoded_size` using the variable - formulas. -- Palette lookup must expose enough information for scene/sprite/composer logic - to fail invalid `palette_id` explicitly instead of silently resolving a - default color. - -### Scene, Sprite, and Composer - -- Scene and sprite composition must treat `palette_id >= palette_count` for the - referenced glyph bank as an explicit invalid reference. -- The implementation plan must choose the concrete status/fault path for this - invalid reference using existing runtime error semantics where possible. -- Canonical composition must not continue by substituting transparent/default - colors for invalid palette references. - -### Firmware / Host / Tooling - -- Firmware/system surfaces that expose asset metadata or debug information must - report the variable `palette_count`. -- Packer/studio fixtures must emit runtime-conforming `GLYPH/indexed_v1` - payloads after the runtime spec is updated. -- Tooling must not generate sparse-to-dense remapping metadata for v1 unless a - later decision introduces that feature. - -### Tests - -- Tests must cover minimum and maximum valid palette counts: `1` and `64`. -- Tests must reject `palette_count = 0` and `palette_count > 64`. -- Tests must validate serialized and decoded size formulas for non-64 counts. -- Tests must verify palette bytes are read in RGBA order for variable counts. -- Tests must cover invalid `palette_id >= palette_count` behavior for scene or - sprite composition. -- Residue scans must check for fixed `64 * 16 * 4` payload assumptions that are - still active contract text or code. - -## Referencias - -- Agenda: `AGD-0049` -- Runtime naming precedent: `DSC-0022` -- RGBA8888 contract precedent: `DSC-0037` -- Spec target: `docs/specs/runtime/15-asset-management.md` -- Spec target: `docs/specs/runtime/04-gfx-peripheral.md` -- Code target: `crates/console/prometeu-hal/src/glyph_bank.rs` -- Code target: `crates/console/prometeu-drivers/src/asset.rs` - -## Propagacao Necessaria - -This decision must be followed by an executable plan before spec or code -changes. - -The plan must separate: - -- spec edits; -- runtime decode/materialization changes; -- scene/sprite/composer invalid-palette handling; -- tests and fixtures; -- downstream packer/studio alignment after the runtime spec is updated. - -## Revision Log - -- 2026-07-14: Initial decision draft from `AGD-0049`. diff --git a/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md b/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md deleted file mode 100644 index c048cf2b..00000000 --- a/discussion/workflow/plans/PLN-0167-spec-contract-update-for-variable-glyph-palettes.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -id: PLN-0167 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Spec Contract Update for Variable Glyph Palettes -status: done -completed: 2026-07-14 -created: 2026-07-14 -ref_decisions: [DEC-0041] -tags: [runtime, gfx, assets, glyph-bank, palette-serialization, protocol, specs] ---- - -## Briefing - -`DEC-0041` accepts variable glyph-bank palette serialization for -`GLYPH/indexed_v1`. The runtime specs must become the canonical source before -code and tooling are changed. - -## Decisions de Origem - -- `DEC-0041` - Variable Glyph Bank Palette Protocol - -## Alvo - -Publish the runtime contract for variable glyph-bank palettes in the canonical -runtime specs. - -## Escopo - -- Update `docs/specs/runtime/15-asset-management.md`. -- Update `docs/specs/runtime/04-gfx-peripheral.md`. -- Remove or reword active spec text that requires exactly 64 serialized or - resident palettes per glyph bank. -- Define `palette_count` as serialized and resident palette count. -- Define `palette_count` as `1..=64`. -- Define `palette_id` validity as `palette_id < palette_count` for the loaded - glyph bank. -- Preserve RGBA8888, alpha-as-data, and ordinary index `0` semantics. - -## Fora de Escopo - -- Runtime code changes. -- Packer or studio code changes. -- New `GLYPH/indexed_v2` format text. -- Sparse-to-dense palette remapping. - -## Plano de Execucao - -### Step 1 - Update asset metadata contract - -**What:** Change the `GLYPH` v1 metadata contract. - -**How:** In `docs/specs/runtime/15-asset-management.md`, replace the exact -`palette_count = 64` requirement with `palette_count` in `1..=64`. State that -the field is both serialized palette count and resident palette count. - -**Files:** `docs/specs/runtime/15-asset-management.md` - -### Step 2 - Update payload and decoded-size formulas - -**What:** Make payload size formulas variable. - -**How:** Define `palette_bytes = palette_count * 16 * 4`, then define -`size = ceil(width * height / 2) + palette_bytes` and -`decoded_size = width * height + palette_bytes`. - -**Files:** `docs/specs/runtime/15-asset-management.md` - -### Step 3 - Update GFX palette reference semantics - -**What:** Document bank-dependent `palette_id` validity. - -**How:** In `docs/specs/runtime/04-gfx-peripheral.md`, make scene and sprite -composition text state that `palette_id` is valid only when it is lower than -the referenced glyph bank's `palette_count`. - -**Files:** `docs/specs/runtime/04-gfx-peripheral.md` - -### Step 4 - Document explicit failure - -**What:** Remove fallback ambiguity for missing palettes. - -**How:** State that canonical composition must not substitute transparent, -black, or default colors for `palette_id >= palette_count`. - -**Files:** `docs/specs/runtime/04-gfx-peripheral.md`, -`docs/specs/runtime/15-asset-management.md` - -### Step 5 - Run spec residue scan - -**What:** Verify no active spec contradicts `DEC-0041`. - -**How:** Search specs for fixed palette phrases and validate remaining hits are -historical or explicitly bounded maximums. - -**Files:** `docs/specs/runtime/*.md` - -## Criterios de Aceite - -- [ ] `15-asset-management.md` defines `palette_count` as `1..=64`. -- [ ] `15-asset-management.md` uses variable size formulas. -- [ ] `04-gfx-peripheral.md` defines bank-dependent `palette_id` validity. -- [ ] Specs do not describe fixed 64 serialized palettes as the active - contract. -- [ ] No v2 format is introduced. - -## Tests / Validacao - -- Run `rg -n "palette_count|64 \\* 16|4096|palette_id" docs/specs/runtime`. -- Run `discussion validate`. - -## Riscos - -- The specs already contain mixed fixed and variable wording; partial edits may - preserve contradiction. -- `palette_id` failure semantics may overlap with existing scene dependency - fatal-failure text and must be worded consistently. 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 deleted file mode 100644 index 198d7f0c..00000000 --- a/discussion/workflow/plans/PLN-0168-glyphbank-variable-palette-resident-model.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -id: PLN-0168 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: GlyphBank Variable Palette Resident Model -status: done -completed: 2026-07-14 -created: 2026-07-14 -ref_decisions: [DEC-0041] -tags: [runtime, gfx, assets, glyph-bank, hal] ---- - -## Briefing - -`DEC-0041` requires resident `GlyphBank` memory to materialize exactly -`palette_count` palettes. The current HAL model exposes a fixed -`[[Color; 16]; 64]` table and must be changed before decode and composition can -enforce bank-dependent palette validity cleanly. - -## Decisions de Origem - -- `DEC-0041` - Variable Glyph Bank Palette Protocol - -## Alvo - -Replace the fixed resident palette table with a variable resident palette model -that preserves direct `palette_id` identity. - -## Escopo - -- Update `crates/console/prometeu-hal/src/glyph_bank.rs`. -- Keep `GLYPH_BANK_COLORS_PER_PALETTE = 16`. -- Replace the fixed palette table with a variable representation. -- Expose palette count and palette validation helpers. -- Ensure `resolve_color` no longer hides invalid palette references in - canonical paths. -- Update local HAL tests and call sites that construct `GlyphBank` directly. - -## Fora de Escopo - -- Asset payload decode changes. -- Composer status/fault policy. -- Packer/studio changes. -- Sparse palette remapping. - -## Plano de Execucao - -### Step 1 - Introduce variable palette storage - -**What:** Replace fixed palette storage. - -**How:** Change `GlyphBank::palettes` from -`[[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1]` to a -variable collection such as `Vec<[Color; GLYPH_BANK_COLORS_PER_PALETTE]>`. -Keep a separate maximum constant for v1 if useful. - -**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` - -### Step 2 - Update constructors - -**What:** Make constructors explicit about palette count. - -**How:** Add or update constructors so tests and loaders can create a bank with -a chosen palette count. Default constructors used only for empty test banks -must choose a valid count and not imply fixed 64 capacity. - -**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` - -### Step 3 - Add validation helpers - -**What:** Provide canonical palette validity APIs. - -**How:** Add methods such as `palette_count()`, `contains_palette(palette_id)`, -and a fallible color lookup that distinguishes invalid palette references from -transparent colors. - -**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` - -### Step 4 - Update direct palette mutations in tests - -**What:** Fix tests that write directly into fixed array slots. - -**How:** Replace direct `bank.palettes[id][color] = value` call sites with -helpers or ensure the test bank was created with enough palettes first. - -**Files:** `crates/console/prometeu-drivers/src/frame_composer.rs`, -`crates/console/prometeu-drivers/src/gfx.rs`, -`crates/console/prometeu-system/src/services/vm_runtime/tests.rs` - -### Step 5 - Keep direct identity - -**What:** Preserve `palette_id` identity. - -**How:** Do not introduce maps, compaction tables, or translated palette ids. -Index `N` in the vector is palette identity `N`. - -**Files:** `crates/console/prometeu-hal/src/glyph_bank.rs` - -## Criterios de Aceite - -- [ ] Resident `GlyphBank` stores exactly the loaded palette count. -- [ ] `GlyphBank` exposes bank-dependent palette validity. -- [ ] Invalid palette lookup can be detected separately from a transparent - color. -- [ ] No sparse-to-dense remapping structure is added. -- [ ] Direct test constructors no longer rely on implicit 64 palette capacity. - -## Tests / Validacao - -- Run HAL and driver unit tests that cover glyph bank construction and color - lookup. -- Add focused tests for `palette_count = 1` and `palette_count = 64`. -- Add a test proving invalid palette lookup is not silently equivalent to - transparent color. - -## Riscos - -- Many tests directly mutate `palettes[id]`; the migration can be noisy. -- Existing renderer paths may rely on `resolve_color` returning transparent for - invalid ids, which conflicts with `DEC-0041`. diff --git a/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md b/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md deleted file mode 100644 index 5976ca85..00000000 --- a/discussion/workflow/plans/PLN-0169-asset-decode-validation-for-variable-glyph-palettes.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -id: PLN-0169 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Asset Decode Validation for Variable Glyph Palettes -status: done -completed: 2026-07-14 -created: 2026-07-14 -ref_decisions: [DEC-0041] -tags: [runtime, assets, glyph-bank, decode, validation] ---- - -## Briefing - -`DEC-0041` changes glyph-bank decode from a fixed 4096-byte palette block to a -variable `palette_count * 16 * 4` block. The asset manager is the runtime gate -that must reject malformed glyph payloads before residency. - -## Decisions de Origem - -- `DEC-0041` - Variable Glyph Bank Palette Protocol - -## Alvo - -Update glyph asset layout validation and decode to accept `palette_count` in -`1..=64` and materialize exactly that many palettes. - -## Escopo - -- Update `crates/console/prometeu-drivers/src/asset.rs`. -- Replace exact `palette_count == 64` validation. -- Replace fixed `GLYPH_BANK_PALETTE_BYTES_V1` usage in decode. -- Validate `size` and `decoded_size` using variable formulas. -- Read exactly `palette_count * 16 * 4` bytes. -- Preserve RGBA byte order. - -## Fora de Escopo - -- HAL storage changes except as required by `PLN-0168`. -- Composer invalid-reference behavior. -- Packer output changes. - -## Plano de Execucao - -### Step 1 - Change layout return data - -**What:** Carry palette count through layout validation. - -**How:** Update `decode_glyph_bank_layout` to return `palette_count` and -palette byte count along with tile size, dimensions, and serialized pixel byte -count. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs` - -### Step 2 - Replace exact palette-count validation - -**What:** Accept only the DEC-0041 range. - -**How:** Reject `palette_count = 0` and `palette_count > 64`; accept all values -in `1..=64`. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs` - -### Step 3 - Use variable size formulas - -**What:** Align entry validation with the spec. - -**How:** Compute `palette_bytes = palette_count * 16 * size_of::()`, -`serialized_size = packed_pixels + palette_bytes`, and -`decoded_size = logical_pixels + palette_bytes`. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs` - -### Step 4 - Decode variable palette data - -**What:** Read only materialized palettes. - -**How:** In buffer and reader decode paths, slice/read exactly `palette_bytes`. -Populate the new variable `GlyphBank` palette representation in order. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs` - -### Step 5 - Remove fixed-byte assumptions - -**What:** Retire the fixed payload block from active decode. - -**How:** Replace helper functions and tests that assume -`GLYPH_BANK_PALETTE_BYTES_V1` is always in the payload. Keep a maximum constant -only if it is named as a maximum, not a payload size. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs` - -## Criterios de Aceite - -- [ ] Decode accepts valid `palette_count` values from `1` through `64`. -- [ ] Decode rejects `palette_count = 0`. -- [ ] Decode rejects `palette_count > 64`. -- [ ] `size` and `decoded_size` validation use variable palette bytes. -- [ ] Buffer and reader decode paths behave consistently. -- [ ] RGBA channel order remains unchanged. - -## Tests / Validacao - -- Add unit tests for `palette_count = 1`, an intermediate count, and `64`. -- Add rejection tests for `0`, `65`, short palette data, oversized metadata - size, and mismatched `decoded_size`. -- Run the crate tests that cover asset manager glyph decode. - -## Riscos - -- Existing tests may use generated glyph payload helpers with fixed 64-palette - size. -- Reader and buffer paths can drift if only one path receives the variable-size - change. diff --git a/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md b/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md deleted file mode 100644 index 4a74f106..00000000 --- a/discussion/workflow/plans/PLN-0170-composer-palette-reference-failure-semantics.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -id: PLN-0170 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Composer Palette Reference Failure Semantics -status: done -completed: 2026-07-14 -created: 2026-07-14 -ref_decisions: [DEC-0041] -tags: [runtime, gfx, composer, scene, sprite, validation] ---- - -## Briefing - -`DEC-0041` requires invalid palette references to fail explicitly when -scene/sprite/composer logic uses `palette_id >= palette_count` against a loaded -glyph bank. Current color resolution can silently return transparent for -missing palettes, which is no longer canonical behavior. - -## Decisions de Origem - -- `DEC-0041` - Variable Glyph Bank Palette Protocol - -## Alvo - -Define and implement explicit runtime failure behavior for invalid palette -references in canonical composition paths. - -## Escopo - -- Inspect scene binding, scene composition, sprite emission, and render - resolution paths. -- Choose existing status/fault behavior where it fits the current ABI. -- Prevent canonical composition from substituting transparent/default color for - invalid palette references. -- Update tests for scene and sprite invalid palette references. - -## Fora de Escopo - -- Adding new public ABI status values unless existing statuses cannot represent - the error. -- Packer or scene authoring validation. -- Sparse palette remapping. - -## Plano de Execucao - -### Step 1 - Map current composition paths - -**What:** Identify where palette ids are consumed. - -**How:** Trace `composer.emit_sprite`, scene binding/composition, frame -composer packet creation, and software GFX resolution from `Glyph` to -`GlyphBank::resolve_color`. - -**Files:** `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`, -`crates/console/prometeu-drivers/src/frame_composer.rs`, -`crates/console/prometeu-drivers/src/gfx.rs`, -`crates/console/prometeu-hal/src/glyph_bank.rs` - -### Step 2 - Select explicit failure behavior - -**What:** Choose the runtime-visible failure route. - -**How:** Use existing semantics where possible: status-returning sprite calls -should use an existing invalid status if it accurately describes the failure; -scene dependency failures that occur during binding/composition should follow -the existing fatal dependency-failure model documented in the GFX spec. - -**Files:** `docs/specs/runtime/04-gfx-peripheral.md`, -`crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`, -`crates/console/prometeu-drivers/src/frame_composer.rs` - -### Step 3 - Validate sprite references - -**What:** Prevent invalid sprite palette references. - -**How:** When `emit_sprite` has access to the target glyph bank, reject -`palette_id >= palette_count` before the sprite is accepted for canonical -composition. - -**Files:** `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`, -`crates/console/prometeu-drivers/src/frame_composer.rs`, -`crates/console/prometeu-drivers/src/hardware.rs` - -### Step 4 - Validate scene references - -**What:** Prevent invalid scene palette references. - -**How:** During scene bind or scene composition, validate tile palette ids -against each referenced loaded glyph bank. Fail explicitly if a scene layer -references a palette not present in its glyph dependency. - -**Files:** `crates/console/prometeu-drivers/src/gfx.rs`, -`crates/console/prometeu-hal/src/scene_viewport_cache.rs`, -`crates/console/prometeu-hal/src/scene_viewport_resolver.rs` - -### Step 5 - Remove silent fallback from canonical paths - -**What:** Stop hiding invalid palette ids as transparent. - -**How:** Use fallible palette lookup in canonical render paths. Transparent is -valid only when produced by an actual RGBA palette entry with alpha `0`. - -**Files:** `crates/console/prometeu-drivers/src/gfx.rs`, -`crates/console/prometeu-hal/src/glyph_bank.rs` - -## Criterios de Aceite - -- [ ] Invalid sprite `palette_id` is rejected before canonical composition. -- [ ] Invalid scene tile `palette_id` fails explicitly against the loaded bank. -- [ ] Canonical render paths do not use invalid palette lookup as transparent. -- [ ] Valid transparent RGBA palette entries still render as transparent. -- [ ] The selected status/fault behavior is documented in the spec. - -## Tests / Validacao - -- Add sprite test for `palette_id == palette_count`. -- Add scene test for a tile palette id above the referenced bank's count. -- Add regression test proving alpha `0` in a valid palette still works. -- Run VM runtime and GFX driver tests. - -## Riscos - -- Some paths may not have easy access to the loaded glyph bank when accepting a - sprite packet. -- Existing ABI statuses may be less precise than a new status, but adding a new - status has wider compatibility cost. diff --git a/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md b/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md deleted file mode 100644 index 2aa5a2d8..00000000 --- a/discussion/workflow/plans/PLN-0171-variable-glyph-palette-tests-fixtures-and-residue-scan.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -id: PLN-0171 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Variable Glyph Palette Tests Fixtures and Residue Scan -status: done -completed: 2026-07-14 -created: 2026-07-14 -ref_decisions: [DEC-0041] -tags: [runtime, tests, fixtures, glyph-bank, palette-serialization] ---- - -## Briefing - -`DEC-0041` changes a cross-cutting asset contract. Tests and fixtures must be -updated as a dedicated pass so fixed 64-palette assumptions do not survive in -helpers, generated data, or residue. - -## Decisions de Origem - -- `DEC-0041` - Variable Glyph Bank Palette Protocol - -## Alvo - -Convert runtime tests and fixtures to exercise variable glyph palette counts -and prove fixed-padding assumptions are gone from active behavior. - -## Escopo - -- Update test helpers that build glyph payloads. -- Add minimum, maximum, and intermediate palette-count tests. -- Add invalid palette-count tests. -- Add invalid palette-reference tests once `PLN-0170` is implemented. -- Run residue scans for fixed palette payload assumptions. - -## Fora de Escopo - -- Production decode implementation. -- Production composer implementation. -- Packer/studio fixture generation. - -## Plano de Execucao - -### Step 1 - Inventory glyph payload helpers - -**What:** Find all runtime test helpers with fixed glyph palette sizes. - -**How:** Search for `GLYPH_BANK_PALETTE_COUNT_V1`, -`GLYPH_BANK_PALETTE_BYTES_V1`, `64 * 16 * 4`, `4096`, and helper names such as -`test_glyph_asset_data`. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs`, -`crates/console/prometeu-system/src/services/vm_runtime/tests.rs`, -`crates/console/prometeu-system/src/services/vm_runtime/tests_asset_bank.rs` - -### Step 2 - Create variable payload builders - -**What:** Make tests express palette count deliberately. - -**How:** Replace fixed helpers with helpers that accept `palette_count`, build -exactly `palette_count * 16 * 4` palette bytes, and compute matching `size` and -`decoded_size`. - -**Files:** Runtime test modules that construct glyph assets. - -### Step 3 - Add decode boundary coverage - -**What:** Prove valid and invalid counts. - -**How:** Add tests for `palette_count = 1`, an intermediate value, `64`, `0`, -and `65`. - -**Files:** `crates/console/prometeu-drivers/src/asset.rs` - -### Step 4 - Add composition coverage - -**What:** Prove invalid palette references fail. - -**How:** After `PLN-0170`, add scene and sprite tests where -`palette_id == palette_count` and confirm explicit failure behavior. - -**Files:** `crates/console/prometeu-drivers/src/gfx.rs`, -`crates/console/prometeu-drivers/src/frame_composer.rs`, -`crates/console/prometeu-system/src/services/vm_runtime/tests.rs` - -### Step 5 - Run residue scan - -**What:** Catch leftover active fixed-palette assumptions. - -**How:** Scan code, tests, and specs for fixed palette byte formulas. Keep only -maximum-bound constants and historical documentation. - -**Files:** `crates/`, `docs/specs/runtime/`, `discussion/` - -## Criterios de Aceite - -- [ ] Tests no longer need a fixed 4096-byte palette payload for every glyph - bank. -- [ ] Decode tests cover `1`, intermediate counts, `64`, `0`, and `65`. -- [ ] Composition tests cover invalid palette references. -- [ ] Residue scan finds no active fixed serialized palette block assumption. -- [ ] Historical or maximum-bound uses of `64` are clearly named. - -## Tests / Validacao - -- Run targeted crate tests for asset decode, GFX, frame composer, and VM asset - bank flows. -- Run `rg -n "4096|64 \\* 16|GLYPH_BANK_PALETTE_BYTES_V1|palette_count"` - against `crates`, `docs/specs/runtime`, and active discussion artifacts. -- Run `discussion validate`. - -## Riscos - -- Residue scans can produce legitimate hits for the v1 maximum of `64`; those - must be classified instead of mechanically removed. -- Test helper churn can obscure the behavioral assertions if not kept focused. diff --git a/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md b/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md deleted file mode 100644 index e6eaf69f..00000000 --- a/discussion/workflow/plans/PLN-0172-runtime-spec-handoff-to-packer-and-studio.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -id: PLN-0172 -ticket: runtime-owned-variable-glyph-bank-palette-protocol -title: Runtime Spec Handoff to Packer and Studio -status: done -completed: 2026-07-14 -created: 2026-07-14 -ref_decisions: [DEC-0041] -tags: [runtime, packer, studio, handoff, glyph-bank, palette-serialization] ---- - -## Briefing - -`DEC-0041` makes runtime the authority for `GLYPH/indexed_v1`. After the -runtime spec and implementation are updated, packer and studio must align as -downstream producers of runtime-conforming assets. - -## Decisions de Origem - -- `DEC-0041` - Variable Glyph Bank Palette Protocol - -## Alvo - -Prepare a clear runtime-owned handoff for packer/studio without moving protocol -authority out of the runtime repository. - -## Escopo - -- Summarize the accepted runtime contract for downstream repositories. -- Identify exact spec sections packer/studio must follow. -- Identify fixture and payload requirements. -- Capture any compatibility note needed for old fixed-padding payloads. -- Update runtime discussion artifacts with handoff status when implementation - is complete. - -## Fora de Escopo - -- Editing `../studio` or packer code from this plan. -- Reopening `AGD-0005` in `../studio`. -- Defining a v2 payload. -- Adding producer-specific runtime exceptions. - -## Plano de Execucao - -### Step 1 - Wait for runtime spec publication - -**What:** Use runtime specs as handoff source. - -**How:** Do not send or encode downstream requirements until `PLN-0167` has -landed. The published runtime spec is the contract. - -**Files:** `docs/specs/runtime/15-asset-management.md`, -`docs/specs/runtime/04-gfx-peripheral.md` - -### Step 2 - Write downstream contract summary - -**What:** Produce a concise handoff note. - -**How:** Summarize `palette_count`, payload layout, size formulas, -`palette_id` validity, absence of remapping, and incompatibility with the old -padding contract. - -**Files:** Runtime discussion plan or follow-up note as appropriate. - -### Step 3 - Identify downstream fixture updates - -**What:** Define what packer/studio fixtures must prove. - -**How:** Require fixtures with `palette_count = 1`, an intermediate count, and -`64`, plus rejection or regeneration of fixed-padding assumptions where the -metadata does not match the payload. - -**Files:** Handoff note only; actual downstream files are outside this repo. - -### Step 4 - Preserve runtime authority - -**What:** Prevent downstream divergence. - -**How:** State that packer/studio metadata such as authored palette count is -informative unless the runtime spec defines it as effective metadata. - -**Files:** Handoff note; runtime spec references. - -### Step 5 - Close loop after downstream acknowledgment - -**What:** Track completion. - -**How:** Once packer/studio work is done elsewhere, update discussion lessons or -housekeeping artifacts from the runtime side without adding new normative text -to lessons. - -**Files:** `discussion/lessons/` only after implementation is complete. - -## Criterios de Aceite - -- [ ] Runtime specs are updated before downstream handoff. -- [ ] Handoff states that runtime owns the protocol. -- [ ] Handoff lists exact payload formulas and validity rules. -- [ ] Handoff states that no sparse-to-dense remapping exists in v1. -- [ ] Handoff states that old fixed-padding payloads are not compatibility - inputs unless they satisfy the new contract directly. - -## Tests / Validacao - -- Verify handoff references the final spec sections, not agenda text. -- Verify no downstream instruction contradicts `DEC-0041`. -- Run `discussion validate`. - -## Riscos - -- Downstream repositories may still treat `metadata.palette_authored` as - authoritative; this plan must make runtime-effective metadata explicit. -- Creating handoff text too early could freeze pre-implementation details. - -## Handoff Note - -Runtime is the protocol authority for `GLYPH/indexed_v1`. Downstream packer and -studio work must follow the published runtime specs, not the historical studio -agenda. - -Canonical runtime references: - -- `docs/specs/runtime/15-asset-management.md`, section `4.1 GLYPH asset - contract in v1` -- `docs/specs/runtime/04-gfx-peripheral.md`, section `19.4 Palette - Association` -- `docs/specs/runtime/04-gfx-peripheral.md`, section `20.1.b Scene dependency - fatal boundary` -- `docs/specs/runtime/04-gfx-peripheral.md`, section `20.2 - composer.emit_sprite` - -Downstream producers must emit: - -- `codec = NONE` -- packed `u4` indexed pixels -- RGBA8888 palette bytes in `R, G, B, A` order -- root effective metadata fields `tile_size`, `width`, `height`, and - `palette_count` -- `palette_count` in `1..=64` -- exactly `palette_count * 16 * 4` palette bytes -- `size = ceil(width * height / 2) + palette_count * 16 * 4` -- `decoded_size = width * height + palette_count * 16 * 4` - -Downstream producers must not emit: - -- sparse-to-dense palette remapping metadata for v1 -- a `GLYPH/indexed_v2` payload for this change -- fixed 64-palette padding unless `palette_count = 64` and the payload - directly satisfies the runtime v1 contract -- effective runtime behavior based on `palette_authored`; that field is - informative unless a later runtime spec promotes it to an effective root - metadata field - -Required downstream fixture coverage: - -- one glyph bank with `palette_count = 1` -- one glyph bank with an intermediate `palette_count` -- one glyph bank with `palette_count = 64` -- payload-size and decoded-size assertions for each fixture -- a negative fixture or validation case for metadata/payload mismatch -- 2.47.2 From f784ccd7741edb62186c94ff4636b72635de9cb9 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 14 Jul 2026 16:16:05 +0100 Subject: [PATCH 9/9] Runtime-Owned Variable Glyph Bank Palette Protocol --- crates/console/prometeu-drivers/src/asset.rs | 29 +++++++------------ .../src/services/vm_runtime/tests.rs | 5 +--- crates/tools/pbxgen-stress/src/lib.rs | 3 +- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index 5a1e9771..aa033729 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -625,9 +625,8 @@ impl AssetManager { let serialized_size = serialized_pixel_bytes .checked_add(palette_bytes) .ok_or("GlyphBank serialized size overflow")?; - let decoded_size = logical_pixels - .checked_add(palette_bytes) - .ok_or("GlyphBank decoded size overflow")?; + let decoded_size = + logical_pixels.checked_add(palette_bytes).ok_or("GlyphBank decoded size overflow")?; if entry.size != serialized_size as u64 { return Err(format!( @@ -1213,11 +1212,9 @@ impl AssetManager { let logical_pixels = width * height; let packed_pixels = &buffer[0..packed_pixel_bytes]; let pixel_indices = Self::unpack_glyph_bank_pixels(packed_pixels, logical_pixels); - let palette_data = - &buffer[packed_pixel_bytes..packed_pixel_bytes + palette_bytes]; + let palette_data = &buffer[packed_pixel_bytes..packed_pixel_bytes + palette_bytes]; - let mut palettes = - vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count]; + let mut palettes = vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count]; for (p, pal) in palettes.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() { let offset = (p * 16 + c) * 4; @@ -1252,8 +1249,7 @@ impl AssetManager { .read_exact(&mut palette_data) .map_err(|_| "Buffer too small for GLYPHBANK".to_string())?; - let mut palettes = - vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count]; + let mut palettes = vec![[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; palette_count]; for (p, pal) in palettes.iter_mut().enumerate() { for (c, slot) in pal.iter_mut().enumerate() { let offset = (p * 16 + c) * 4; @@ -1903,10 +1899,7 @@ mod tests { fn test_glyph_asset_data_with_palette_count(palette_count: usize) -> Vec { let mut data = vec![0x11u8; 128]; - data.extend_from_slice(&vec![ - 0u8; - palette_count * GLYPH_BANK_PALETTE_BYTES_PER_PALETTE - ]); + data.extend_from_slice(&vec![0u8; palette_count * GLYPH_BANK_PALETTE_BYTES_PER_PALETTE]); data } @@ -2109,8 +2102,8 @@ mod tests { let from_buffer = AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("buffer decode"); let mut reader = std::io::Cursor::new(data); - let from_reader = - AssetManager::decode_glyph_bank_from_reader(&entry, &mut reader).expect("reader decode"); + let from_reader = AssetManager::decode_glyph_bank_from_reader(&entry, &mut reader) + .expect("reader decode"); assert_eq!(from_buffer.palette_count(), from_reader.palette_count()); assert_eq!(from_buffer.pixel_indices, from_reader.pixel_indices); @@ -2120,10 +2113,8 @@ mod tests { #[test] fn test_decode_glyph_bank_rejects_short_packed_buffer() { let entry = test_glyph_asset_entry("glyphs", 16, 16); - let data = vec![ - 0u8; - expected_glyph_payload_size(16, 16, GLYPH_BANK_MAX_PALETTE_COUNT_V1) - 1 - ]; + let data = + vec![0u8; expected_glyph_payload_size(16, 16, GLYPH_BANK_MAX_PALETTE_COUNT_V1) - 1]; let err = match AssetManager::decode_glyph_bank_from_buffer(&entry, &data) { Ok(_) => panic!("glyph decode should reject short buffer"), 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 4e81ca75..e23abe67 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -142,10 +142,7 @@ fn test_glyph_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry { fn test_glyph_asset_data() -> Vec { let mut data = - vec![ - 0x11u8; - test_glyph_payload_size(16, 16) - (GLYPH_BANK_MAX_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 } diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index 62c9d56f..9395b431 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -275,8 +275,7 @@ fn build_glyph_asset() -> (AssetEntry, Vec) { bank_type: BankType::GLYPH, offset: 0, size: payload.len() as u64, - decoded_size: (8 * 8 - + GLYPH_BANK_MAX_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 4) + decoded_size: (8 * 8 + GLYPH_BANK_MAX_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 4) as u64, codec: AssetCodec::None, metadata: serde_json::json!({ -- 2.47.2