primitives pipeline adjustments
This commit is contained in:
parent
4a5210f347
commit
b0b8bb9028
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1475,6 +1475,8 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prometeu-bytecode",
|
||||
"prometeu-hal",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -5,4 +5,6 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
prometeu-bytecode = { path = "../../console/prometeu-bytecode" }
|
||||
prometeu-hal = { path = "../../console/prometeu-hal" }
|
||||
anyhow = "1"
|
||||
serde_json = "1"
|
||||
|
||||
@ -3,7 +3,25 @@ use prometeu_bytecode::assembler::assemble;
|
||||
use prometeu_bytecode::model::{
|
||||
BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta, SyscallDecl,
|
||||
};
|
||||
use prometeu_hal::asset::{
|
||||
AssetCodec, AssetEntry, BankType, PreloadEntry, SCENE_DECODED_LAYER_OVERHEAD_BYTES_V1,
|
||||
SCENE_LAYER_COUNT_V1, SCENE_PAYLOAD_MAGIC_V1, SCENE_PAYLOAD_VERSION_V1,
|
||||
};
|
||||
use prometeu_hal::cartridge::{
|
||||
AssetsPackHeader, AssetsPackPrelude, ASSETS_PA_MAGIC, ASSETS_PA_PRELUDE_SIZE,
|
||||
ASSETS_PA_SCHEMA_VERSION,
|
||||
};
|
||||
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,
|
||||
};
|
||||
use prometeu_hal::scene_bank::SceneBank;
|
||||
use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer};
|
||||
use prometeu_hal::tile::Tile;
|
||||
use prometeu_hal::tilemap::TileMap;
|
||||
use std::fs;
|
||||
use std::mem::size_of;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn asm(s: &str) -> Vec<u8> {
|
||||
@ -20,13 +38,6 @@ pub fn generate() -> Result<()> {
|
||||
arg_slots: 1,
|
||||
ret_slots: 0,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "gfx".into(),
|
||||
name: "draw_disc".into(),
|
||||
version: 1,
|
||||
arg_slots: 5,
|
||||
ret_slots: 0,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "gfx".into(),
|
||||
name: "draw_text".into(),
|
||||
@ -41,6 +52,20 @@ pub fn generate() -> Result<()> {
|
||||
arg_slots: 2,
|
||||
ret_slots: 0,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "composer".into(),
|
||||
name: "bind_scene".into(),
|
||||
version: 1,
|
||||
arg_slots: 1,
|
||||
ret_slots: 1,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "composer".into(),
|
||||
name: "set_camera".into(),
|
||||
version: 1,
|
||||
arg_slots: 2,
|
||||
ret_slots: 0,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "composer".into(),
|
||||
name: "emit_sprite".into(),
|
||||
@ -59,7 +84,7 @@ pub fn generate() -> Result<()> {
|
||||
param_slots: 0,
|
||||
local_slots: 2,
|
||||
return_slots: 0,
|
||||
max_stack_slots: 16,
|
||||
max_stack_slots: 32,
|
||||
}];
|
||||
|
||||
let module = BytecodeModule {
|
||||
@ -67,7 +92,6 @@ pub fn generate() -> Result<()> {
|
||||
const_pool: vec![
|
||||
ConstantPoolEntry::String("stress".into()),
|
||||
ConstantPoolEntry::String("frame".into()),
|
||||
ConstantPoolEntry::String("missing_glyph_bank".into()),
|
||||
],
|
||||
functions,
|
||||
code: rom,
|
||||
@ -89,129 +113,326 @@ pub fn generate() -> Result<()> {
|
||||
out_dir.push("stress-console");
|
||||
fs::create_dir_all(&out_dir)?;
|
||||
fs::write(out_dir.join("program.pbx"), bytes)?;
|
||||
let assets_pa_path = out_dir.join("assets.pa");
|
||||
if assets_pa_path.exists() {
|
||||
fs::remove_file(&assets_pa_path)?;
|
||||
}
|
||||
fs::write(out_dir.join("manifest.json"), b"{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": 1,\n \"title\": \"Stress Console\",\n \"app_version\": \"0.1.0\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"gfx\", \"log\"]\n}\n")?;
|
||||
fs::write(out_dir.join("assets.pa"), build_assets_pack()?)?;
|
||||
fs::write(out_dir.join("manifest.json"), b"{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": 1,\n \"title\": \"Stress Console\",\n \"app_version\": \"0.1.0\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"gfx\", \"log\", \"asset\"]\n}\n")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn heavy_load(rom: &mut Vec<u8>) {
|
||||
// Single function 0: main
|
||||
// Everything runs here — no coroutines, no SPAWN, no YIELD.
|
||||
//
|
||||
// Global 0 = t (frame counter)
|
||||
// Local 0 = scratch
|
||||
// Local 1 = loop counter for discs
|
||||
//
|
||||
// Loop:
|
||||
// t = (t + 1)
|
||||
// clear screen
|
||||
// draw 500 discs using t for animation
|
||||
// draw 20 texts using t for animation
|
||||
// RET (runtime handles the frame loop)
|
||||
// Global 0 = frame counter
|
||||
// Global 1 = scene bound flag
|
||||
// Local 0 = sprite row
|
||||
// Local 1 = sprite col
|
||||
|
||||
// --- init locals ---
|
||||
// local 0: scratch
|
||||
// local 1: loop counter for discs
|
||||
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 0\nPUSH_I32 0\nSET_LOCAL 1"));
|
||||
|
||||
// --- t = (t + 1) ---
|
||||
// t is global 0 to persist across prepare_call resets
|
||||
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 1\nADD\nSET_GLOBAL 0"));
|
||||
|
||||
// --- clear screen ---
|
||||
rom.extend(asm("GET_GLOBAL 1\nPUSH_I32 0\nEQ"));
|
||||
let jif_bind_done_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||
|
||||
rom.extend(asm("PUSH_I32 0\nHOSTCALL 3\nPOP_N 1\nPUSH_I32 1\nSET_GLOBAL 1"));
|
||||
let bind_done_target = rom.len() as u32;
|
||||
|
||||
rom.extend(asm("PUSH_I32 0\nHOSTCALL 0"));
|
||||
// --- call composer-domain sprite emission path once per frame and drop status ---
|
||||
|
||||
rom.extend(asm(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 4\nPOP_N 1",
|
||||
"GET_GLOBAL 0\nPUSH_I32 2\nMUL\nPUSH_I32 192\nMOD\nGET_GLOBAL 0\nPUSH_I32 76\nMOD\nHOSTCALL 4",
|
||||
));
|
||||
|
||||
// --- draw 500 discs ---
|
||||
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
||||
let disc_loop_start = rom.len() as u32;
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 500\nLT"));
|
||||
let jif_disc_end_offset = rom.len() + 2;
|
||||
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 0"));
|
||||
let row_loop_start = rom.len() as u32;
|
||||
rom.extend(asm("GET_LOCAL 0\nPUSH_I32 16\nLT"));
|
||||
let jif_row_end_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||
|
||||
// x = (t * (i+7) + i * 13) % 320
|
||||
rom.extend(asm("GET_GLOBAL 0\nGET_LOCAL 1\nPUSH_I32 7\nADD\nMUL\nGET_LOCAL 1\nPUSH_I32 13\nMUL\nADD\nPUSH_I32 320\nMOD"));
|
||||
// y = (t * (i+11) + i * 17) % 180
|
||||
rom.extend(asm("GET_GLOBAL 0\nGET_LOCAL 1\nPUSH_I32 11\nADD\nMUL\nGET_LOCAL 1\nPUSH_I32 17\nMUL\nADD\nPUSH_I32 180\nMOD"));
|
||||
// r = ( (i*13) % 20 ) + 5
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 13\nMUL\nPUSH_I32 20\nMOD\nPUSH_I32 5\nADD"));
|
||||
// border color = (i * 1234) & 0xFFFF
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1234\nMUL\nPUSH_I32 65535\nBIT_AND"));
|
||||
// fill color = (i * 5678 + t) & 0xFFFF
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 5678\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 65535\nBIT_AND"));
|
||||
// HOSTCALL gfx.draw_disc (x, y, r, border, fill)
|
||||
rom.extend(asm("HOSTCALL 1"));
|
||||
|
||||
// i++
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nADD\nSET_LOCAL 1"));
|
||||
let jmp_disc_loop_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP 0"));
|
||||
let disc_loop_end = rom.len() as u32;
|
||||
|
||||
// --- draw 20 texts ---
|
||||
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
||||
let text_loop_start = rom.len() as u32;
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 20\nLT"));
|
||||
let jif_text_end_offset = rom.len() + 2;
|
||||
let col_loop_start = rom.len() as u32;
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 32\nLT"));
|
||||
let jif_col_end_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||
|
||||
// x = (t * 3 + i * 40) % 320
|
||||
rom.extend(asm(
|
||||
"GET_GLOBAL 0\nPUSH_I32 3\nMUL\nGET_LOCAL 1\nPUSH_I32 40\nMUL\nADD\nPUSH_I32 320\nMOD",
|
||||
"PUSH_I32 0\n\
|
||||
GET_LOCAL 0\nPUSH_I32 32\nMUL\nGET_LOCAL 1\nADD\nGET_GLOBAL 0\nADD\nPUSH_I32 15\nMOD\nPUSH_I32 1\nADD\n\
|
||||
GET_LOCAL 1\nPUSH_I32 10\nMUL\nGET_GLOBAL 0\nPUSH_I32 10\nMOD\nADD\nPUSH_I32 320\nMOD\n\
|
||||
GET_LOCAL 0\nPUSH_I32 10\nMUL\nGET_GLOBAL 0\nPUSH_I32 2\nMUL\nPUSH_I32 10\nMOD\nADD\nPUSH_I32 180\nMOD\n\
|
||||
GET_LOCAL 0\nGET_LOCAL 1\nADD\nGET_GLOBAL 0\nADD\nPUSH_I32 4\nMOD\n\
|
||||
PUSH_I32 0\n\
|
||||
GET_LOCAL 1\nGET_GLOBAL 0\nADD\nPUSH_I32 1\nBIT_AND\nPUSH_I32 0\nNEQ\n\
|
||||
GET_LOCAL 0\nGET_GLOBAL 0\nADD\nPUSH_I32 1\nBIT_AND\nPUSH_I32 0\nNEQ\n\
|
||||
GET_LOCAL 0\nGET_LOCAL 1\nADD\nPUSH_I32 4\nMOD\n\
|
||||
HOSTCALL 5\nPOP_N 1",
|
||||
));
|
||||
// y = (i * 30 + t) % 180
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 30\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 180\nMOD"));
|
||||
// string (toggle between "stress" and "frame")
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nBIT_AND\nPUSH_I32 0\nNEQ"));
|
||||
let jif_text_alt_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||
rom.extend(asm("PUSH_CONST 0")); // "stress"
|
||||
let jmp_text_join_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP 0"));
|
||||
let text_alt_target = rom.len() as u32;
|
||||
rom.extend(asm("PUSH_CONST 1")); // "frame"
|
||||
let text_join_target = rom.len() as u32;
|
||||
|
||||
// color = (t * 10 + i * 1000) & 0xFFFF
|
||||
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 10\nMUL\nGET_LOCAL 1\nPUSH_I32 1000\nMUL\nADD\nPUSH_I32 65535\nBIT_AND"));
|
||||
// HOSTCALL gfx.draw_text (x, y, str, color)
|
||||
rom.extend(asm("HOSTCALL 2"));
|
||||
|
||||
// i++
|
||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nADD\nSET_LOCAL 1"));
|
||||
let jmp_text_loop_offset = rom.len() + 2;
|
||||
let jmp_col_loop_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP 0"));
|
||||
let text_loop_end = rom.len() as u32;
|
||||
let col_loop_end = rom.len() as u32;
|
||||
|
||||
rom.extend(asm("GET_LOCAL 0\nPUSH_I32 1\nADD\nSET_LOCAL 0"));
|
||||
let jmp_row_loop_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP 0"));
|
||||
let row_loop_end = rom.len() as u32;
|
||||
|
||||
rom.extend(asm(
|
||||
"PUSH_I32 8\n\
|
||||
PUSH_I32 8\n\
|
||||
PUSH_CONST 0\n\
|
||||
GET_GLOBAL 0\nPUSH_I32 2047\nMUL\nPUSH_I32 65535\nBIT_AND\n\
|
||||
HOSTCALL 1",
|
||||
));
|
||||
rom.extend(asm(
|
||||
"PUSH_I32 8\n\
|
||||
PUSH_I32 20\n\
|
||||
PUSH_CONST 1\n\
|
||||
GET_GLOBAL 0\nPUSH_I32 4093\nMUL\nPUSH_I32 65535\nBIT_AND\n\
|
||||
HOSTCALL 1",
|
||||
));
|
||||
|
||||
// --- log every 60 frames ---
|
||||
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 60\nMOD\nPUSH_I32 0\nEQ"));
|
||||
let jif_log_offset = rom.len() + 2;
|
||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||
rom.extend(asm("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 3"));
|
||||
rom.extend(asm("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 2"));
|
||||
let after_log = rom.len() as u32;
|
||||
|
||||
// --- end of function ---
|
||||
rom.extend(asm("FRAME_SYNC\nRET"));
|
||||
|
||||
// --- Patch jump targets ---
|
||||
let patch = |buf: &mut Vec<u8>, imm_offset: usize, target: u32| {
|
||||
buf[imm_offset..imm_offset + 4].copy_from_slice(&target.to_le_bytes());
|
||||
};
|
||||
|
||||
patch(rom, jif_disc_end_offset, disc_loop_end);
|
||||
patch(rom, jmp_disc_loop_offset, disc_loop_start);
|
||||
|
||||
patch(rom, jif_text_end_offset, text_loop_end);
|
||||
patch(rom, jif_text_alt_offset, text_alt_target);
|
||||
patch(rom, jmp_text_join_offset, text_join_target);
|
||||
patch(rom, jmp_text_loop_offset, text_loop_start);
|
||||
|
||||
patch(rom, jif_bind_done_offset, bind_done_target);
|
||||
patch(rom, jif_row_end_offset, row_loop_end);
|
||||
patch(rom, jif_col_end_offset, col_loop_end);
|
||||
patch(rom, jmp_col_loop_offset, col_loop_start);
|
||||
patch(rom, jmp_row_loop_offset, row_loop_start);
|
||||
patch(rom, jif_log_offset, after_log);
|
||||
}
|
||||
|
||||
fn build_assets_pack() -> Result<Vec<u8>> {
|
||||
let (glyph_entry, glyph_payload) = build_glyph_asset();
|
||||
let scene = build_scene_bank();
|
||||
let scene_payload = encode_scene_payload(&scene);
|
||||
let scene_entry = AssetEntry {
|
||||
asset_id: 1,
|
||||
asset_name: "stress_scene".into(),
|
||||
bank_type: BankType::SCENE,
|
||||
offset: glyph_payload.len() as u64,
|
||||
size: scene_payload.len() as u64,
|
||||
decoded_size: expected_scene_decoded_size(&scene) as u64,
|
||||
codec: AssetCodec::None,
|
||||
metadata: serde_json::json!({}),
|
||||
};
|
||||
|
||||
let asset_table = vec![glyph_entry, scene_entry];
|
||||
let preload =
|
||||
vec![PreloadEntry { asset_id: 0, slot: 0 }, PreloadEntry { asset_id: 1, slot: 0 }];
|
||||
let payload_len = glyph_payload.len() + scene_payload.len();
|
||||
let header = serde_json::to_vec(&AssetsPackHeader { asset_table, preload })?;
|
||||
let payload_offset = (ASSETS_PA_PRELUDE_SIZE + header.len()) as u64;
|
||||
let prelude = AssetsPackPrelude {
|
||||
magic: ASSETS_PA_MAGIC,
|
||||
schema_version: ASSETS_PA_SCHEMA_VERSION,
|
||||
header_len: header.len() as u32,
|
||||
payload_offset,
|
||||
flags: 0,
|
||||
reserved: 0,
|
||||
header_checksum: 0,
|
||||
};
|
||||
|
||||
let mut bytes = prelude.to_bytes().to_vec();
|
||||
bytes.extend_from_slice(&header);
|
||||
bytes.extend_from_slice(&glyph_payload);
|
||||
bytes.extend_from_slice(&scene_payload);
|
||||
debug_assert_eq!(bytes.len(), payload_offset as usize + payload_len);
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn build_glyph_asset() -> (AssetEntry, Vec<u8>) {
|
||||
let pixel_indices = vec![1_u8; 8 * 8];
|
||||
let mut payload = pack_4bpp(&pixel_indices);
|
||||
payload.extend_from_slice(&build_palette_bytes());
|
||||
|
||||
let entry = AssetEntry {
|
||||
asset_id: 0,
|
||||
asset_name: "stress_square".into(),
|
||||
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 * 2)
|
||||
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
|
||||
}),
|
||||
};
|
||||
|
||||
(entry, payload)
|
||||
}
|
||||
|
||||
fn build_palette_bytes() -> Vec<u8> {
|
||||
let mut bytes =
|
||||
Vec::with_capacity(GLYPH_BANK_PALETTE_COUNT_V1 * GLYPH_BANK_COLORS_PER_PALETTE * 2);
|
||||
for palette_id in 0..GLYPH_BANK_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_le_bytes());
|
||||
}
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
fn stress_color(palette_id: usize) -> Color {
|
||||
let r = ((palette_id * 53) % 256) as u8;
|
||||
let g = ((palette_id * 97 + 64) % 256) as u8;
|
||||
let b = ((palette_id * 29 + 128) % 256) as u8;
|
||||
Color::rgb(r, g, b)
|
||||
}
|
||||
|
||||
fn pack_4bpp(indices: &[u8]) -> Vec<u8> {
|
||||
let mut packed = Vec::with_capacity(indices.len().div_ceil(2));
|
||||
for chunk in indices.chunks(2) {
|
||||
let hi = chunk[0] & 0x0f;
|
||||
let lo = chunk.get(1).copied().unwrap_or(0) & 0x0f;
|
||||
packed.push((hi << 4) | lo);
|
||||
}
|
||||
packed
|
||||
}
|
||||
|
||||
fn build_scene_bank() -> SceneBank {
|
||||
let mut layers = std::array::from_fn(|layer_index| {
|
||||
let mut tiles = vec![
|
||||
Tile {
|
||||
active: false,
|
||||
glyph: Glyph { glyph_id: 0, palette_id: layer_index as u8 + 1 },
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
};
|
||||
64 * 32
|
||||
];
|
||||
|
||||
for step in 0..8 {
|
||||
let x = 4 + step * 7 + layer_index * 2;
|
||||
let y = 2 + step * 3 + layer_index * 2;
|
||||
let index = y * 64 + x;
|
||||
tiles[index] = Tile {
|
||||
active: true,
|
||||
glyph: Glyph { glyph_id: 0, palette_id: layer_index as u8 + 1 },
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
};
|
||||
}
|
||||
|
||||
SceneLayer {
|
||||
active: true,
|
||||
glyph_bank_id: 0,
|
||||
tile_size: TileSize::Size8,
|
||||
parallax_factor: match layer_index {
|
||||
0 => ParallaxFactor { x: 1.0, y: 1.0 },
|
||||
1 => ParallaxFactor { x: 0.75, y: 0.75 },
|
||||
2 => ParallaxFactor { x: 0.5, y: 0.5 },
|
||||
_ => ParallaxFactor { x: 0.25, y: 0.25 },
|
||||
},
|
||||
tilemap: TileMap { width: 64, height: 32, tiles },
|
||||
}
|
||||
});
|
||||
|
||||
// Keep the farthest layer a bit sparser so the diagonal remains visually readable.
|
||||
for step in 0..4 {
|
||||
let x = 10 + step * 12;
|
||||
let y = 4 + step * 5;
|
||||
let index = y * 64 + x;
|
||||
layers[3].tilemap.tiles[index].active = false;
|
||||
}
|
||||
|
||||
SceneBank { layers }
|
||||
}
|
||||
|
||||
fn expected_scene_decoded_size(scene: &SceneBank) -> usize {
|
||||
scene
|
||||
.layers
|
||||
.iter()
|
||||
.map(|layer| {
|
||||
SCENE_DECODED_LAYER_OVERHEAD_BYTES_V1 + layer.tilemap.tiles.len() * size_of::<Tile>()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn encode_scene_payload(scene: &SceneBank) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(&SCENE_PAYLOAD_MAGIC_V1);
|
||||
data.extend_from_slice(&SCENE_PAYLOAD_VERSION_V1.to_le_bytes());
|
||||
data.extend_from_slice(&(SCENE_LAYER_COUNT_V1 as u16).to_le_bytes());
|
||||
data.extend_from_slice(&0_u32.to_le_bytes());
|
||||
|
||||
for layer in &scene.layers {
|
||||
let layer_flags = if layer.active { 0b0000_0001 } else { 0 };
|
||||
data.push(layer_flags);
|
||||
data.push(layer.glyph_bank_id);
|
||||
data.push(layer.tile_size as u8);
|
||||
data.push(0);
|
||||
data.extend_from_slice(&layer.parallax_factor.x.to_le_bytes());
|
||||
data.extend_from_slice(&layer.parallax_factor.y.to_le_bytes());
|
||||
data.extend_from_slice(&(layer.tilemap.width as u32).to_le_bytes());
|
||||
data.extend_from_slice(&(layer.tilemap.height as u32).to_le_bytes());
|
||||
data.extend_from_slice(&(layer.tilemap.tiles.len() as u32).to_le_bytes());
|
||||
data.extend_from_slice(&0_u32.to_le_bytes());
|
||||
|
||||
for tile in &layer.tilemap.tiles {
|
||||
let mut tile_flags = 0_u8;
|
||||
if tile.active {
|
||||
tile_flags |= 0b0000_0001;
|
||||
}
|
||||
if tile.flip_x {
|
||||
tile_flags |= 0b0000_0010;
|
||||
}
|
||||
if tile.flip_y {
|
||||
tile_flags |= 0b0000_0100;
|
||||
}
|
||||
data.push(tile_flags);
|
||||
data.push(tile.glyph.palette_id);
|
||||
data.extend_from_slice(&tile.glyph.glyph_id.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assets_pack_contains_preloaded_glyph_and_scene_assets() {
|
||||
let bytes = build_assets_pack().expect("assets pack");
|
||||
let prelude =
|
||||
AssetsPackPrelude::from_bytes(&bytes[..ASSETS_PA_PRELUDE_SIZE]).expect("prelude");
|
||||
assert_eq!(prelude.magic, ASSETS_PA_MAGIC);
|
||||
assert_eq!(prelude.schema_version, ASSETS_PA_SCHEMA_VERSION);
|
||||
|
||||
let header_start = ASSETS_PA_PRELUDE_SIZE;
|
||||
let header_end = header_start + prelude.header_len as usize;
|
||||
let header: AssetsPackHeader =
|
||||
serde_json::from_slice(&bytes[header_start..header_end]).expect("header");
|
||||
|
||||
assert_eq!(header.asset_table.len(), 2);
|
||||
assert_eq!(header.preload.len(), 2);
|
||||
assert_eq!(header.asset_table[0].bank_type, BankType::GLYPH);
|
||||
assert_eq!(header.asset_table[1].bank_type, BankType::SCENE);
|
||||
assert_eq!(header.preload[0].slot, 0);
|
||||
assert_eq!(header.preload[1].slot, 0);
|
||||
assert_eq!(header.asset_table[0].offset, 0);
|
||||
assert_eq!(header.asset_table[1].offset, header.asset_table[0].size);
|
||||
assert_eq!(
|
||||
bytes.len(),
|
||||
prelude.payload_offset as usize
|
||||
+ header.asset_table[0].size as usize
|
||||
+ header.asset_table[1].size as usize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{"type":"meta","next_id":{"DSC":28,"AGD":28,"DEC":16,"PLN":26,"LSN":31,"CLSN":1}}
|
||||
{"type":"meta","next_id":{"DSC":29,"AGD":29,"DEC":17,"PLN":30,"LSN":31,"CLSN":1}}
|
||||
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"workflow/agendas/AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"workflow/decisions/DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"workflow/plans/PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"workflow/agendas/AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"workflow/decisions/DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"workflow/plans/PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
||||
@ -20,6 +20,7 @@
|
||||
{"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-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-0027","status":"open","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-17","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[{"id":"AGD-0027","file":"workflow/agendas/AGD-0027-frame-composer-public-syscall-surface.md","status":"accepted","created_at":"2026-04-17","updated_at":"2026-04-17"}],"decisions":[{"id":"DEC-0015","file":"workflow/decisions/DEC-0015-frame-composer-public-syscall-surface.md","status":"accepted","created_at":"2026-04-17","updated_at":"2026-04-17","ref_agenda":"AGD-0027"}],"plans":[{"id":"PLN-0022","file":"workflow/plans/PLN-0022-composer-syscall-domain-and-spec-propagation.md","status":"accepted","created_at":"2026-04-17","updated_at":"2026-04-17","ref_decisions":["DEC-0015"]},{"id":"PLN-0023","file":"workflow/plans/PLN-0023-composer-runtime-dispatch-and-legacy-removal.md","status":"accepted","created_at":"2026-04-17","updated_at":"2026-04-17","ref_decisions":["DEC-0015"]},{"id":"PLN-0024","file":"workflow/plans/PLN-0024-composer-cartridge-tooling-and-regression-migration.md","status":"accepted","created_at":"2026-04-17","updated_at":"2026-04-17","ref_decisions":["DEC-0015"]},{"id":"PLN-0025","file":"workflow/plans/PLN-0025-final-ci-validation-and-polish.md","status":"accepted","created_at":"2026-04-17","updated_at":"2026-04-17","ref_decisions":["DEC-0015"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0028","status":"open","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[{"id":"AGD-0028","file":"workflow/agendas/AGD-0028-deferred-overlay-and-primitive-composition.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-18"}],"decisions":[{"id":"DEC-0016","file":"workflow/decisions/DEC-0016-deferred-gfx-overlay-outside-frame-composer.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-18","ref_agenda":"AGD-0028"}],"plans":[{"id":"PLN-0026","file":"workflow/plans/PLN-0026-gfx-overlay-contract-and-spec-propagation.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-18","ref_decisions":["DEC-0016"]},{"id":"PLN-0027","file":"workflow/plans/PLN-0027-deferred-gfx-overlay-subsystem.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-18","ref_decisions":["DEC-0016"]},{"id":"PLN-0028","file":"workflow/plans/PLN-0028-runtime-frame-end-overlay-integration-and-parity.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-18","ref_decisions":["DEC-0016"]},{"id":"PLN-0029","file":"workflow/plans/PLN-0029-final-overlay-ci-validation-and-polish.md","status":"accepted","created_at":"2026-04-18","updated_at":"2026-04-18","ref_decisions":["DEC-0016"]}],"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"}]}
|
||||
|
||||
@ -0,0 +1,140 @@
|
||||
---
|
||||
id: AGD-0028
|
||||
ticket: deferred-overlay-and-primitive-composition
|
||||
title: Deferred Overlay and Primitive Composition over FrameComposer
|
||||
status: accepted
|
||||
created: 2026-04-18
|
||||
updated: 2026-04-18
|
||||
resolved: 2026-04-18
|
||||
decision: DEC-0016
|
||||
tags: [gfx, runtime, render, frame-composer, overlay, primitives, hud]
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
`FrameComposer.render_frame()` hoje recompõe o `back` no fim da logical frame. Quando há scene bound, o caminho `render_scene_from_cache(...)` limpa o buffer e desenha scene + sprites, o que apaga qualquer primitive ou `draw_text(...)` emitido antes via `gfx`.
|
||||
|
||||
Isso expôs um conflito de modelo:
|
||||
|
||||
- `composer.*` já é o caminho canônico de orquestração de frame;
|
||||
- `gfx.draw_text(...)` e demais primitives ainda escrevem diretamente no `back`;
|
||||
- o runtime só chama `render_frame()` no final do frame, então a escrita imediata em `back` deixou de ser semanticamente estável.
|
||||
- As primitives de `gfx` não são o mecanismo desejado para composição de jogos com scene/tile/sprite; elas existem principalmente como debug, instrumentação visual e artefatos rápidos.
|
||||
|
||||
Conteúdo relevante migrado de [AGD-0010](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md):
|
||||
|
||||
- a arquitetura aceita continua sendo de framebuffer destrutivo em memória, não scene graph ou renderer tipo GPU;
|
||||
- otimizações em primitives devem preservar a semântica observável, mesmo quando ganharem fast paths internos;
|
||||
- existe preocupação explícita com custo por classe de primitive e com orçamento de memória no alvo handheld;
|
||||
- caminhos de spans/linhas/clears são desejáveis como aceleração interna, mas sem reabrir o modelo operacional do pipeline do jogo.
|
||||
|
||||
## Problema
|
||||
|
||||
Precisamos decidir qual é o modelo canônico para primitives e texto no pipeline pós-`FrameComposer`.
|
||||
|
||||
Sem isso:
|
||||
|
||||
- texto e primitives continuam com comportamento dependente da ordem interna do renderer;
|
||||
- o stress test e qualquer cartridge que combine `composer.*` com `gfx.*` terão resultado inconsistente;
|
||||
- fica indefinido se primitives pertencem ao mundo, ao HUD, ou a um overlay final.
|
||||
|
||||
## Pontos Criticos
|
||||
|
||||
- `draw_text(...)` e primitives screen-space não podem depender de escrita imediata em `back`.
|
||||
- Para esta thread, primitives de `gfx` devem permanecer agnósticas ao pipeline canônico de render do jogo e não devem ser mescladas semanticamente com tiles/sprites.
|
||||
- A ordem de composição precisa ser explícita e estável: `scene -> sprites -> HUD -> primitives/debug overlay`, ou outra ordem formal equivalente.
|
||||
- Precisamos decidir se o contrato público de `gfx.*` muda semanticamente sem mudar ABI, ou se parte dessa superfície migra para `composer.*`.
|
||||
- A solução deve preservar o caminho sem scene bound.
|
||||
- A implementação deve evitar contaminar a infraestrutura de `gfx` responsável por scene, sprites e HUD com estado misto de overlay/debug; se necessário, o overlay deve ter fila/fase própria.
|
||||
- melhorias internas de primitive path devem continuar permitidas, desde que não mudem a semântica de overlay final e não exijam buffers extras incompatíveis com o orçamento de memória aceito.
|
||||
|
||||
## Opcoes
|
||||
|
||||
### Opcao 1 - Manter escrita direta em `back`
|
||||
|
||||
- **Abordagem:** manter `gfx.draw_text(...)` e primitives rasterizando imediatamente.
|
||||
- **Pro:** zero mudança estrutural agora.
|
||||
- **Contra:** o modelo continua quebrado sempre que `render_frame()` recompõe o buffer depois.
|
||||
- **Tradeoff:** só funciona de forma confiável fora do caminho canônico do `FrameComposer`.
|
||||
|
||||
### Opcao 2 - Fila única de draw commands pós-scene/pós-sprite
|
||||
|
||||
- **Abordagem:** transformar texto e primitives em comandos diferidos, drenados depois de `scene + sprites`.
|
||||
- **Pro:** resolve o problema imediato de overlay/HUD e estabiliza o stress test.
|
||||
- **Contra:** mistura HUD e primitives/debug sob o mesmo conceito, reduzindo clareza contratual mesmo quando a ordem prática for a mesma.
|
||||
- **Tradeoff:** simples para V1, mas semanticamente mais fraco do que separar overlay de jogo e overlay de debug.
|
||||
|
||||
### Opcao 3 - Separar HUD diferido de primitives/debug overlay final
|
||||
|
||||
- **Abordagem:** tratar `gfx.draw_text(...)` e demais primitives de `gfx` como overlay/debug final, separado da composição canônica de jogo (`scene + sprites + HUD`).
|
||||
- **Pro:** casa com a intenção declarada para `gfx.*`: debug, artefato rápido e instrumentação visual acima do frame do jogo.
|
||||
- **Contra:** exige modelar explicitamente uma fase extra no pipeline.
|
||||
- **Tradeoff:** aumenta a clareza contratual e evita mesclar primitives com o domínio de jogo.
|
||||
|
||||
### Opcao 4 - Manter HUD e primitives no mesmo estágio final, mas com categorias separadas
|
||||
|
||||
- **Abordagem:** drenar HUD e primitives ambos no fim do frame, porém com filas/categorias distintas e ordem formal `HUD -> primitives`.
|
||||
- **Pro:** preserva implementação próxima entre caminhos similares, mantendo contrato separado.
|
||||
- **Contra:** é mais custoso que a opção 3 sem entregar muito valor adicional imediato.
|
||||
- **Tradeoff:** bom se já houver expectativa de HUD canônico separado no curtíssimo prazo.
|
||||
|
||||
## Sugestao / Recomendacao
|
||||
|
||||
Seguir com a **Opcao 3**.
|
||||
|
||||
Minha recomendação é:
|
||||
|
||||
- retirar a escrita direta em `back` como contrato operacional para `gfx.draw_text(...)` e demais primitives de `gfx`;
|
||||
- introduzir uma fila diferida canônica de primitives/debug overlay drenada no fim do frame;
|
||||
- tratar `gfx.*` primitive/text como superfície agnóstica ao pipeline de jogo e explicitamente acima da composição canônica;
|
||||
- não misturar semanticamente primitives com scene/tile/sprite/HUD.
|
||||
- evitar compartilhar indevidamente o mesmo mecanismo operacional de composição entre overlay/debug e os caminhos de scene/sprite/HUD, mesmo quando o backend de rasterização reutilizado for o mesmo.
|
||||
|
||||
Ordem recomendada para o frame canônico:
|
||||
|
||||
1. limpar/compor scene;
|
||||
2. compor sprites;
|
||||
3. compor HUD canônico, se existir;
|
||||
4. aplicar `scene_fade`;
|
||||
5. aplicar `hud_fade`;
|
||||
6. drenar primitives/debug overlay de `gfx.*`.
|
||||
|
||||
## Perguntas em Aberto
|
||||
|
||||
- `draw_text(...)` e as demais primitives de `gfx` entram todas na mesma família de overlay final já na V1, ou começamos só com `draw_text(...)`?
|
||||
- `render_no_scene_frame()` deve usar a mesma fila diferida para manter semântica idêntica com e sem scene?
|
||||
- HUD canônico precisa existir explicitamente nesta mesma thread, ou pode continuar implícito/externo enquanto as primitives já migram para overlay final?
|
||||
- quais fast paths internos de primitives continuam desejáveis nessa nova fase, por exemplo spans horizontais/verticais, fills e clears, sem misturar isso com a composição do jogo?
|
||||
- o overlay/debug final precisa de dirtying próprio por classe de primitive ou isso pode ficar fora da primeira migração?
|
||||
|
||||
## Criterio para Encerrar
|
||||
|
||||
Esta agenda pode ser encerrada quando tivermos uma resposta explícita para:
|
||||
|
||||
- o destino semântico de `draw_text(...)`;
|
||||
- se haverá uma fila própria para primitives/debug overlay e qual a relação dela com HUD;
|
||||
- a ordem canônica de composição do frame;
|
||||
- o escopo exato da primeira migração implementável sem reabrir o restante do pipeline.
|
||||
|
||||
## Resolucao Parcial
|
||||
|
||||
Direção já aceita nesta agenda:
|
||||
|
||||
- primitives e `draw_text(...)` de `gfx.*` devem ser tratadas como overlay/debug final;
|
||||
- esse overlay deve ser drenado **depois** de `hud_fade`;
|
||||
- scene, sprites e HUD canônico não devem ser semanticamente misturados com o overlay/debug;
|
||||
- a implementação deve preservar separação operacional suficiente para que o `gfx` usado pelo pipeline do jogo não passe a depender do estado transitório de primitives/debug;
|
||||
- otimizações de primitive path discutidas na `AGD-0010` continuam válidas, mas passam a operar dentro do domínio de overlay/debug final, não como parte da composição canônica de scene/sprite/HUD.
|
||||
|
||||
## Resolucao
|
||||
|
||||
Esta agenda fica aceita com os seguintes pontos fechados:
|
||||
|
||||
- `gfx.draw_text(...)` e as demais primitives públicas de `gfx.*` pertencem à mesma família V1 de overlay/debug final;
|
||||
- esse overlay/debug fica **fora** do `FrameComposer`;
|
||||
- `FrameComposer` continua restrito à composição canônica do jogo (`scene`, `sprites` e HUD canônico quando existir);
|
||||
- o overlay/debug deve ser drenado depois de `hud_fade`;
|
||||
- o caminho sem scene bound deve observar a mesma semântica final de overlay/debug;
|
||||
- HUD canônico explícito não faz parte desta thread e pode permanecer implícito/externo por enquanto;
|
||||
- fast paths internos de primitives continuam permitidos, desde que preservem a semântica observável do overlay/debug final;
|
||||
- dirtying granular ou otimizações finas por classe de primitive não fazem parte da primeira migração normativa desta thread.
|
||||
@ -0,0 +1,150 @@
|
||||
---
|
||||
id: DEC-0016
|
||||
ticket: deferred-overlay-and-primitive-composition
|
||||
title: Deferred GFX Overlay Outside FrameComposer
|
||||
status: accepted
|
||||
created: 2026-04-18
|
||||
accepted: 2026-04-18
|
||||
agenda: AGD-0028
|
||||
plans: [PLN-0026, PLN-0027, PLN-0028, PLN-0029]
|
||||
tags: [gfx, runtime, render, frame-composer, overlay, primitives, hud]
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
Accepted.
|
||||
|
||||
## Contexto
|
||||
|
||||
`DEC-0014` and `DEC-0015` established `FrameComposer` as the canonical orchestration path for game-frame composition and exposed that orchestration publicly through `composer.*`.
|
||||
|
||||
That migration left `gfx.draw_text(...)` and other `gfx` primitives with their historical immediate-write behavior against the working framebuffer. Once the runtime moved to end-of-frame composition through `FrameComposer.render_frame()`, those immediate writes became unstable: scene-backed frame composition can rebuild the backbuffer after primitive calls have already touched it.
|
||||
|
||||
The resulting conflict is not about whether primitives should remain available. It is about their semantic place in the pipeline. The accepted direction of this thread is that `gfx` primitives are not part of the canonical game composition model. They are primarily for debug, quick visual instrumentation, and rapid artifacts, and they must remain agnostic to scene/tile/sprite/HUD composition.
|
||||
|
||||
Relevant performance context migrated from `AGD-0010` also remains in force:
|
||||
|
||||
- the renderer continues to be a destructive software framebuffer model, not a retained scene graph or GPU-style renderer;
|
||||
- internal primitive fast paths remain desirable;
|
||||
- memory growth must remain constrained for the handheld target;
|
||||
- optimization of primitive execution must not alter observable semantics.
|
||||
|
||||
## Decisao
|
||||
|
||||
`gfx.*` primitives and text SHALL move to a deferred final overlay model that lives outside `FrameComposer`.
|
||||
|
||||
Normatively:
|
||||
|
||||
- `FrameComposer` SHALL remain responsible only for canonical game-frame composition:
|
||||
- scene composition;
|
||||
- sprite composition;
|
||||
- canonical HUD composition when such a HUD stage exists.
|
||||
- `FrameComposer` MUST NOT become the owner of debug/primitive overlay state.
|
||||
- Public `gfx.*` primitives, including `gfx.draw_text(...)`, SHALL belong to a V1 `gfx` overlay/debug family.
|
||||
- That overlay/debug family SHALL be deferred rather than written immediately as the stable operational contract.
|
||||
- The deferred overlay/debug stage SHALL be drained after `hud_fade`.
|
||||
- The deferred overlay/debug stage SHALL be above scene, sprites, and canonical HUD in final visual order.
|
||||
- The no-scene path MUST preserve the same final overlay/debug semantics.
|
||||
- `gfx.*` primitives MUST remain semantically separate from scene/tile/sprite/HUD composition.
|
||||
- The implementation MUST preserve operational separation sufficient to prevent the canonical game pipeline from depending on transient primitive/debug state.
|
||||
|
||||
## Rationale
|
||||
|
||||
This decision keeps the architectural boundary clean.
|
||||
|
||||
`FrameComposer` exists to own the canonical game frame. Debug primitives do not belong to that contract. Pulling them into `FrameComposer` would make the orchestration service responsible for a second semantic domain with different goals:
|
||||
|
||||
- game composition must be deterministic and canonical;
|
||||
- primitive/text overlay must be opportunistic, screen-space, and pipeline-agnostic.
|
||||
|
||||
Keeping overlay/debug outside `FrameComposer` also aligns with the stated product intent: these primitives are useful helpers, but they are not meant to become a second composition language for games.
|
||||
|
||||
Draining them after `hud_fade` preserves the user-visible requirement that debug/overlay content stay truly on top and legible. This is more faithful to the accepted intent than treating primitives as part of HUD or world composition.
|
||||
|
||||
Finally, separating semantic ownership still leaves room for implementation reuse. Raster backends, span paths, and buffer-writing helpers may still be shared internally, provided the public operational model remains separate.
|
||||
|
||||
## Invariantes / Contrato
|
||||
|
||||
### 1. Ownership Boundary
|
||||
|
||||
- `FrameComposer` MUST own only canonical game-frame composition.
|
||||
- Primitive/debug overlay state MUST live outside `FrameComposer`.
|
||||
- The canonical game pipeline MUST NOT depend on primitive/debug overlay state for correctness.
|
||||
|
||||
### 2. Overlay Semantics
|
||||
|
||||
- `gfx.draw_text(...)` and sibling `gfx` primitives SHALL be treated as deferred final overlay/debug operations.
|
||||
- Immediate direct writes to `back` MUST NOT remain the stable operational contract for these primitives.
|
||||
- Final overlay/debug output MUST appear after:
|
||||
- scene composition;
|
||||
- sprite composition;
|
||||
- canonical HUD composition, if present;
|
||||
- `scene_fade`;
|
||||
- `hud_fade`.
|
||||
|
||||
### 3. Separation from Game Composition
|
||||
|
||||
- Primitive/debug overlay MUST NOT be reinterpreted as scene content.
|
||||
- Primitive/debug overlay MUST NOT be reinterpreted as sprite content.
|
||||
- Primitive/debug overlay MUST NOT be the vehicle for canonical HUD composition.
|
||||
- The public `gfx.*` primitive surface SHALL remain pipeline-agnostic relative to `composer.*`.
|
||||
|
||||
### 4. Consistency Across Frame Paths
|
||||
|
||||
- The scene-bound path and no-scene path MUST expose the same final overlay/debug behavior.
|
||||
- Users MUST NOT need to know whether a scene is bound for `gfx.*` primitives to appear as final overlay/debug content.
|
||||
|
||||
### 5. Internal Optimization Contract
|
||||
|
||||
- Internal fast paths for lines, spans, fills, clears, or similar primitive operations MAY be introduced.
|
||||
- Such fast paths MUST preserve the observable deferred overlay/debug semantics.
|
||||
- This decision DOES NOT require fine-grained dirtying or per-primitive-class invalidation in the first migration.
|
||||
|
||||
## Impactos
|
||||
|
||||
### Runtime / Drivers
|
||||
|
||||
- The runtime frame-end sequence must gain a distinct overlay/debug drain stage outside `FrameComposer`.
|
||||
- `gfx.draw_text(...)` and peer primitives can no longer rely on stable immediate framebuffer writes once this migration lands.
|
||||
|
||||
### GFX Backend
|
||||
|
||||
- `Gfx` will need an explicit deferred overlay/debug command path or equivalent subsystem boundary.
|
||||
- Shared raster helpers remain allowed, but the overlay/debug phase must stay semantically distinct from scene/sprite/HUD composition.
|
||||
|
||||
### FrameComposer
|
||||
|
||||
- `FrameComposer` must remain free of primitive/debug overlay ownership.
|
||||
- Any future HUD integration must not collapse that boundary.
|
||||
|
||||
### Spec / Docs
|
||||
|
||||
- The canonical graphics/runtime spec must describe `gfx.*` primitives as deferred final overlay/debug operations rather than stable immediate backbuffer writes.
|
||||
- Documentation that describes frame ordering must show overlay/debug after `hud_fade`.
|
||||
|
||||
### Performance Follow-up
|
||||
|
||||
- `AGD-0010` remains the home for broader renderer performance work, dirtying strategy, and low-level primitive optimization policy.
|
||||
- Primitive optimization carried out under that thread must respect the normative separation established here.
|
||||
|
||||
## Referencias
|
||||
|
||||
- [AGD-0028-deferred-overlay-and-primitive-composition.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0028-deferred-overlay-and-primitive-composition.md)
|
||||
- [AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md)
|
||||
- [DEC-0014-frame-composer-render-integration.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md)
|
||||
- [DEC-0015-frame-composer-public-syscall-surface.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/decisions/DEC-0015-frame-composer-public-syscall-surface.md)
|
||||
|
||||
## Propagacao Necessaria
|
||||
|
||||
- A new implementation plan MUST be created before code changes.
|
||||
- That plan MUST cover:
|
||||
- deferred overlay/debug ownership outside `FrameComposer`;
|
||||
- runtime frame-end ordering changes;
|
||||
- no-scene path parity;
|
||||
- spec/documentation updates for `gfx.*` primitive semantics.
|
||||
- The implementation plan MUST NOT reopen the ownership boundary accepted here.
|
||||
|
||||
## Revision Log
|
||||
|
||||
- 2026-04-18: Initial accepted decision from `AGD-0028`.
|
||||
- 2026-04-18: Linked implementation plan family `PLN-0026` through `PLN-0029`.
|
||||
@ -0,0 +1,93 @@
|
||||
---
|
||||
id: PLN-0026
|
||||
ticket: deferred-overlay-and-primitive-composition
|
||||
title: Plan - GFX Overlay Contract and Spec Propagation
|
||||
status: accepted
|
||||
created: 2026-04-18
|
||||
completed:
|
||||
tags: [gfx, runtime, spec, overlay, primitives, hud]
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Propagate `DEC-0016` into the canonical specs and internal contracts so `gfx.*` primitives are defined as deferred final overlay/debug operations outside `FrameComposer`.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0016` locks a new semantic boundary:
|
||||
|
||||
- `FrameComposer` remains the owner of canonical game-frame composition;
|
||||
- `gfx.*` primitives and `draw_text(...)` become deferred final overlay/debug operations;
|
||||
- that overlay lives outside `FrameComposer` and is drained after `hud_fade`.
|
||||
|
||||
Execution must start by updating the normative contract before implementation changes spread through runtime and drivers.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- update canonical runtime/gfx spec text to describe deferred overlay semantics
|
||||
- update any ABI-facing or developer-facing docs that still imply direct stable writes to `back`
|
||||
- align local contract comments and module docs where they currently imply immediate-write semantics as the stable model
|
||||
|
||||
### Excluded
|
||||
- implementation of the overlay subsystem
|
||||
- runtime frame-end integration
|
||||
- final repository-wide CI
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Update canonical graphics/runtime documentation
|
||||
|
||||
**What:**
|
||||
Publish the new semantic contract for `gfx.*` primitives.
|
||||
|
||||
**How:**
|
||||
- Update the canonical runtime/gfx spec so `gfx.draw_text(...)` and peer primitives are described as deferred final overlay/debug operations.
|
||||
- State explicitly that primitives are not part of canonical scene/sprite/HUD composition.
|
||||
- State the ordering rule that overlay/debug is drained after `hud_fade`.
|
||||
- Ensure the no-scene and scene-bound paths are described consistently.
|
||||
|
||||
**File(s):**
|
||||
- canonical runtime/gfx spec files under `docs/specs/runtime/`
|
||||
|
||||
### Step 2 - Align implementation-facing contract text
|
||||
|
||||
**What:**
|
||||
Remove stale implementation comments that imply immediate stable writes to the framebuffer.
|
||||
|
||||
**How:**
|
||||
- Inspect module-level comments and trait docs in `hal`, `drivers`, and runtime code for language that now contradicts `DEC-0016`.
|
||||
- Update only the contract-bearing comments and docs that materially affect maintenance and implementation clarity.
|
||||
|
||||
**File(s):**
|
||||
- `crates/console/prometeu-hal/src/gfx_bridge.rs`
|
||||
- `crates/console/prometeu-drivers/src/gfx.rs`
|
||||
- `crates/console/prometeu-drivers/src/frame_composer.rs`
|
||||
- runtime-adjacent modules where frame ordering is described
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
- none required for pure doc propagation
|
||||
|
||||
### Integration Tests
|
||||
- none required for pure doc propagation
|
||||
|
||||
### Manual Verification
|
||||
- inspect the updated spec and local contract comments to confirm they no longer describe primitives as stable direct writes to `back`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Canonical spec text describes `gfx.*` primitives as deferred final overlay/debug operations.
|
||||
- [ ] The spec states that overlay/debug is outside `FrameComposer`.
|
||||
- [ ] The spec states that overlay/debug is drained after `hud_fade`.
|
||||
- [ ] Local implementation-facing contract comments no longer imply immediate-write semantics as the stable model.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Source decision: `DEC-0016`
|
||||
|
||||
## Risks
|
||||
|
||||
- Missing a normative doc location would leave code and published contract divergent.
|
||||
- Over-editing local comments could unintentionally restate design choices outside the scope of `DEC-0016`.
|
||||
@ -0,0 +1,104 @@
|
||||
---
|
||||
id: PLN-0027
|
||||
ticket: deferred-overlay-and-primitive-composition
|
||||
title: Plan - Deferred GFX Overlay Subsystem
|
||||
status: accepted
|
||||
created: 2026-04-18
|
||||
completed:
|
||||
tags: [gfx, runtime, overlay, primitives, text, drivers]
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Introduce a dedicated deferred overlay/debug subsystem for `gfx.*` primitives outside `FrameComposer`, with command capture for `draw_text(...)` and the primitive family selected for the first migration.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0016` requires primitive/text overlay ownership to remain outside `FrameComposer` while still allowing shared raster helpers and low-level optimizations internally. The new subsystem must preserve semantic separation from scene/sprite/HUD composition.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- introduce an overlay/debug command queue or equivalent subsystem outside `FrameComposer`
|
||||
- route `gfx.draw_text(...)` into deferred command capture instead of stable direct framebuffer writes
|
||||
- route the chosen V1 primitive family into the same deferred overlay/debug path
|
||||
- keep raster helper reuse allowed without merging semantic ownership
|
||||
|
||||
### Excluded
|
||||
- runtime frame-end sequencing
|
||||
- no-scene/scene parity tests at the runtime level
|
||||
- final repository-wide CI
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Define overlay/debug state ownership in drivers
|
||||
|
||||
**What:**
|
||||
Create the subsystem that owns deferred `gfx.*` overlay/debug commands.
|
||||
|
||||
**How:**
|
||||
- Add a dedicated owner adjacent to `Gfx`/`Hardware`, but not inside `FrameComposer`.
|
||||
- Define the minimal command model required for V1 operations.
|
||||
- Keep the subsystem screen-space and explicitly pipeline-agnostic relative to `composer.*`.
|
||||
|
||||
**File(s):**
|
||||
- `crates/console/prometeu-drivers/src/*`
|
||||
- `crates/console/prometeu-hal/src/*` if bridge traits need extension
|
||||
|
||||
### Step 2 - Route text and selected primitives into deferred capture
|
||||
|
||||
**What:**
|
||||
Stop treating text/primitives as stable direct writes.
|
||||
|
||||
**How:**
|
||||
- Change `gfx.draw_text(...)` to enqueue deferred overlay/debug work.
|
||||
- Migrate the selected V1 primitive set into the same deferred path.
|
||||
- Keep any remaining unmigrated primitives either explicitly out of scope or routed consistently if they are already part of the accepted V1 set.
|
||||
- Preserve internal raster helper reuse where useful.
|
||||
|
||||
**File(s):**
|
||||
- `crates/console/prometeu-drivers/src/gfx.rs`
|
||||
- runtime dispatch call sites that submit `gfx.*` primitives
|
||||
|
||||
### Step 3 - Add local driver-level tests for deferred capture semantics
|
||||
|
||||
**What:**
|
||||
Prove that overlay/debug commands are captured separately from game composition state.
|
||||
|
||||
**How:**
|
||||
- Add tests that assert text/primitives do not need direct stable writes to `back` to survive until overlay drain.
|
||||
- Add tests that assert the overlay owner is independent from `FrameComposer` state.
|
||||
|
||||
**File(s):**
|
||||
- `crates/console/prometeu-drivers/src/gfx.rs`
|
||||
- new or existing driver test modules
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
- command capture tests for `draw_text(...)`
|
||||
- tests for each migrated V1 primitive class
|
||||
- tests proving overlay/debug state is owned outside `FrameComposer`
|
||||
|
||||
### Integration Tests
|
||||
- none in this plan; runtime-level ordering is covered by the next plan
|
||||
|
||||
### Manual Verification
|
||||
- inspect driver ownership boundaries to confirm `FrameComposer` does not gain overlay/debug state
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A dedicated deferred overlay/debug subsystem exists outside `FrameComposer`.
|
||||
- [ ] `gfx.draw_text(...)` is captured as deferred overlay/debug work.
|
||||
- [ ] The selected V1 primitive family is captured through the same subsystem.
|
||||
- [ ] Driver-level tests prove overlay/debug state is operationally separate from canonical game composition state.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Source decision: `DEC-0016`
|
||||
- Prefer to execute after `PLN-0026`
|
||||
|
||||
## Risks
|
||||
|
||||
- Accidentally reusing `FrameComposer` storage or state would violate the accepted ownership boundary.
|
||||
- Migrating only part of the primitive family without explicit scoping could create inconsistent semantics across `gfx.*`.
|
||||
@ -0,0 +1,106 @@
|
||||
---
|
||||
id: PLN-0028
|
||||
ticket: deferred-overlay-and-primitive-composition
|
||||
title: Plan - Runtime Frame-End Overlay Integration and Parity
|
||||
status: accepted
|
||||
created: 2026-04-18
|
||||
completed:
|
||||
tags: [runtime, overlay, frame-composer, no-scene, regression, stress]
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Integrate deferred overlay/debug draining into the runtime frame-end sequence so scene-bound and no-scene frames both present the same final `gfx.*` primitive behavior after `hud_fade`.
|
||||
|
||||
## Background
|
||||
|
||||
After `PLN-0027`, the overlay/debug subsystem will exist but still needs to be drained in the correct place relative to `FrameComposer.render_frame()`, fades, and present/present-adjacent behavior. This plan closes the observable runtime semantics required by `DEC-0016`.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- runtime frame-end ordering changes
|
||||
- scene-bound and no-scene parity
|
||||
- regression coverage for overlay visibility above the canonical game frame
|
||||
- stress-cartridge adjustments if needed to prove text/primitives now survive frame composition
|
||||
|
||||
### Excluded
|
||||
- broad renderer optimization work
|
||||
- final repository-wide CI
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Insert overlay/debug drain into the frame-end path
|
||||
|
||||
**What:**
|
||||
Drain deferred overlay/debug after canonical game composition is complete.
|
||||
|
||||
**How:**
|
||||
- Update the runtime frame-end path so overlay/debug drain occurs after:
|
||||
- `FrameComposer.render_frame()`
|
||||
- `scene_fade`
|
||||
- `hud_fade`
|
||||
- Ensure the same ordering is respected in the no-scene path.
|
||||
|
||||
**File(s):**
|
||||
- `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`
|
||||
- `crates/console/prometeu-drivers/src/hardware.rs`
|
||||
- `crates/console/prometeu-drivers/src/gfx.rs`
|
||||
- any bridge traits needed by the runtime/hardware path
|
||||
|
||||
### Step 2 - Add runtime and driver regressions for final visual ordering
|
||||
|
||||
**What:**
|
||||
Lock the new visible behavior.
|
||||
|
||||
**How:**
|
||||
- Add tests proving `gfx.draw_text(...)` remains visible after scene-backed frame composition.
|
||||
- Add tests proving the same behavior with no scene bound.
|
||||
- Add tests proving overlay/debug sits above the canonical game frame rather than being erased by it.
|
||||
|
||||
**File(s):**
|
||||
- `crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs`
|
||||
- driver-level render tests where helpful
|
||||
|
||||
### Step 3 - Update stress/integration fixtures if needed
|
||||
|
||||
**What:**
|
||||
Restore or improve stress scenarios that rely on visible text/primitives.
|
||||
|
||||
**How:**
|
||||
- Update `pbxgen-stress` or related stress fixtures so text/primitives are once again a valid visible overlay signal.
|
||||
- Keep the stress focused on the new model rather than reintroducing obsolete immediate-write assumptions.
|
||||
|
||||
**File(s):**
|
||||
- `crates/tools/pbxgen-stress/src/lib.rs`
|
||||
- `test-cartridges/stress-console/*`
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
- local ordering tests where runtime integration depends on helper sequencing
|
||||
|
||||
### Integration Tests
|
||||
- runtime tests for scene-bound overlay/debug visibility
|
||||
- runtime tests for no-scene parity
|
||||
- stress/tooling validation that text or primitives are visible again as final overlay/debug
|
||||
|
||||
### Manual Verification
|
||||
- run the stress path and visually confirm overlay/debug survives on top of scene/sprites after frame composition
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] The runtime drains deferred overlay/debug after canonical game composition and after `hud_fade`.
|
||||
- [ ] Scene-bound and no-scene paths expose the same overlay/debug semantics.
|
||||
- [ ] Regression tests prove `draw_text(...)` is no longer erased by scene-backed frame composition.
|
||||
- [ ] Stress/integration fixtures reflect the new final-overlay semantics where applicable.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Source decision: `DEC-0016`
|
||||
- Depends on `PLN-0027`
|
||||
|
||||
## Risks
|
||||
|
||||
- If fades are still applied after overlay/debug drain, the visible contract will contradict `DEC-0016`.
|
||||
- Incomplete parity between scene-bound and no-scene paths would leave runtime behavior mode-dependent.
|
||||
@ -0,0 +1,82 @@
|
||||
---
|
||||
id: PLN-0029
|
||||
ticket: deferred-overlay-and-primitive-composition
|
||||
title: Plan - Final Overlay CI Validation and Polish
|
||||
status: accepted
|
||||
created: 2026-04-18
|
||||
completed:
|
||||
tags: [ci, overlay, runtime, gfx, validation]
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Run the final repository validation path for the deferred overlay/debug migration and perform the last compatibility, formatting, lint, and regression fixes required to close the thread cleanly.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0016` changes visible runtime semantics and touches both specs and code paths around frame composition. A dedicated final-validation plan is needed so the implementation family can close on a clean CI signal rather than leaving integration fallout for later.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- full-tree formatting, lint, and test validation
|
||||
- stress-path smoke validation after overlay integration
|
||||
- final cleanup fixes required to satisfy CI
|
||||
|
||||
### Excluded
|
||||
- new feature work outside the accepted overlay/debug migration
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Run focused validation before full CI
|
||||
|
||||
**What:**
|
||||
Catch local fallout in the touched areas before the full repository pass.
|
||||
|
||||
**How:**
|
||||
- Run targeted tests for drivers, runtime, and `pbxgen-stress`.
|
||||
- Inspect touched files for stale immediate-write assumptions or missed contract updates.
|
||||
|
||||
**File(s):**
|
||||
- touched files from `PLN-0026` through `PLN-0028`
|
||||
|
||||
### Step 2 - Run final repository CI
|
||||
|
||||
**What:**
|
||||
Validate the migration end to end.
|
||||
|
||||
**How:**
|
||||
- Run the repository validation path, including `make ci`.
|
||||
- Fix any final formatting, lint, test, or generated-fixture fallout caused by the overlay/debug migration.
|
||||
- Do not widen scope beyond the accepted thread.
|
||||
|
||||
**File(s):**
|
||||
- repository-wide
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
- all relevant crate unit tests pass after the migration
|
||||
|
||||
### Integration Tests
|
||||
- runtime and stress/integration tests pass after the migration
|
||||
- `make ci` passes
|
||||
|
||||
### Manual Verification
|
||||
- inspect the tree for residual direct-write assumptions or incomplete overlay propagation
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Targeted validation passes for the touched drivers/runtime/stress areas.
|
||||
- [ ] `make ci` passes after the deferred overlay/debug migration family lands.
|
||||
- [ ] No residual contract mismatch remains between spec text and code behavior in the touched thread.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Source decision: `DEC-0016`
|
||||
- Depends on `PLN-0026`, `PLN-0027`, and `PLN-0028`
|
||||
|
||||
## Risks
|
||||
|
||||
- Final CI may surface unrelated renderer assumptions that still expect immediate-write semantics.
|
||||
- Generated cartridge fixtures may drift if regeneration is forgotten during earlier plans.
|
||||
BIN
test-cartridges/stress-console/assets.pa
Normal file
BIN
test-cartridges/stress-console/assets.pa
Normal file
Binary file not shown.
@ -5,5 +5,5 @@
|
||||
"title": "Stress Console",
|
||||
"app_version": "0.1.0",
|
||||
"app_mode": "Game",
|
||||
"capabilities": ["gfx", "log"]
|
||||
"capabilities": ["gfx", "log", "asset"]
|
||||
}
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user