From 98d2d81882bdfb3664e8cf6c65b931e472672f1a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 15 Apr 2026 08:42:46 +0100 Subject: [PATCH] adjustments over frame composer contract - agnostic tile size --- discussion/index.ndjson | 2 +- ...-all-scene-cache-and-camera-integration.md | 61 ++++++++++++++++++- ...-0014-frame-composer-render-integration.md | 19 ++++++ ...me-composer-core-and-hardware-ownership.md | 3 + ...9-scene-binding-camera-and-scene-status.md | 4 ++ ...020-cache-refresh-and-render-frame-path.md | 8 +++ ...ement-callsite-migration-and-regression.md | 5 ++ 7 files changed, 100 insertions(+), 2 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 8e8748d7..b8501bd6 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -18,7 +18,7 @@ {"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0025","status":"done","ticket":"scene-bank-and-viewport-cache-refactor","title":"Scene Bank and Viewport Cache Refactor","created_at":"2026-04-11","updated_at":"2026-04-14","tags":["gfx","tilemap","runtime","render"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md","status":"done","created_at":"2026-04-14","updated_at":"2026-04-14"}]} -{"type":"discussion","id":"DSC-0026","status":"open","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-14","tags":["gfx","runtime","render","camera","scene"],"agendas":[{"id":"AGD-0026","file":"workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14"}],"decisions":[{"id":"DEC-0014","file":"workflow/decisions/DEC-0014-frame-composer-render-integration.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_agenda":"AGD-0026"}],"plans":[{"id":"PLN-0017","file":"workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0018","file":"workflow/plans/PLN-0018-sprite-controller-and-frame-emission-model.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0019","file":"workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0020","file":"workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0021","file":"workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0026","status":"open","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-15","tags":["gfx","runtime","render","camera","scene"],"agendas":[{"id":"AGD-0026","file":"workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-15"}],"decisions":[{"id":"DEC-0014","file":"workflow/decisions/DEC-0014-frame-composer-render-integration.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-15","ref_agenda":"AGD-0026"}],"plans":[{"id":"PLN-0017","file":"workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-15","ref_decisions":["DEC-0014"]},{"id":"PLN-0018","file":"workflow/plans/PLN-0018-sprite-controller-and-frame-emission-model.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-14","ref_decisions":["DEC-0014"]},{"id":"PLN-0019","file":"workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-15","ref_decisions":["DEC-0014"]},{"id":"PLN-0020","file":"workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-15","ref_decisions":["DEC-0014"]},{"id":"PLN-0021","file":"workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md","status":"accepted","created_at":"2026-04-14","updated_at":"2026-04-15","ref_decisions":["DEC-0014"]}],"lessons":[]} {"type":"discussion","id":"DSC-0014","status":"open","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} diff --git a/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md b/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md index 1dfa1409..756daaea 100644 --- a/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md +++ b/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md @@ -4,7 +4,7 @@ ticket: render-all-scene-cache-and-camera-integration title: Agenda - Integrate render_all with Scene Cache and Camera status: accepted created: 2026-04-14 -updated: 2026-04-14 +updated: 2026-04-15 tags: [gfx, runtime, render, camera, scene] --- @@ -54,6 +54,10 @@ O problema concreto não é só “chamar uma função”. É decidir: - cache ativo - resolver ativo - câmera ativa +- O `FrameComposer` não pode regredir o contrato já aceito no scene model: + - `SceneLayer.tile_size` já é por-layer e aceita `8x8`, `16x16` e `32x32`; + - o decoder de `SCENE` já materializa esses tamanhos; + - fixar o pipeline em `16x16` dentro do orquestrador de frame criaria uma restrição artificial que não existe no modelo canônico. - Se a integração for mal feita, o renderer pode voltar a misturar: - política de câmera - atualização de cache @@ -315,6 +319,60 @@ Aceitar o contrato mínimo acima como base de fechamento da agenda, a menos que - O HUD entra nesta integração já agora, ou o foco da primeira integração é apenas world + sprites + fades? - fechado: sem HUD nesta primeira integração. +## Reabertura 2026-04-15 + +### Contexto adicional + +Ao revisitar a thread, apareceu uma restrição indevida: tratar o `FrameComposer` como se aceitasse apenas tilesets `16x16`. + +Isso conflita com o estado atual do runtime: + +- `TileSize` no HAL já enumera `Size8`, `Size16` e `Size32`; +- `SceneLayer` carrega `tile_size` por layer; +- o decoder de `SCENE` já aceita `8`, `16` e `32`; +- `SceneViewportResolver` e `SceneViewportCache` já calculam offsets, anchors e cópia a partir do `tile_size` da própria layer. + +O risco aqui não é apenas de implementação. Se o contrato do `FrameComposer` assumir `16x16` como pré-condição, ele quebra a neutralidade do orquestrador e reabre uma limitação artificial acima do scene model. + +### Problema reaberto + +Precisamos fechar explicitamente que o `FrameComposer` aceita cenas/layers com `tile_size` `8x8` e não impõe `16x16` como tamanho mínimo ou obrigatório para o world path. + +### Opcoes adicionais + +### Opcao 4 - Fixar `16x16` no `FrameComposer` e tratar `8x8` como fora de escopo + +**Vantagens:** +- reduz casos de teste imediatos; +- simplifica implementação inicial se alguém estiver assumindo viewport/caches calibrados manualmente para `16`. + +**Desvantagens:** +- contradiz o scene model já aceito; +- introduz restrição artificial no orquestrador; +- obriga futura revisão de contrato para reaceitar algo que a base já suporta. + +### Opcao 5 - Manter `FrameComposer` tile-size agnostic e aceitar `8x8` desde V1 + +**Vantagens:** +- preserva o contrato canônico já existente em `SceneLayer`; +- mantém o `FrameComposer` como orquestrador, não como redefinidor de formato; +- evita bifurcação entre pipeline de scene e pipeline de composição. + +**Desvantagens:** +- exige deixar isso explícito na decisão e nos planos; +- aumenta a exigência de testes para viewport/cache/cópia com `8x8`. + +### Recomendacao adicional + +Seguir com a **Opcao 5**. + +Norma proposta para fechamento desta reabertura: + +- `FrameComposer` deve aceitar scenes/layers cujo `tile_size` resolvido seja `8x8`, `16x16` ou `32x32`; +- `FrameComposer` nao deve impor `16x16` como pré-condição para bind, cache, resolver ou composição; +- qualquer validação de compatibilidade deve ser derivada do `tile_size` declarado pela própria layer / glyph bank, nunca de um default rígido no compositor; +- os planos derivados desta thread precisam citar testes explícitos para `8x8`. + ## Criterio para Encerrar Esta agenda pode ser encerrada quando estiver explícito: @@ -324,6 +382,7 @@ Esta agenda pode ser encerrada quando estiver explícito: - como funciona o bind/unbind da scene ativa; - quando o cache é atualizado; - como `render_all()` passa a compor o world path aceito; +- que o `FrameComposer` permanece agnóstico ao `tile_size` canônico da layer e aceita `8x8` sem downgrade contratual; - e qual é a superfície mínima de integração para implementação sem reabrir a arquitetura base. ## Resolucao diff --git a/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md b/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md index ab824e2e..db95ec61 100644 --- a/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md +++ b/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md @@ -60,6 +60,12 @@ This also preserves future backend freedom: - hardware-assisted blit path later; - or a more PPU-like backend in bare-metal environments. +It also preserves the scene-model contract already accepted below the frame layer: + +- tile size is a property of each scene layer; +- the frame orchestrator must consume that contract, not redefine it; +- `FrameComposer` must therefore remain tile-size agnostic rather than hard-coding `16x16` assumptions. + ## Invariantes / Contrato ### 1. Frame Entry @@ -113,6 +119,15 @@ This also preserves future backend freedom: - `Gfx` MUST NOT own cache refresh policy. - `Gfx` MUST only consume already prepared render state. +### 5A. Tile Size Contract + +- `FrameComposer` SHALL remain tile-size agnostic. +- `FrameComposer` MUST accept scene layers whose canonical `tile_size` is `8x8`, `16x16`, or `32x32`. +- `FrameComposer` MUST NOT impose `16x16` as a bind-time, cache-time, resolver-time, or render-time precondition. +- Cache sizing, resolver math, and world-copy preparation SHALL derive from the `tile_size` declared by each bound scene layer. +- Compatibility checks, when needed, MUST be derived from canonical scene-layer and glyph-bank metadata rather than from a hard-coded compositor default. +- Any implementation path that only works for `16x16` tiles is NON-COMPLIANT with this decision. + ### 6. Sprite Model - `Sprite.active` MUST be removed from the canonical operational model. @@ -158,6 +173,7 @@ This also preserves future backend freedom: - `Hardware` will need to aggregate `FrameComposer` next to `Gfx`. - `Gfx` will need to lose ownership of scene/sprite operational state. - Sprite submission state will need to move into `SpriteController`. +- `FrameComposer`, cache, and resolver integration must preserve per-layer `tile_size` semantics, including `8x8`. ### Runtime / VM @@ -168,6 +184,7 @@ This also preserves future backend freedom: - Scene activation will become explicit through bank-id binding. - Scene slot replacement will require explicit rebinding behavior from callers. +- Scene-driven tile-size metadata must propagate unchanged into `FrameComposer` orchestration and backend copy preparation. ## Referencias @@ -180,7 +197,9 @@ This also preserves future backend freedom: - `FrameComposer` and `SpriteController` need explicit planning and migration sequencing. - `Gfx.render_all()` retirement MUST be planned rather than removed ad hoc. - The frame service rename and integration path MUST be propagated through the frame loop callsites. +- Plan steps and tests that cover world composition MUST explicitly include `8x8` tile-size coverage. ## Revision Log - 2026-04-14: Initial accepted decision from `AGD-0026`. +- 2026-04-15: Revision accepted to make `FrameComposer` explicitly tile-size agnostic and to require `8x8` support alongside `16x16` and `32x32`. diff --git a/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md b/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md index 80897371..fac4e898 100644 --- a/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md +++ b/discussion/workflow/plans/PLN-0017-frame-composer-core-and-hardware-ownership.md @@ -54,6 +54,7 @@ Create `FrameComposer` as a concrete driver-side subsystem. - `SceneViewportResolver` - `SpriteController` - Keep scene/cache/resolver fields optional where no-scene operation is required. +- Do not introduce any fixed `16x16` assumption into owned state; tile-size-sensitive behavior must derive from bound scene metadata. **File(s):** - `crates/console/prometeu-drivers/src/frame_composer.rs` @@ -93,6 +94,7 @@ Give the driver layer a stable initial surface for `FrameComposer`. ### Unit Tests - `FrameComposer` can be constructed without a bound scene. - `Hardware` successfully constructs with both `gfx` and `frame_composer`. +- Construction and owned state shape do not encode `16x16` as an implicit world-path invariant. ### Integration Tests - Shared bank access needed by `FrameComposer` is available through hardware construction. @@ -115,3 +117,4 @@ Give the driver layer a stable initial surface for `FrameComposer`. - Introducing `FrameComposer` with too much behavior too early can blur later migration steps. - Introducing too little owned state can leave ownership ambiguous and force rework in later plans. +- Encoding `16x16` into the initial owned-state shape would create a contract violation that later plans would have to unwind. diff --git a/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md b/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md index 16991d27..33c3693d 100644 --- a/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md +++ b/discussion/workflow/plans/PLN-0019-scene-binding-camera-and-scene-status.md @@ -15,6 +15,7 @@ Implement the `FrameComposer` scene-binding contract, minimal camera state, and ## Background `DEC-0014` locks scene activation around `bind_scene(scene_bank_id)` with `SceneBankPoolAccess`, pointer-based access only, and `scene_bank_id + Arc` retained inside `FrameComposer`. +The same decision also requires `FrameComposer` to remain tile-size agnostic and to preserve canonical per-layer `tile_size`, including `8x8`. ## Scope @@ -90,6 +91,7 @@ Align cache/resolver lifetime with the active scene contract. - create or reinitialize cache/resolver. - On unbind: - discard cache/resolver and invalidate the world path. +- Any initialization must derive layer math from the bound scene tile sizes instead of assuming `16x16`. **File(s):** - `crates/console/prometeu-drivers/src/frame_composer.rs` @@ -101,6 +103,7 @@ Align cache/resolver lifetime with the active scene contract. - unbind clears active scene and cache. - scene status reflects no-scene and active-scene states. - camera coordinates are stored as top-left pixel-space values. +- bind/unbind remains valid for scenes whose layers use `8x8` tiles. ### Integration Tests - `FrameComposer` can resolve a scene from the pool and survive no-scene operation. @@ -115,6 +118,7 @@ Align cache/resolver lifetime with the active scene contract. - [ ] Scene status is explicit. - [ ] Camera contract is implemented as `i32` top-left viewport coordinates. - [ ] Cache/resolver lifetime follows scene bind/unbind. +- [ ] Scene bind/cache/resolver setup preserves canonical per-layer tile sizes, including `8x8`. ## Dependencies diff --git a/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md b/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md index 240118f4..9f1064f4 100644 --- a/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md +++ b/discussion/workflow/plans/PLN-0020-cache-refresh-and-render-frame-path.md @@ -15,6 +15,7 @@ Connect `FrameComposer` to `SceneViewportResolver`, apply cache refreshes inside ## Background `DEC-0014` requires that cache refresh policy remain inside `FrameComposer` and that `FrameComposer.render_frame()` become the canonical frame entry while `Gfx` remains only the low-level execution backend. +`DEC-0014` also requires the world path to remain tile-size agnostic, with explicit support for `8x8`, `16x16`, and `32x32` scene-layer tile sizes. ## Scope @@ -41,6 +42,7 @@ Move cache-refresh orchestration fully into `FrameComposer`. - consume returned `CacheRefreshRequest`s - apply them to `SceneViewportCache` - Keep `Gfx` unaware of refresh semantics. +- Ensure resolver and refresh math follow the bound layer `tile_size` values rather than any fixed `16x16` default. **File(s):** - `crates/console/prometeu-drivers/src/frame_composer.rs` @@ -58,6 +60,7 @@ Introduce the new frame service on `FrameComposer`. - call the cache-backed world path in `Gfx` - If no scene is active: - call the no-scene path for `sprites + fades` +- World rendering must remain valid when the active scene uses `8x8` tiles. **File(s):** - `crates/console/prometeu-drivers/src/frame_composer.rs` @@ -87,6 +90,7 @@ Protect the two canonical frame modes. - no-scene `sprites + fades` - scene transition through unbind/rebind - cache refresh behavior staying inside `FrameComposer` + - active-scene composition with `8x8` tile-size layers **File(s):** - `crates/console/prometeu-drivers/src/frame_composer.rs` @@ -98,9 +102,11 @@ Protect the two canonical frame modes. - `render_frame()` with no scene produces valid no-scene composition. - `render_frame()` with a scene applies resolver refreshes before composition. - cache refresh requests are applied by `FrameComposer`, not `Gfx`. +- `render_frame()` with an `8x8` scene uses resolver/cache math derived from layer tile size rather than a `16x16` assumption. ### Integration Tests - scene bind + camera set + sprite emission + `render_frame()` produces the expected composed frame. +- scene bind + camera set + `8x8` scene + `render_frame()` produces the expected composed frame. ### Manual Verification - Verify that no-scene frames still render sprites/fades without crashes or hidden clears. @@ -112,6 +118,7 @@ Protect the two canonical frame modes. - [ ] World rendering consumes the cache-backed path. - [ ] No-scene `sprites + fades` behavior remains valid. - [ ] `Gfx` remains backend-only for this path. +- [ ] The world path is explicitly covered for `8x8` scenes without `16x16`-specific assumptions. ## Dependencies @@ -122,3 +129,4 @@ Protect the two canonical frame modes. - If refresh application leaks into `Gfx`, the ownership split from `DEC-0014` collapses. - If no-scene behavior is not tested explicitly, scene integration can accidentally make scene binding mandatory. +- If tests cover only `16x16`, a latent compositor regression against canonical `8x8` scenes can ship unnoticed. diff --git a/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md b/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md index e95ecbae..00edec77 100644 --- a/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md +++ b/discussion/workflow/plans/PLN-0021-service-retirement-callsite-migration-and-regression.md @@ -15,6 +15,7 @@ Retire `Gfx.render_all()` from the canonical flow, migrate callsites to `FrameCo ## Background `DEC-0014` is explicit that `Gfx.render_all()` must be retired and that `FrameComposer.render_frame()` becomes the canonical frame orchestration entrypoint. This final plan removes the old canonical service shape and validates the migration end-to-end. +The same decision also requires the new canonical path to preserve scene-layer tile sizes such as `8x8`, not just `16x16`. ## Scope @@ -68,6 +69,7 @@ Protect the new service model against fallback to the old renderer path. - frame-loop code calls `FrameComposer.render_frame()` - no-scene frames remain valid - active-scene frames render through cache-backed composition + - active-scene frames remain valid for canonical `8x8` scenes - sprite emission and ordering survive the full path - Add assertions or test failures for accidental continued reliance on `Gfx.render_all()`. @@ -96,6 +98,7 @@ Confirm the migration did not break unrelated systems. ### Integration Tests - runtime tick path renders through `FrameComposer.render_frame()`. - no-scene and active-scene frame modes both remain valid. +- runtime tick path remains valid when the active scene uses `8x8` tiles. ### Manual Verification - Run the repository CI path and confirm the final integrated service model is green. @@ -106,6 +109,7 @@ Confirm the migration did not break unrelated systems. - [ ] `Gfx.render_all()` is retired from the canonical service path. - [ ] Regression coverage protects against fallback to the old model. - [ ] Repository validation passes after the migration. +- [ ] Regression coverage includes the canonical `8x8` world-path case. ## Dependencies @@ -116,3 +120,4 @@ Confirm the migration did not break unrelated systems. - Removing `render_all()` too early can strand intermediate callsites. - Leaving it in place as a canonical path for too long can create a dual-service model that is harder to remove later. +- Migrating callsites without `8x8` regression coverage can falsely validate only the legacy `16x16` path.