dev/asset-management (#6)

Co-authored-by: Nilton Constantino <nilton.constantino@visma.com>
Reviewed-on: #6
This commit is contained in:
bquarkz 2026-01-22 15:22:13 +00:00
parent 0e6da1433b
commit db338d0c4e
44 changed files with 2697 additions and 865 deletions

View File

@ -18,8 +18,10 @@ pub struct Codegen {
pub symbols: Vec<Symbol>,
instructions: Vec<(Asm, bool)>, // (Asm, has_symbol)
locals: HashMap<String, u32>,
globals: HashMap<String, u32>,
constant_pool: Vec<ConstantPoolEntry>,
next_local: u32,
next_global: u32,
label_count: u32,
}
@ -31,8 +33,10 @@ impl Codegen {
symbols: Vec::new(),
instructions: Vec::new(),
locals: HashMap::new(),
globals: HashMap::new(),
constant_pool: vec![ConstantPoolEntry::Null], // Index 0 is always Null
next_local: 0,
next_global: 0,
label_count: 0,
}
}
@ -52,7 +56,7 @@ impl Codegen {
}
pub fn compile_programs(&mut self, programs: Vec<(String, String, &Program)>) -> Result<Vec<u8>> {
// First pass: collect all functions and their indices
// First pass: collect all functions and global variables
let mut all_functions = Vec::new();
for (file, source, program) in &programs {
for item in &program.body {
@ -63,6 +67,29 @@ impl Codegen {
Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration {
all_functions.push((file.clone(), source.clone(), f.as_ref()));
} else if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration {
for decl in &var.declarations {
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
let name = ident.name.to_string();
if !self.globals.contains_key(&name) {
let id = self.next_global;
self.globals.insert(name, id);
self.next_global += 1;
}
}
}
}
}
Statement::VariableDeclaration(var) => {
for decl in &var.declarations {
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
let name = ident.name.to_string();
if !self.globals.contains_key(&name) {
let id = self.next_global;
self.globals.insert(name, id);
self.next_global += 1;
}
}
}
}
_ => {}
@ -70,8 +97,8 @@ impl Codegen {
}
}
// Find tick function (should be in the first program, which is the entry)
let mut tick_fn_name = None;
// Find frame function (should be in the first program, which is the entry)
let mut frame_fn_name = None;
if let Some((_, _, entry_program)) = programs.first() {
for item in &entry_program.body {
let f_opt = match item {
@ -88,8 +115,8 @@ impl Codegen {
if let Some(f) = f_opt {
if let Some(ident) = &f.id {
if ident.name == "tick" {
tick_fn_name = Some(ident.name.to_string());
if ident.name == "frame" {
frame_fn_name = Some(ident.name.to_string());
break;
}
}
@ -97,11 +124,45 @@ impl Codegen {
}
}
let tick_fn_name = tick_fn_name.ok_or_else(|| anyhow!("export function tick() not found in entry file"))?;
let frame_fn_name = frame_fn_name.ok_or_else(|| anyhow!("export function frame() not found in entry file"))?;
// Entry point: loop calling tick
// Initialize globals
for (file, source, program) in &programs {
self.file_name = file.clone();
self.source_text = source.clone();
for item in &program.body {
let var_opt = match item {
Statement::VariableDeclaration(var) => Some(var.as_ref()),
Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration {
Some(var.as_ref())
} else {
None
}
}
_ => None,
};
if let Some(var) = var_opt {
for decl in &var.declarations {
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
let name = ident.name.to_string();
let id = *self.globals.get(&name).unwrap();
if let Some(init) = &decl.init {
self.compile_expr(init)?;
} else {
self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span);
}
self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], decl.span);
}
}
}
}
}
// Entry point: loop calling frame
self.emit_label("entry".to_string());
self.emit_op(OpCode::Call, vec![Operand::Label(tick_fn_name), Operand::U32(0)], Span::default());
self.emit_op(OpCode::Call, vec![Operand::Label(frame_fn_name), Operand::U32(0)], Span::default());
self.emit_op(OpCode::Pop, vec![], Span::default());
self.emit_op(OpCode::FrameSync, vec![], Span::default());
self.emit_op(OpCode::Jmp, vec![Operand::Label("entry".to_string())], Span::default());
@ -221,7 +282,7 @@ impl Codegen {
if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 {
self.emit_op(OpCode::PushI32, vec![Operand::I32(val as i32)], n.span);
} else {
self.emit_op(OpCode::PushI64, vec![Operand::I64(val as i64)], n.span);
self.emit_op(OpCode::PushF64, vec![Operand::F64(val)], n.span);
}
}
Expression::BooleanLiteral(b) => {
@ -238,6 +299,8 @@ impl Codegen {
let name = ident.name.to_string();
if let Some(&id) = self.locals.get(&name) {
self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], ident.span);
} else if let Some(&id) = self.globals.get(&name) {
self.emit_op(OpCode::GetGlobal, vec![Operand::U32(id)], ident.span);
} else {
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
}
@ -249,6 +312,9 @@ impl Codegen {
if let Some(&id) = self.locals.get(&name) {
self.emit_op(OpCode::SetLocal, vec![Operand::U32(id)], assign.span);
self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], assign.span);
} else if let Some(&id) = self.globals.get(&name) {
self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], assign.span);
self.emit_op(OpCode::GetGlobal, vec![Operand::U32(id)], assign.span);
} else {
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
}

View File

@ -80,6 +80,14 @@ pub enum DebugEvent {
vm_steps: u32,
syscalls: u32,
cycles: u64,
host_cpu_time_us: u64,
violations: u32,
gfx_used_bytes: usize,
gfx_inflight_bytes: usize,
gfx_slots_occupied: u32,
audio_used_bytes: usize,
audio_inflight_bytes: usize,
audio_slots_occupied: u32,
},
#[serde(rename = "cert")]
Cert {

View File

@ -46,8 +46,11 @@ impl Firmware {
///
/// This method is called exactly once per Host frame (60Hz).
/// It updates peripheral signals and delegates the logic to the current state.
pub fn step_frame(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
// 1. Update peripheral state using the latest signals from the Host.
pub fn tick(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
// 0. Process asset commits at the beginning of the frame boundary.
hw.assets_mut().apply_commits();
// 1. Update the peripheral state using the latest signals from the Host.
// This ensures input is consistent throughout the entire update.
hw.pad_mut().begin_frame(signals);
hw.touch_mut().begin_frame(signals);

View File

@ -11,7 +11,7 @@ impl GameRunningStep {
}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
let result = ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw);
let result = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw);
if !ctx.os.logical_frame_active {
ctx.hw.gfx_mut().present();

View File

@ -21,7 +21,7 @@ impl HubHomeStep {
ctx.hub.window_manager.remove_window(focused_id);
} else {
// System App runs here, drawing over the Hub background
error = ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw);
error = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw);
}
}

View File

@ -11,6 +11,14 @@ pub struct LoadCartridgeStep {
impl LoadCartridgeStep {
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, format!("Loading cartridge: {}", self.cartridge.title));
// Initialize Asset Manager
ctx.hw.assets_mut().initialize_for_cartridge(
self.cartridge.asset_table.clone(),
self.cartridge.preload.clone(),
self.cartridge.assets.clone()
);
ctx.os.initialize_vm(ctx.vm, &self.cartridge);
}

View File

@ -1,8 +1,7 @@
use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep};
use crate::firmware::prometeu_context::PrometeuContext;
use crate::hardware::LoopMode;
use crate::model::Color;
use crate::log::{LogLevel, LogSource};
use crate::model::Color;
#[derive(Debug, Clone)]
pub struct SplashScreenStep {
@ -13,9 +12,7 @@ impl SplashScreenStep {
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
// Play sound on enter
if let Some(sample) = ctx.os.sample_square.clone() {
ctx.hw.audio_mut().play(sample, 0, 255, 127, 1.0, 0, LoopMode::Off);
}
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {

View File

@ -0,0 +1,864 @@
use crate::hardware::memory_banks::{TileBankPoolInstaller, SoundBankPoolInstaller};
use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, SlotRef, SlotStats, TileBank, TileSize, SoundBank, Sample, PreloadEntry};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::Instant;
/// Resident metadata for a decoded/materialized asset inside a BankPolicy.
#[derive(Debug)]
pub struct ResidentEntry<T> {
/// The resident, materialized object.
pub value: Arc<T>,
/// Resident size in bytes (post-decode). Used for telemetry/budgets.
pub bytes: usize,
// /// Pin count (optional): if > 0, entry should not be evicted by policy.
// pub pins: u32,
/// Telemetry / profiling fields (optional but useful).
pub loads: u64,
pub last_used: Instant,
}
impl<T> ResidentEntry<T> {
pub fn new(value: Arc<T>, bytes: usize) -> Self {
Self {
value,
bytes,
// pins: 0,
loads: 1,
last_used: Instant::now(),
}
}
}
/// Encapsulates the residency and staging policy for a specific type of asset.
/// This is internal to the AssetManager and not visible to peripherals.
pub struct BankPolicy<T> {
/// Dedup table: asset_id -> resident entry (value + telemetry).
resident: Arc<RwLock<HashMap<u32, ResidentEntry<T>>>>,
/// Staging area: handle -> value ready to commit.
staging: Arc<RwLock<HashMap<HandleId, Arc<T>>>>,
}
impl<T> BankPolicy<T> {
pub fn new() -> Self {
Self {
resident: Arc::new(RwLock::new(HashMap::new())),
staging: Arc::new(RwLock::new(HashMap::new())),
}
}
/// Try get a resident value by asset_id (dedupe path).
pub fn get_resident(&self, asset_id: u32) -> Option<Arc<T>> {
let mut map = self.resident.write().unwrap();
let entry = map.get_mut(&asset_id)?;
entry.last_used = Instant::now();
Some(Arc::clone(&entry.value))
}
/// Insert or reuse a resident entry. Returns the resident Arc<T>.
pub fn put_resident(&self, asset_id: u32, value: Arc<T>, bytes: usize) -> Arc<T> {
let mut map = self.resident.write().unwrap();
match map.get_mut(&asset_id) {
Some(existing) => {
existing.last_used = Instant::now();
existing.loads += 1;
Arc::clone(&existing.value)
}
None => {
let entry = ResidentEntry::new(Arc::clone(&value), bytes);
map.insert(asset_id, entry);
value
}
}
}
/// Place a value into staging for a given handle.
pub fn stage(&self, handle: HandleId, value: Arc<T>) {
self.staging.write().unwrap().insert(handle, value);
}
/// Take staged value (used by commit path).
pub fn take_staging(&self, handle: HandleId) -> Option<Arc<T>> {
self.staging.write().unwrap().remove(&handle)
}
pub fn clear(&self) {
self.resident.write().unwrap().clear();
self.staging.write().unwrap().clear();
}
}
pub struct AssetManager {
assets: Arc<RwLock<HashMap<u32, AssetEntry>>>,
name_to_id: Arc<RwLock<HashMap<String, u32>>>,
handles: Arc<RwLock<HashMap<HandleId, LoadHandleInfo>>>,
next_handle_id: Mutex<HandleId>,
assets_data: Arc<RwLock<Vec<u8>>>,
/// Narrow hardware interfaces
gfx_installer: Arc<dyn TileBankPoolInstaller>,
sound_installer: Arc<dyn SoundBankPoolInstaller>,
/// Track what is installed in each hardware slot (for stats/info).
gfx_slots: Arc<RwLock<[Option<u32>; 16]>>,
sound_slots: Arc<RwLock<[Option<u32>; 16]>>,
/// Residency policy for GFX tile banks.
gfx_policy: BankPolicy<TileBank>,
/// Residency policy for sound banks.
sound_policy: BankPolicy<SoundBank>,
// Commits that are ready to be applied at the next frame boundary.
pending_commits: Mutex<Vec<HandleId>>,
}
struct LoadHandleInfo {
_asset_id: u32,
slot: SlotRef,
status: LoadStatus,
}
impl AssetManager {
pub fn new(
assets: Vec<AssetEntry>,
assets_data: Vec<u8>,
gfx_installer: Arc<dyn TileBankPoolInstaller>,
sound_installer: Arc<dyn SoundBankPoolInstaller>,
) -> Self {
let mut asset_map = HashMap::new();
let mut name_to_id = HashMap::new();
for entry in assets {
name_to_id.insert(entry.asset_name.clone(), entry.asset_id);
asset_map.insert(entry.asset_id, entry);
}
Self {
assets: Arc::new(RwLock::new(asset_map)),
name_to_id: Arc::new(RwLock::new(name_to_id)),
gfx_installer,
sound_installer,
gfx_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
gfx_policy: BankPolicy::new(),
sound_policy: BankPolicy::new(),
handles: Arc::new(RwLock::new(HashMap::new())),
next_handle_id: Mutex::new(1),
assets_data: Arc::new(RwLock::new(assets_data)),
pending_commits: Mutex::new(Vec::new()),
}
}
pub fn initialize_for_cartridge(&self, assets: Vec<AssetEntry>, preload: Vec<PreloadEntry>, assets_data: Vec<u8>) {
self.shutdown();
{
let mut asset_map = self.assets.write().unwrap();
let mut name_to_id = self.name_to_id.write().unwrap();
asset_map.clear();
name_to_id.clear();
for entry in assets.iter() {
name_to_id.insert(entry.asset_name.clone(), entry.asset_id);
asset_map.insert(entry.asset_id, entry.clone());
}
}
*self.assets_data.write().unwrap() = assets_data;
// Perform Preload for assets in the preload list
for item in preload {
let entry_opt = {
let assets = self.assets.read().unwrap();
let name_to_id = self.name_to_id.read().unwrap();
name_to_id.get(&item.asset_name)
.and_then(|id| assets.get(id))
.cloned()
};
if let Some(entry) = entry_opt {
let slot_index = item.slot;
match entry.bank_type {
BankType::TILES => {
if let Ok(bank) = Self::perform_load_tile_bank(&entry, self.assets_data.clone()) {
let bank_arc = Arc::new(bank);
self.gfx_policy.put_resident(entry.asset_id, Arc::clone(&bank_arc), entry.decoded_size as usize);
self.gfx_installer.install_tile_bank(slot_index, bank_arc);
let mut slots = self.gfx_slots.write().unwrap();
if slot_index < slots.len() {
slots[slot_index] = Some(entry.asset_id);
}
println!("[AssetManager] Preloaded tile asset '{}' (id: {}) into slot {}", entry.asset_name, entry.asset_id, slot_index);
} else {
eprintln!("[AssetManager] Failed to preload tile asset '{}'", entry.asset_name);
}
}
BankType::SOUNDS => {
if let Ok(bank) = Self::perform_load_sound_bank(&entry, self.assets_data.clone()) {
let bank_arc = Arc::new(bank);
self.sound_policy.put_resident(entry.asset_id, Arc::clone(&bank_arc), entry.decoded_size as usize);
self.sound_installer.install_sound_bank(slot_index, bank_arc);
let mut slots = self.sound_slots.write().unwrap();
if slot_index < slots.len() {
slots[slot_index] = Some(entry.asset_id);
}
println!("[AssetManager] Preloaded sound asset '{}' (id: {}) into slot {}", entry.asset_name, entry.asset_id, slot_index);
} else {
eprintln!("[AssetManager] Failed to preload sound asset '{}'", entry.asset_name);
}
}
}
} else {
eprintln!("[AssetManager] Preload failed: asset '{}' not found in table", item.asset_name);
}
}
}
pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, String> {
let entry = {
let assets = self.assets.read().unwrap();
let name_to_id = self.name_to_id.read().unwrap();
let id = name_to_id.get(asset_name).ok_or_else(|| format!("Asset not found: {}", asset_name))?;
assets.get(id).ok_or_else(|| format!("Asset ID {} not found in table", id))?.clone()
};
let asset_id = entry.asset_id;
if slot.asset_type != entry.bank_type {
return Err("INCOMPATIBLE_SLOT_KIND".to_string());
}
let mut next_id = self.next_handle_id.lock().unwrap();
let handle_id = *next_id;
*next_id += 1;
// Check if already resident (Dedup)
let already_resident = match entry.bank_type {
BankType::TILES => {
if let Some(bank) = self.gfx_policy.get_resident(asset_id) {
self.gfx_policy.stage(handle_id, bank);
true
} else { false }
}
BankType::SOUNDS => {
if let Some(bank) = self.sound_policy.get_resident(asset_id) {
self.sound_policy.stage(handle_id, bank);
true
} else { false }
}
};
if already_resident {
self.handles.write().unwrap().insert(handle_id, LoadHandleInfo {
_asset_id: asset_id,
slot,
status: LoadStatus::READY,
});
return Ok(handle_id);
}
// Not resident, start loading
self.handles.write().unwrap().insert(handle_id, LoadHandleInfo {
_asset_id: asset_id,
slot,
status: LoadStatus::PENDING,
});
let handles = self.handles.clone();
let assets_data = self.assets_data.clone();
let entry_clone = entry.clone();
// Capture policies for the worker thread
let gfx_policy_resident = Arc::clone(&self.gfx_policy.resident);
let gfx_policy_staging = Arc::clone(&self.gfx_policy.staging);
let sound_policy_resident = Arc::clone(&self.sound_policy.resident);
let sound_policy_staging = Arc::clone(&self.sound_policy.staging);
thread::spawn(move || {
// Update status to LOADING
{
let mut handles_map = handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle_id) {
if h.status == LoadStatus::PENDING {
h.status = LoadStatus::LOADING;
} else {
return;
}
} else {
return;
}
}
match entry_clone.bank_type {
BankType::TILES => {
let result = Self::perform_load_tile_bank(&entry_clone, assets_data);
if let Ok(tilebank) = result {
let bank_arc = Arc::new(tilebank);
let resident_arc = {
let mut map = gfx_policy_resident.write().unwrap();
if let Some(existing) = map.get_mut(&asset_id) {
existing.last_used = Instant::now();
existing.loads += 1;
Arc::clone(&existing.value)
} else {
let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize);
map.insert(asset_id, entry);
bank_arc
}
};
gfx_policy_staging.write().unwrap().insert(handle_id, resident_arc);
let mut handles_map = handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle_id) {
if h.status == LoadStatus::LOADING { h.status = LoadStatus::READY; }
}
} else {
let mut handles_map = handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle_id) { h.status = LoadStatus::ERROR; }
}
}
BankType::SOUNDS => {
let result = Self::perform_load_sound_bank(&entry_clone, assets_data);
if let Ok(soundbank) = result {
let bank_arc = Arc::new(soundbank);
let resident_arc = {
let mut map = sound_policy_resident.write().unwrap();
if let Some(existing) = map.get_mut(&asset_id) {
existing.last_used = Instant::now();
existing.loads += 1;
Arc::clone(&existing.value)
} else {
let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize);
map.insert(asset_id, entry);
bank_arc
}
};
sound_policy_staging.write().unwrap().insert(handle_id, resident_arc);
let mut handles_map = handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle_id) {
if h.status == LoadStatus::LOADING { h.status = LoadStatus::READY; }
}
} else {
let mut handles_map = handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle_id) { h.status = LoadStatus::ERROR; }
}
}
}
});
Ok(handle_id)
}
fn perform_load_tile_bank(entry: &AssetEntry, assets_data: Arc<RwLock<Vec<u8>>>) -> Result<TileBank, String> {
if entry.codec != "RAW" {
return Err(format!("Unsupported codec: {}", entry.codec));
}
let assets_data = assets_data.read().unwrap();
let start = entry.offset as usize;
let end = start + entry.size as usize;
if end > assets_data.len() {
return Err("Asset offset/size out of bounds".to_string());
}
let buffer = &assets_data[start..end];
// Decode TILEBANK metadata
let tile_size_val = entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?;
let width = entry.metadata.get("width").and_then(|v| v.as_u64()).ok_or("Missing width")? as usize;
let height = entry.metadata.get("height").and_then(|v| v.as_u64()).ok_or("Missing height")? as usize;
let tile_size = match tile_size_val {
8 => TileSize::Size8,
16 => TileSize::Size16,
32 => TileSize::Size32,
_ => return Err(format!("Invalid tile_size: {}", tile_size_val)),
};
let pixel_data_size = width * height;
if buffer.len() < pixel_data_size + 2048 {
return Err("Buffer too small for TILEBANK".to_string());
}
let pixel_indices = buffer[0..pixel_data_size].to_vec();
let palette_data = &buffer[pixel_data_size..pixel_data_size + 2048];
let mut palettes = [[Color::BLACK; 16]; 64];
for p in 0..64 {
for c in 0..16 {
let offset = (p * 16 + c) * 2;
let color_raw = u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]);
palettes[p][c] = Color(color_raw);
}
}
Ok(TileBank {
tile_size,
width,
height,
pixel_indices,
palettes,
})
}
fn perform_load_sound_bank(entry: &AssetEntry, assets_data: Arc<RwLock<Vec<u8>>>) -> Result<SoundBank, String> {
if entry.codec != "RAW" {
return Err(format!("Unsupported codec: {}", entry.codec));
}
let assets_data = assets_data.read().unwrap();
let start = entry.offset as usize;
let end = start + entry.size as usize;
if end > assets_data.len() {
return Err("Asset offset/size out of bounds".to_string());
}
let buffer = &assets_data[start..end];
let sample_rate = entry.metadata.get("sample_rate").and_then(|v| v.as_u64()).unwrap_or(44100) as u32;
let mut data = Vec::with_capacity(buffer.len() / 2);
for i in (0..buffer.len()).step_by(2) {
if i + 1 < buffer.len() {
data.push(i16::from_le_bytes([buffer[i], buffer[i+1]]));
}
}
let sample = Arc::new(Sample::new(sample_rate, data));
Ok(SoundBank::new(vec![sample]))
}
pub fn status(&self, handle: HandleId) -> LoadStatus {
self.handles.read().unwrap().get(&handle).map(|h| h.status).unwrap_or(LoadStatus::ERROR)
}
pub fn commit(&self, handle: HandleId) {
let mut handles_map = self.handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle) {
if h.status == LoadStatus::READY {
self.pending_commits.lock().unwrap().push(handle);
}
}
}
pub fn cancel(&self, handle: HandleId) {
let mut handles_map = self.handles.write().unwrap();
if let Some(h) = handles_map.get_mut(&handle) {
match h.status {
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
h.status = LoadStatus::CANCELED;
}
_ => {}
}
}
self.gfx_policy.take_staging(handle);
self.sound_policy.take_staging(handle);
}
pub fn apply_commits(&self) {
let mut pending = self.pending_commits.lock().unwrap();
let mut handles = self.handles.write().unwrap();
for handle_id in pending.drain(..) {
if let Some(h) = handles.get_mut(&handle_id) {
if h.status == LoadStatus::READY {
match h.slot.asset_type {
BankType::TILES => {
if let Some(bank) = self.gfx_policy.take_staging(handle_id) {
self.gfx_installer.install_tile_bank(h.slot.index, bank);
let mut slots = self.gfx_slots.write().unwrap();
if h.slot.index < slots.len() {
slots[h.slot.index] = Some(h._asset_id);
}
h.status = LoadStatus::COMMITTED;
}
}
BankType::SOUNDS => {
if let Some(bank) = self.sound_policy.take_staging(handle_id) {
self.sound_installer.install_sound_bank(h.slot.index, bank);
let mut slots = self.sound_slots.write().unwrap();
if h.slot.index < slots.len() {
slots[h.slot.index] = Some(h._asset_id);
}
h.status = LoadStatus::COMMITTED;
}
}
}
}
}
}
}
pub fn bank_info(&self, kind: BankType) -> BankStats {
match kind {
BankType::TILES => {
let mut used_bytes = 0;
{
let resident = self.gfx_policy.resident.read().unwrap();
for entry in resident.values() {
used_bytes += entry.bytes;
}
}
let mut inflight_bytes = 0;
{
let staging = self.gfx_policy.staging.read().unwrap();
let assets = self.assets.read().unwrap();
let handles = self.handles.read().unwrap();
for (handle_id, _) in staging.iter() {
if let Some(h) = handles.get(handle_id) {
if let Some(entry) = assets.get(&h._asset_id) {
inflight_bytes += entry.decoded_size as usize;
}
}
}
}
let mut slots_occupied = 0;
{
let slots = self.gfx_slots.read().unwrap();
for s in slots.iter() {
if s.is_some() { slots_occupied += 1; }
}
}
BankStats {
total_bytes: 16 * 1024 * 1024,
used_bytes,
free_bytes: (16usize * 1024 * 1024).saturating_sub(used_bytes),
inflight_bytes,
slot_count: 16,
slots_occupied,
}
}
BankType::SOUNDS => {
let mut used_bytes = 0;
{
let resident = self.sound_policy.resident.read().unwrap();
for entry in resident.values() {
used_bytes += entry.bytes;
}
}
let mut inflight_bytes = 0;
{
let staging = self.sound_policy.staging.read().unwrap();
let assets = self.assets.read().unwrap();
let handles = self.handles.read().unwrap();
for (handle_id, _) in staging.iter() {
if let Some(h) = handles.get(handle_id) {
if let Some(entry) = assets.get(&h._asset_id) {
inflight_bytes += entry.decoded_size as usize;
}
}
}
}
let mut slots_occupied = 0;
{
let slots = self.sound_slots.read().unwrap();
for s in slots.iter() {
if s.is_some() { slots_occupied += 1; }
}
}
BankStats {
total_bytes: 32 * 1024 * 1024,
used_bytes,
free_bytes: (32usize * 1024 * 1024).saturating_sub(used_bytes),
inflight_bytes,
slot_count: 16,
slots_occupied,
}
}
}
}
pub fn slot_info(&self, slot: SlotRef) -> SlotStats {
match slot.asset_type {
BankType::TILES => {
let slots = self.gfx_slots.read().unwrap();
let asset_id = slots.get(slot.index).and_then(|s| s.clone());
let (bytes, asset_name) = if let Some(id) = &asset_id {
let bytes = self.gfx_policy.resident.read().unwrap()
.get(id)
.map(|entry| entry.bytes)
.unwrap_or(0);
let name = self.assets.read().unwrap().get(id).map(|e| e.asset_name.clone());
(bytes, name)
} else {
(0, None)
};
SlotStats {
asset_id,
asset_name,
generation: 0,
resident_bytes: bytes,
}
}
BankType::SOUNDS => {
let slots = self.sound_slots.read().unwrap();
let asset_id = slots.get(slot.index).and_then(|s| s.clone());
let (bytes, asset_name) = if let Some(id) = &asset_id {
let bytes = self.sound_policy.resident.read().unwrap()
.get(id)
.map(|entry| entry.bytes)
.unwrap_or(0);
let name = self.assets.read().unwrap().get(id).map(|e| e.asset_name.clone());
(bytes, name)
} else {
(0, None)
};
SlotStats {
asset_id,
asset_name,
generation: 0,
resident_bytes: bytes,
}
}
}
}
pub fn find_slot_by_name(&self, asset_name: &str, kind: BankType) -> Option<u8> {
let asset_id = {
let name_to_id = self.name_to_id.read().unwrap();
*name_to_id.get(asset_name)?
};
match kind {
BankType::TILES => {
let slots = self.gfx_slots.read().unwrap();
slots.iter().position(|&s| s == Some(asset_id)).map(|p| p as u8)
}
BankType::SOUNDS => {
let slots = self.sound_slots.read().unwrap();
slots.iter().position(|&s| s == Some(asset_id)).map(|p| p as u8)
}
}
}
pub fn shutdown(&self) {
self.gfx_policy.clear();
self.sound_policy.clear();
self.handles.write().unwrap().clear();
self.pending_commits.lock().unwrap().clear();
self.gfx_slots.write().unwrap().fill(None);
self.sound_slots.write().unwrap().fill(None);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hardware::memory_banks::{TileBankPoolAccess, SoundBankPoolAccess, MemoryBanks};
#[test]
fn test_asset_loading_flow() {
let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let mut data = vec![1u8; 256];
data.extend_from_slice(&[0u8; 2048]);
let asset_entry = AssetEntry {
asset_id: 0,
asset_name: "test_tiles".to_string(),
bank_type: BankType::TILES,
offset: 0,
size: data.len() as u64,
decoded_size: data.len() as u64,
codec: "RAW".to_string(),
metadata: serde_json::json!({
"tile_size": 16,
"width": 16,
"height": 16
}),
};
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
let slot = SlotRef::gfx(0);
let handle = am.load("test_tiles", slot).expect("Should start loading");
let mut status = am.status(handle);
let start = Instant::now();
while status != LoadStatus::READY && start.elapsed().as_secs() < 5 {
thread::sleep(std::time::Duration::from_millis(10));
status = am.status(handle);
}
assert_eq!(status, LoadStatus::READY);
{
let staging = am.gfx_policy.staging.read().unwrap();
assert!(staging.contains_key(&handle));
}
am.commit(handle);
am.apply_commits();
assert_eq!(am.status(handle), LoadStatus::COMMITTED);
assert!(banks.tile_bank_slot(0).is_some());
}
#[test]
fn test_asset_dedup() {
let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let mut data = vec![1u8; 256];
data.extend_from_slice(&[0u8; 2048]);
let asset_entry = AssetEntry {
asset_id: 0,
asset_name: "test_tiles".to_string(),
bank_type: BankType::TILES,
offset: 0,
size: data.len() as u64,
decoded_size: data.len() as u64,
codec: "RAW".to_string(),
metadata: serde_json::json!({
"tile_size": 16,
"width": 16,
"height": 16
}),
};
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
let handle1 = am.load("test_tiles", SlotRef::gfx(0)).unwrap();
let start = Instant::now();
while am.status(handle1) != LoadStatus::READY && start.elapsed().as_secs() < 5 {
thread::sleep(std::time::Duration::from_millis(10));
}
let handle2 = am.load("test_tiles", SlotRef::gfx(1)).unwrap();
assert_eq!(am.status(handle2), LoadStatus::READY);
let staging = am.gfx_policy.staging.read().unwrap();
let bank1 = staging.get(&handle1).unwrap();
let bank2 = staging.get(&handle2).unwrap();
assert!(Arc::ptr_eq(bank1, bank2));
}
#[test]
fn test_sound_asset_loading() {
let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
// 100 samples of 16-bit PCM (zeros)
let data = vec![0u8; 200];
let asset_entry = AssetEntry {
asset_id: 1,
asset_name: "test_sound".to_string(),
bank_type: BankType::SOUNDS,
offset: 0,
size: data.len() as u64,
decoded_size: data.len() as u64,
codec: "RAW".to_string(),
metadata: serde_json::json!({
"sample_rate": 44100
}),
};
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
let slot = SlotRef::audio(0);
let handle = am.load("test_sound", slot).expect("Should start loading");
let start = Instant::now();
while am.status(handle) != LoadStatus::READY && start.elapsed().as_secs() < 5 {
thread::sleep(std::time::Duration::from_millis(10));
}
assert_eq!(am.status(handle), LoadStatus::READY);
am.commit(handle);
am.apply_commits();
assert_eq!(am.status(handle), LoadStatus::COMMITTED);
assert!(banks.sound_bank_slot(0).is_some());
}
#[test]
fn test_preload_on_init() {
let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let data = vec![0u8; 200];
let asset_entry = AssetEntry {
asset_id: 2,
asset_name: "preload_sound".to_string(),
bank_type: BankType::SOUNDS,
offset: 0,
size: data.len() as u64,
decoded_size: data.len() as u64,
codec: "RAW".to_string(),
metadata: serde_json::json!({
"sample_rate": 44100
}),
};
let preload = vec![
PreloadEntry { asset_name: "preload_sound".to_string(), slot: 5 }
];
let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer);
// Before init, slot 5 is empty
assert!(banks.sound_bank_slot(5).is_none());
am.initialize_for_cartridge(vec![asset_entry], preload, data);
// After init, slot 5 should be occupied because of preload
assert!(banks.sound_bank_slot(5).is_some());
assert_eq!(am.slot_info(SlotRef::audio(5)).asset_id, Some(2));
}
#[test]
fn test_find_slot_by_name() {
let banks = Arc::new(MemoryBanks::new());
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
let mut data = vec![0u8; 256]; // pixels
data.extend_from_slice(&[0u8; 2048]); // palette
let asset_entry = AssetEntry {
asset_id: 10,
asset_name: "my_tiles".to_string(),
bank_type: BankType::TILES,
offset: 0,
size: data.len() as u64,
decoded_size: data.len() as u64,
codec: "RAW".to_string(),
metadata: serde_json::json!({ "tile_size": 16, "width": 16, "height": 16 }),
};
let preload = vec![
PreloadEntry { asset_name: "my_tiles".to_string(), slot: 3 }
];
let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer);
am.initialize_for_cartridge(vec![asset_entry], preload, data);
assert_eq!(am.find_slot_by_name("my_tiles", BankType::TILES), Some(3));
assert_eq!(am.find_slot_by_name("unknown", BankType::TILES), None);
assert_eq!(am.find_slot_by_name("my_tiles", BankType::SOUNDS), None);
}
}

View File

@ -1,3 +1,4 @@
use crate::hardware::memory_banks::SoundBankPoolAccess;
use crate::model::Sample;
use std::sync::Arc;
@ -21,8 +22,10 @@ pub enum LoopMode {
/// The Core maintains this state to provide information to the App (e.g., is_playing),
/// but the actual real-time mixing is performed by the Host using commands.
pub struct Channel {
/// Reference to the PCM data being played.
/// The actual sample data being played.
pub sample: Option<Arc<Sample>>,
/// Whether this channel is currently active.
pub active: bool,
/// Current playback position within the sample (fractional for pitch shifting).
pub pos: f64,
/// Playback speed multiplier (1.0 = original speed).
@ -41,6 +44,7 @@ impl Default for Channel {
fn default() -> Self {
Self {
sample: None,
active: false,
pos: 0.0,
pitch: 1.0,
volume: 255,
@ -85,6 +89,10 @@ pub enum AudioCommand {
voice_id: usize,
pitch: f64,
},
/// Pause all audio processing.
MasterPause,
/// Resume audio processing.
MasterResume,
}
/// PROMETEU Audio Subsystem.
@ -97,68 +105,99 @@ pub struct Audio {
pub voices: [Channel; MAX_CHANNELS],
/// Queue of pending commands to be processed by the Host mixer.
pub commands: Vec<AudioCommand>,
/// Interface to access sound memory banks.
pub sound_banks: Arc<dyn SoundBankPoolAccess>,
}
impl Audio {
/// Initializes the audio system with empty voices.
pub fn new() -> Self {
const EMPTY_CHANNEL: Channel = Channel {
sample: None,
pos: 0.0,
pitch: 1.0,
volume: 255,
pan: 127,
loop_mode: LoopMode::Off,
priority: 0,
};
/// Initializes the audio system with empty voices and sound bank access.
pub fn new(sound_banks: Arc<dyn SoundBankPoolAccess>) -> Self {
Self {
voices: [EMPTY_CHANNEL; MAX_CHANNELS],
voices: std::array::from_fn(|_| Channel::default()),
commands: Vec::new(),
sound_banks,
}
}
pub fn play(&mut self, sample: Arc<Sample>, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) {
if voice_id < MAX_CHANNELS {
self.commands.push(AudioCommand::Play {
sample,
voice_id,
volume,
pan,
pitch,
priority,
loop_mode,
});
pub fn play(&mut self, bank_id: u8, sample_id: u16, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) {
if voice_id >= MAX_CHANNELS {
return;
}
// Resolve the sample from the hardware pools
let sample = self.sound_banks.sound_bank_slot(bank_id as usize)
.and_then(|bank| bank.samples.get(sample_id as usize).map(Arc::clone));
if let Some(s) = sample {
println!("[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.", bank_id, sample_id, voice_id);
self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode);
} else {
eprintln!("[Audio] Failed to resolve sample from bank {} sample {}.", bank_id, sample_id);
}
}
pub fn play_sample(&mut self, sample: Arc<Sample>, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) {
if voice_id >= MAX_CHANNELS {
return;
}
// Update local state
self.voices[voice_id] = Channel {
sample: Some(Arc::clone(&sample)),
active: true,
pos: 0.0,
pitch,
volume,
pan,
loop_mode,
priority,
};
// Push command to the host
self.commands.push(AudioCommand::Play {
sample,
voice_id,
volume,
pan,
pitch,
priority,
loop_mode,
});
}
pub fn stop(&mut self, voice_id: usize) {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].active = false;
self.voices[voice_id].sample = None;
self.commands.push(AudioCommand::Stop { voice_id });
}
}
pub fn set_volume(&mut self, voice_id: usize, volume: u8) {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].volume = volume;
self.commands.push(AudioCommand::SetVolume { voice_id, volume });
}
}
pub fn set_pan(&mut self, voice_id: usize, pan: u8) {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].pan = pan;
self.commands.push(AudioCommand::SetPan { voice_id, pan });
}
}
pub fn set_pitch(&mut self, voice_id: usize, pitch: f64) {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].pitch = pitch;
self.commands.push(AudioCommand::SetPitch { voice_id, pitch });
}
}
pub fn is_playing(&self, voice_id: usize) -> bool {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].sample.is_some()
self.voices[voice_id].active
} else {
false
}

View File

@ -1,5 +1,6 @@
use crate::hardware::memory_banks::TileBankPoolAccess;
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize};
use std::mem::size_of;
use std::sync::Arc;
/// Blending modes inspired by classic 16-bit hardware.
/// Defines how source pixels are combined with existing pixels in the framebuffer.
@ -46,8 +47,8 @@ pub struct Gfx {
pub layers: [ScrollableTileLayer; 4],
/// 1 fixed layer for User Interface.
pub hud: HudTileLayer,
/// Up to 16 sets of graphical assets (tiles + palettes).
pub banks: [Option<TileBank>; 16],
/// Interface to access graphical memory banks.
pub tile_banks: Arc<dyn TileBankPoolAccess>,
/// Hardware sprites (Object Attribute Memory equivalent).
pub sprites: [Sprite; 512],
@ -65,9 +66,8 @@ pub struct Gfx {
}
impl Gfx {
/// Initializes the graphics system with a specific resolution.
pub fn new(w: usize, h: usize) -> Self {
const EMPTY_BANK: Option<TileBank> = None;
/// Initializes the graphics system with a specific resolution and shared memory banks.
pub fn new(w: usize, h: usize, tile_banks: Arc<dyn TileBankPoolAccess>) -> Self {
const EMPTY_SPRITE: Sprite = Sprite {
tile: crate::model::Tile { id: 0, flip_x: false, flip_y: false, palette_id: 0 },
x: 0,
@ -94,7 +94,7 @@ impl Gfx {
back: vec![0; len],
layers,
hud: HudTileLayer::new(64, 32),
banks: [EMPTY_BANK; 16],
tile_banks,
sprites: [EMPTY_SPRITE; 512],
scene_fade_level: 31,
scene_fade_color: Color::BLACK,
@ -328,25 +328,25 @@ impl Gfx {
}
// 1. Priority 0 sprites: drawn at the very back, behind everything else.
Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[0], &self.sprites, &self.banks);
Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[0], &self.sprites, &*self.tile_banks);
// 2. Main layers and prioritized sprites.
// Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ...
for i in 0..self.layers.len() {
let bank_id = self.layers[i].bank_id as usize;
if let Some(Some(bank)) = self.banks.get(bank_id) {
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[i].map, bank, self.layers[i].scroll_x, self.layers[i].scroll_y);
if let Some(bank) = self.tile_banks.tile_bank_slot(bank_id) {
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[i].map, &bank, self.layers[i].scroll_x, self.layers[i].scroll_y);
}
// Draw sprites that belong to this depth level
Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[i + 1], &self.sprites, &self.banks);
Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[i + 1], &self.sprites, &*self.tile_banks);
}
// 4. Scene Fade: Applies a color blend to the entire world (excluding HUD).
Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color);
// 5. HUD: The fixed interface layer, always drawn on top of the world.
self.render_hud();
Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks);
// 6. HUD Fade: Independent fade effect for the UI.
Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color);
@ -360,23 +360,27 @@ impl Gfx {
let scroll_x = self.layers[layer_idx].scroll_x;
let scroll_y = self.layers[layer_idx].scroll_y;
let bank = match self.banks.get(bank_id) {
Some(Some(b)) => b,
let bank = match self.tile_banks.tile_bank_slot(bank_id) {
Some(b) => b,
_ => return,
};
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, bank, scroll_x, scroll_y);
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, &bank, scroll_x, scroll_y);
}
/// Renders the HUD (fixed position, no scroll).
pub fn render_hud(&mut self) {
let bank_id = self.hud.bank_id as usize;
let bank = match self.banks.get(bank_id) {
Some(Some(b)) => b,
Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks);
}
fn render_hud_with_pool(back: &mut [u16], w: usize, h: usize, hud: &HudTileLayer, tile_banks: &dyn TileBankPoolAccess) {
let bank_id = hud.bank_id as usize;
let bank = match tile_banks.tile_bank_slot(bank_id) {
Some(b) => b,
_ => return,
};
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.hud.map, bank, 0, 0);
Self::draw_tile_map(back, w, h, &hud.map, &bank, 0, 0);
}
/// Rasterizes a TileMap into the provided pixel buffer using scrolling.
@ -464,13 +468,13 @@ impl Gfx {
screen_h: usize,
bucket: &[usize],
sprites: &[Sprite],
banks: &[Option<TileBank>],
tile_banks: &dyn TileBankPoolAccess,
) {
for &idx in bucket {
let s = &sprites[idx];
let bank_id = s.bank_id as usize;
if let Some(Some(bank)) = banks.get(bank_id) {
Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, bank);
if let Some(bank) = tile_banks.tile_bank_slot(bank_id) {
Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, &bank);
}
}
}
@ -532,45 +536,6 @@ impl Gfx {
}
}
/// Returns the actual memory usage of the graphics structures in bytes.
/// Reflects index buffers, palettes, and OAM as per Chapter 10.8.
pub fn memory_usage_bytes(&self) -> usize {
let mut total = 0;
// 1. Framebuffers (Front + Back)
// Each is Vec<u16>, occupying 2 bytes per pixel.
total += self.front.len() * 2;
total += self.back.len() * 2;
// 2. Tile Layers (4 Game Layers)
for layer in &self.layers {
// Struct size + map data (Vec<Tile>)
total += size_of::<ScrollableTileLayer>();
total += layer.map.tiles.len() * size_of::<crate::model::Tile>();
}
// 3. HUD Layer
total += size_of::<HudTileLayer>();
total += self.hud.map.tiles.len() * size_of::<crate::model::Tile>();
// 4. Tile Banks (Assets and Palettes)
for bank_opt in &self.banks {
if let Some(bank) = bank_opt {
total += size_of::<TileBank>();
total += bank.pixel_indices.len();
// Palette Table: 256 palettes * 16 colors * Color struct size
total += 256 * 16 * size_of::<Color>();
}
}
// 5. Sprites (OAM)
// Fixed array of 512 Sprites.
total += self.sprites.len() * size_of::<Sprite>();
total
}
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) {
let mut cx = x;
for c in text.chars() {
@ -647,10 +612,12 @@ impl Gfx {
#[cfg(test)]
mod tests {
use super::*;
use crate::hardware::MemoryBanks;
#[test]
fn test_draw_pixel() {
let mut gfx = Gfx::new(10, 10);
let banks = Arc::new(MemoryBanks::new());
let mut gfx = Gfx::new(10, 10, banks);
gfx.draw_pixel(5, 5, Color::WHITE);
assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0);
@ -661,7 +628,8 @@ mod tests {
#[test]
fn test_draw_line() {
let mut gfx = Gfx::new(10, 10);
let banks = Arc::new(MemoryBanks::new());
let mut gfx = Gfx::new(10, 10, banks);
gfx.draw_line(0, 0, 9, 9, Color::WHITE);
assert_eq!(gfx.back[0], Color::WHITE.0);
assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0);
@ -669,7 +637,8 @@ mod tests {
#[test]
fn test_draw_rect() {
let mut gfx = Gfx::new(10, 10);
let banks = Arc::new(MemoryBanks::new());
let mut gfx = Gfx::new(10, 10, banks);
gfx.draw_rect(0, 0, 10, 10, Color::WHITE);
assert_eq!(gfx.back[0], Color::WHITE.0);
assert_eq!(gfx.back[9], Color::WHITE.0);
@ -679,14 +648,16 @@ mod tests {
#[test]
fn test_fill_circle() {
let mut gfx = Gfx::new(10, 10);
let banks = Arc::new(MemoryBanks::new());
let mut gfx = Gfx::new(10, 10, banks);
gfx.fill_circle(5, 5, 2, Color::WHITE);
assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0);
}
#[test]
fn test_draw_square() {
let mut gfx = Gfx::new(10, 10);
let banks = Arc::new(MemoryBanks::new());
let mut gfx = Gfx::new(10, 10, banks);
gfx.draw_square(2, 2, 6, 6, Color::WHITE, Color::BLACK);
// Border
assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0);

View File

@ -1,10 +1,14 @@
use crate::hardware::{Audio, Gfx, HardwareBridge, Pad, Touch};
use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, Pad, Touch, MemoryBanks};
use crate::hardware::memory_banks::{TileBankPoolAccess, TileBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller};
use std::sync::Arc;
/// Aggregate structure for all virtual hardware peripherals.
///
/// This struct represents the "Mainboard" of the PROMETEU console,
/// containing instances of GFX, Audio, Input (Pad), and Touch.
pub struct Hardware {
/// Shared memory banks for hardware assets.
pub memory_banks: Arc<MemoryBanks>,
/// The Graphics Processing Unit.
pub gfx: Gfx,
/// The Sound Processing Unit.
@ -13,6 +17,8 @@ pub struct Hardware {
pub pad: Pad,
/// The absolute pointer input device.
pub touch: Touch,
/// The Asset Management system.
pub assets: AssetManager,
}
impl HardwareBridge for Hardware {
@ -27,6 +33,9 @@ impl HardwareBridge for Hardware {
fn touch(&self) -> &Touch { &self.touch }
fn touch_mut(&mut self) -> &mut Touch { &mut self.touch }
fn assets(&self) -> &AssetManager { &self.assets }
fn assets_mut(&mut self) -> &mut AssetManager { &mut self.assets }
}
impl Hardware {
@ -37,11 +46,19 @@ impl Hardware {
/// Creates a fresh hardware instance with default settings.
pub fn new() -> Self {
let memory_banks = Arc::new(MemoryBanks::new());
Self {
gfx: Gfx::new(Self::W, Self::H),
audio: Audio::new(),
memory_banks: Arc::clone(&memory_banks),
gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc<dyn TileBankPoolAccess>),
audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>),
pad: Pad::default(),
touch: Touch::default(),
assets: AssetManager::new(
vec![],
vec![],
Arc::clone(&memory_banks) as Arc<dyn TileBankPoolInstaller>,
Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolInstaller>,
),
}
}
}

View File

@ -0,0 +1,90 @@
use std::sync::{Arc, RwLock};
use crate::model::{TileBank, SoundBank};
/// Non-generic interface for peripherals to access graphical tile banks.
pub trait TileBankPoolAccess: Send + Sync {
/// Returns a reference to the resident TileBank in the specified slot, if any.
fn tile_bank_slot(&self, slot: usize) -> Option<Arc<TileBank>>;
/// Returns the total number of slots available in this bank.
fn tile_bank_slot_count(&self) -> usize;
}
/// Non-generic interface for the AssetManager to install graphical tile banks.
pub trait TileBankPoolInstaller: Send + Sync {
/// Atomically swaps the resident TileBank in the specified slot.
fn install_tile_bank(&self, slot: usize, bank: Arc<TileBank>);
}
/// Non-generic interface for peripherals to access sound banks.
pub trait SoundBankPoolAccess: Send + Sync {
/// Returns a reference to the resident SoundBank in the specified slot, if any.
fn sound_bank_slot(&self, slot: usize) -> Option<Arc<SoundBank>>;
/// Returns the total number of slots available in this bank.
fn sound_bank_slot_count(&self) -> usize;
}
/// Non-generic interface for the AssetManager to install sound banks.
pub trait SoundBankPoolInstaller: Send + Sync {
/// Atomically swaps the resident SoundBank in the specified slot.
fn install_sound_bank(&self, slot: usize, bank: Arc<SoundBank>);
}
/// Centralized container for all hardware memory banks.
///
/// MemoryBanks represent the actual hardware slot state.
/// Peripherals consume this state via narrow, non-generic traits.
/// AssetManager coordinates residency and installs assets into these slots.
pub struct MemoryBanks {
tile_bank_pool: Arc<RwLock<[Option<Arc<TileBank>>; 16]>>,
sound_bank_pool: Arc<RwLock<[Option<Arc<SoundBank>>; 16]>>,
}
impl MemoryBanks {
/// Creates a new set of memory banks with empty slots.
pub fn new() -> Self {
Self {
tile_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
sound_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
}
}
}
impl TileBankPoolAccess for MemoryBanks {
fn tile_bank_slot(&self, slot: usize) -> Option<Arc<TileBank>> {
let pool = self.tile_bank_pool.read().unwrap();
pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone))
}
fn tile_bank_slot_count(&self) -> usize {
16
}
}
impl TileBankPoolInstaller for MemoryBanks {
fn install_tile_bank(&self, slot: usize, bank: Arc<TileBank>) {
let mut pool = self.tile_bank_pool.write().unwrap();
if slot < 16 {
pool[slot] = Some(bank);
}
}
}
impl SoundBankPoolAccess for MemoryBanks {
fn sound_bank_slot(&self, slot: usize) -> Option<Arc<SoundBank>> {
let pool = self.sound_bank_pool.read().unwrap();
pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone))
}
fn sound_bank_slot_count(&self) -> usize {
16
}
}
impl SoundBankPoolInstaller for MemoryBanks {
fn install_sound_bank(&self, slot: usize, bank: Arc<SoundBank>) {
let mut pool = self.sound_bank_pool.write().unwrap();
if slot < 16 {
pool[slot] = Some(bank);
}
}
}

View File

@ -1,16 +1,21 @@
mod asset;
mod gfx;
mod pad;
mod touch;
mod input_signal;
mod audio;
mod memory_banks;
pub mod hardware;
pub use gfx::Gfx;
pub use crate::model::HandleId;
pub use asset::AssetManager;
pub use audio::{Audio, AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
pub use gfx::BlendMode;
pub use gfx::Gfx;
pub use input_signal::InputSignals;
pub use memory_banks::MemoryBanks;
pub use pad::Pad;
pub use touch::Touch;
pub use audio::{Audio, AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
pub trait HardwareBridge {
fn gfx(&self) -> &Gfx;
@ -24,4 +29,7 @@ pub trait HardwareBridge {
fn touch(&self) -> &Touch;
fn touch_mut(&mut self) -> &mut Touch;
fn assets(&self) -> &AssetManager;
fn assets_mut(&mut self) -> &mut AssetManager;
}

View File

@ -0,0 +1,80 @@
use serde::{Deserialize, Serialize};
pub type HandleId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[allow(non_camel_case_types)]
pub enum BankType {
TILES,
SOUNDS,
// TILEMAPS,
// BLOBS,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AssetEntry {
pub asset_id: u32,
pub asset_name: String,
pub bank_type: BankType,
pub offset: u64,
pub size: u64,
pub decoded_size: u64,
pub codec: String, // e.g., "RAW"
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PreloadEntry {
pub asset_name: String,
pub slot: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LoadStatus {
PENDING,
LOADING,
READY,
COMMITTED,
CANCELED,
ERROR,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BankStats {
pub total_bytes: usize,
pub used_bytes: usize,
pub free_bytes: usize,
pub inflight_bytes: usize,
pub slot_count: usize,
pub slots_occupied: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotStats {
pub asset_id: Option<u32>,
pub asset_name: Option<String>,
pub generation: u32,
pub resident_bytes: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SlotRef {
pub asset_type: BankType,
pub index: usize,
}
impl SlotRef {
pub fn gfx(index: usize) -> Self {
Self {
asset_type: BankType::TILES,
index,
}
}
pub fn audio(index: usize) -> Self {
Self {
asset_type: BankType::SOUNDS,
index,
}
}
}

View File

@ -1,4 +1,4 @@
use std::path::PathBuf;
use crate::model::asset::{AssetEntry, PreloadEntry};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
@ -15,7 +15,9 @@ pub struct Cartridge {
pub app_mode: AppMode,
pub entrypoint: String,
pub program: Vec<u8>,
pub assets_path: Option<PathBuf>,
pub assets: Vec<u8>,
pub asset_table: Vec<AssetEntry>,
pub preload: Vec<PreloadEntry>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -26,7 +28,11 @@ pub struct CartridgeDTO {
pub app_mode: AppMode,
pub entrypoint: String,
pub program: Vec<u8>,
pub assets_path: Option<PathBuf>,
pub assets: Vec<u8>,
#[serde(default)]
pub asset_table: Vec<AssetEntry>,
#[serde(default)]
pub preload: Vec<PreloadEntry>,
}
impl From<CartridgeDTO> for Cartridge {
@ -38,7 +44,9 @@ impl From<CartridgeDTO> for Cartridge {
app_mode: dto.app_mode,
entrypoint: dto.entrypoint,
program: dto.program,
assets_path: dto.assets_path,
assets: dto.assets,
asset_table: dto.asset_table,
preload: dto.preload,
}
}
}
@ -62,4 +70,8 @@ pub struct CartridgeManifest {
pub app_version: String,
pub app_mode: AppMode,
pub entrypoint: String,
#[serde(default)]
pub asset_table: Vec<AssetEntry>,
#[serde(default)]
pub preload: Vec<PreloadEntry>,
}

View File

@ -48,11 +48,11 @@ impl DirectoryCartridgeLoader {
let program = fs::read(program_path).map_err(|_| CartridgeError::IoError)?;
let assets_path = path.join("assets");
let assets_path = if assets_path.exists() && assets_path.is_dir() {
Some(assets_path)
let assets_pa_path = path.join("assets.pa");
let assets = if assets_pa_path.exists() {
fs::read(assets_pa_path).map_err(|_| CartridgeError::IoError)?
} else {
None
Vec::new()
};
let dto = CartridgeDTO {
@ -62,7 +62,9 @@ impl DirectoryCartridgeLoader {
app_mode: manifest.app_mode,
entrypoint: manifest.entrypoint,
program,
assets_path,
assets,
asset_table: manifest.asset_table,
preload: manifest.preload,
};
Ok(Cartridge::from(dto))

View File

@ -1,19 +1,23 @@
mod asset;
mod color;
mod button;
mod tile;
mod tile_layer;
mod tile_bank;
mod sound_bank;
mod sprite;
mod sample;
mod cartridge;
mod cartridge_loader;
mod window;
pub use asset::{AssetEntry, BankType, BankStats, LoadStatus, SlotRef, SlotStats, HandleId, PreloadEntry};
pub use button::{Button, ButtonId};
pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError};
pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader};
pub use color::Color;
pub use sample::Sample;
pub use sound_bank::SoundBank;
pub use sprite::Sprite;
pub use tile::Tile;
pub use tile_bank::{TileBank, TileSize};

View File

@ -0,0 +1,16 @@
use crate::model::Sample;
use std::sync::Arc;
/// A container for audio assets.
///
/// A SoundBank stores multiple audio samples that can be played by the
/// audio subsystem.
pub struct SoundBank {
pub samples: Vec<Arc<Sample>>,
}
impl SoundBank {
pub fn new(samples: Vec<Arc<Sample>>) -> Self {
Self { samples }
}
}

View File

@ -27,8 +27,8 @@ pub struct TileBank {
/// Pixel data stored as 4-bit indices (packed into 8-bit values).
/// Index 0 is always reserved for transparency.
pub pixel_indices: Vec<u8>,
/// Table of 256 palettes, each containing 16 RGB565 colors.
pub palettes: [[Color; 16]; 256],
/// Table of 64 palettes, each containing 16 RGB565 colors, total of 1024 colors for a bank.
pub palettes: [[Color; 16]; 64],
}
impl TileBank {
@ -39,7 +39,7 @@ impl TileBank {
width,
height,
pixel_indices: vec![0; width * height], // Index 0 = Transparent
palettes: [[Color::BLACK; 16]; 256],
palettes: [[Color::BLACK; 16]; 64],
}
}

View File

@ -35,7 +35,7 @@ impl TileLayer {
fn create(width: usize, height: usize, tile_size: TileSize) -> Self {
Self {
bank_id: 0,
tile_size: tile_size,
tile_size,
map: TileMap::create(width, height),
}
}

View File

@ -1,12 +1,11 @@
use crate::fs::{FsBackend, FsState, VirtualFS};
use crate::hardware::{HardwareBridge, InputSignals};
use crate::log::{LogLevel, LogService, LogSource};
use crate::model::{Cartridge, Color, Sample};
use crate::model::{BankType, Cartridge, Color};
use crate::prometeu_os::{NativeInterface, Syscall};
use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
use crate::virtual_machine::{Value, VirtualMachine};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
/// PrometeuOS (POS): The system firmware/base.
@ -25,11 +24,6 @@ pub struct PrometeuOS {
/// Real-world CPU time (in microseconds) consumed by the last host tick.
pub last_frame_cpu_time_us: u64,
// Example assets (kept for compatibility with v0 audio syscalls)
pub sample_square: Option<Arc<Sample>>,
pub sample_kick: Option<Arc<Sample>>,
pub sample_snare: Option<Arc<Sample>>,
// Filesystem
/// The virtual filesystem interface.
pub fs: VirtualFS,
@ -87,9 +81,6 @@ impl PrometeuOS {
logical_frame_active: false,
logical_frame_remaining_cycles: 0,
last_frame_cpu_time_us: 0,
sample_square: None,
sample_kick: None,
sample_snare: None,
fs: VirtualFS::new(),
fs_state: FsState::Unmounted,
open_files: HashMap::new(),
@ -108,9 +99,6 @@ impl PrometeuOS {
boot_time,
};
// Initializes basic samples (same logic as LogicalHardware)
os.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1)));
os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string());
os
@ -188,8 +176,8 @@ impl PrometeuOS {
/// This method is responsible for managing the logical frame lifecycle.
/// A single host tick might execute a full logical frame, part of it,
/// or multiple frames depending on the configured slices.
pub fn step_frame(&mut self, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge) -> Option<String> {
let start = std::time::Instant::now();
pub fn tick(&mut self, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge) -> Option<String> {
let start = Instant::now();
self.tick_index += 1;
// If the system is paused, we don't advance unless there's a debug step request.
@ -214,13 +202,13 @@ impl PrometeuOS {
}
// 2. Budget Allocation
// Determine how many cycles we can run in this host tick.
// Determines how many cycles we can run in this host tick.
let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles);
// 3. VM Execution
if budget > 0 {
// Run the VM until budget is hit or FRAME_SYNC is reached.
let run_result = vm.run_budget(budget, self, hw);
// Run the VM until the budget is hit or FRAME_SYNC is reached.
let run_result = vm.run_budget(budget, self, hw); // internally dispatch to frame on SDK
match run_result {
Ok(run) => {
@ -275,9 +263,26 @@ impl PrometeuOS {
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
// Update bank telemetry in current frame (snapshot)
let gfx_stats = hw.assets().bank_info(BankType::TILES);
self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes;
self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes;
self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32;
let audio_stats = hw.assets().bank_info(BankType::SOUNDS);
self.telemetry_current.audio_used_bytes = audio_stats.used_bytes;
self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes;
self.telemetry_current.audio_slots_occupied = audio_stats.slots_occupied as u32;
// If the frame ended exactly in this tick, we update the final real time in the latch.
if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) {
self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us;
self.telemetry_last.gfx_used_bytes = self.telemetry_current.gfx_used_bytes;
self.telemetry_last.gfx_inflight_bytes = self.telemetry_current.gfx_inflight_bytes;
self.telemetry_last.gfx_slots_occupied = self.telemetry_current.gfx_slots_occupied;
self.telemetry_last.audio_used_bytes = self.telemetry_current.audio_used_bytes;
self.telemetry_last.audio_inflight_bytes = self.telemetry_current.audio_inflight_bytes;
self.telemetry_last.audio_slots_occupied = self.telemetry_current.audio_slots_occupied;
}
None
@ -367,24 +372,6 @@ impl PrometeuOS {
_ => false,
}
}
fn create_square_sample(freq: f64, duration: f64) -> Sample {
let sample_rate = crate::hardware::OUTPUT_SAMPLE_RATE;
let num_samples = (duration * sample_rate as f64) as usize;
let mut data = Vec::with_capacity(num_samples);
let period = sample_rate as f64 / freq;
for i in 0..num_samples {
let val = if (i as f64 % period) < (period / 2.0) {
10000
} else {
-10000
};
data.push(val);
}
Sample::new(sample_rate, data)
}
}
#[cfg(test)]
@ -410,17 +397,19 @@ mod tests {
app_mode: AppMode::Game,
entrypoint: "0".to_string(),
program: rom,
assets_path: None,
assets: vec![],
asset_table: vec![],
preload: vec![],
};
os.initialize_vm(&mut vm, &cartridge);
// First tick
os.step_frame(&mut vm, &signals, &mut hw);
os.tick(&mut vm, &signals, &mut hw);
let cycles_after_tick_1 = vm.cycles;
assert!(cycles_after_tick_1 >= PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
// Second tick - Now it SHOULD NOT gain more budget
os.step_frame(&mut vm, &signals, &mut hw);
os.tick(&mut vm, &signals, &mut hw);
let cycles_after_tick_2 = vm.cycles;
// FIX: It should not have consumed cycles in the second tick because the logical frame budget ended
@ -450,12 +439,14 @@ mod tests {
app_mode: AppMode::Game,
entrypoint: "0".to_string(),
program: rom,
assets_path: None,
assets: vec![],
asset_table: vec![],
preload: vec![],
};
os.initialize_vm(&mut vm, &cartridge);
// First tick
os.step_frame(&mut vm, &signals, &mut hw);
os.tick(&mut vm, &signals, &mut hw);
let cycles_after_tick_1 = vm.cycles;
// Should have stopped at FrameSync
@ -463,7 +454,7 @@ mod tests {
assert!(cycles_after_tick_1 < PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
// Second tick - Should reset the budget and run a bit more until the next FrameSync
os.step_frame(&mut vm, &signals, &mut hw);
os.tick(&mut vm, &signals, &mut hw);
let cycles_after_tick_2 = vm.cycles;
assert!(cycles_after_tick_2 > cycles_after_tick_1, "VM should have consumed more cycles because FrameSync reset the budget");
@ -480,6 +471,54 @@ mod tests {
assert_eq!(os.get_color(3), Color::from_raw(3));
}
#[test]
fn test_gfx_set_sprite_syscall_pops() {
let mut os = PrometeuOS::new(None);
let mut vm = VirtualMachine::default();
let mut hw = Hardware::new();
// Push arguments in order 1 to 10
vm.push(Value::String("mouse_cursor".to_string())); // arg1: assetName
vm.push(Value::Int32(0)); // arg2: id
// Simulating touch.x and touch.y syscalls
vm.push(Value::Int32(10)); // arg3: x (returned from syscall)
vm.push(Value::Int32(20)); // arg4: y (returned from syscall)
vm.push(Value::Int32(0)); // arg5: tileId
vm.push(Value::Int32(0)); // arg6: paletteId
vm.push(Value::Boolean(true)); // arg7: active
vm.push(Value::Boolean(false)); // arg8: flipX
vm.push(Value::Boolean(false)); // arg9: flipY
vm.push(Value::Int32(4)); // arg10: priority
let res = os.syscall(0x1007, &mut vm, &mut hw);
assert!(res.is_ok(), "GfxSetSprite syscall should succeed, but got: {:?}", res.err());
}
#[test]
fn test_gfx_set_sprite_with_swapped_arguments_repro() {
let mut os = PrometeuOS::new(None);
let mut vm = VirtualMachine::default();
let mut hw = Hardware::new();
// Repro: what if the compiler is pushing in reverse order?
vm.push(Value::Int32(4)); // arg10?
vm.push(Value::Boolean(false));
vm.push(Value::Boolean(false));
vm.push(Value::Boolean(true));
vm.push(Value::Int32(0));
vm.push(Value::Int32(0));
vm.push(Value::Int32(20));
vm.push(Value::Int32(10));
vm.push(Value::Int32(0));
vm.push(Value::String("mouse_cursor".to_string())); // arg1?
let res = os.syscall(0x1007, &mut vm, &mut hw);
assert!(res.is_err());
assert_eq!(res.err().unwrap(), "Expected integer"); // Because it tries to pop priority but gets a string
}
#[test]
fn test_syscall_log_write_and_rate_limit() {
let mut os = PrometeuOS::new(None);
@ -659,6 +698,40 @@ impl NativeInterface for PrometeuOS {
vm.push(Value::Null);
Ok(200)
}
// gfx.set_sprite(asset_name, id, x, y, tile_id, palette_id, active, flip_x, flip_y, priority)
Syscall::GfxSetSprite => {
let priority = vm.pop_integer()? as u8;
let flip_y = vm.pop_integer()? != 0;
let flip_x = vm.pop_integer()? != 0;
let active = vm.pop_integer()? != 0;
let palette_id = vm.pop_integer()? as u8;
let tile_id = vm.pop_integer()? as u16;
let y = vm.pop_integer()? as i32;
let x = vm.pop_integer()? as i32;
let index = vm.pop_integer()? as usize;
let val = vm.pop()?;
let asset_name = match val {
Value::String(ref s) => s.clone(),
_ => return Err(format!("Expected string asset_name in GfxSetSprite, but got {:?}", val).into()),
};
let bank_id = hw.assets().find_slot_by_name(&asset_name, crate::model::BankType::TILES).unwrap_or(0);
if index < 512 {
hw.gfx_mut().sprites[index] = crate::model::Sprite {
tile: crate::model::Tile { id: tile_id, flip_x: false, flip_y: false, palette_id },
x,
y,
bank_id,
active,
flip_x,
flip_y,
priority,
};
}
vm.push(Value::Null);
Ok(100)
}
// --- Input Syscalls ---
@ -723,16 +796,31 @@ impl NativeInterface for PrometeuOS {
let voice_id = vm.pop_integer()? as usize;
let sample_id = vm.pop_integer()? as u32;
let sample = match sample_id {
0 => self.sample_square.clone(),
1 => self.sample_kick.clone(),
2 => self.sample_snare.clone(),
_ => None,
hw.audio_mut().play(0, sample_id as u16, voice_id, volume, pan, pitch, 0, crate::hardware::LoopMode::Off);
vm.push(Value::Null);
Ok(300)
}
// audio.play(asset_name, sample_id, voice_id, volume, pan, pitch, loop_mode)
Syscall::AudioPlay => {
let loop_mode = match vm.pop_integer()? {
0 => crate::hardware::LoopMode::Off,
_ => crate::hardware::LoopMode::On,
};
let pitch = vm.pop_number()?;
let pan = vm.pop_integer()? as u8;
let volume = vm.pop_integer()? as u8;
let voice_id = vm.pop_integer()? as usize;
let sample_id = vm.pop_integer()? as u16;
let val = vm.pop()?;
let asset_name = match val {
Value::String(ref s) => s.clone(),
_ => return Err(format!("Expected string asset_name in AudioPlay, but got {:?}", val).into()),
};
if let Some(s) = sample {
hw.audio_mut().play(s, voice_id, volume, pan, pitch, 0, crate::hardware::LoopMode::Off);
}
let bank_id = hw.assets().find_slot_by_name(&asset_name, crate::model::BankType::SOUNDS).unwrap_or(0);
hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode);
vm.push(Value::Null);
Ok(300)
}
@ -860,6 +948,83 @@ impl NativeInterface for PrometeuOS {
let level = vm.pop_integer()?;
self.syscall_log_write(vm, level, tag, msg)
}
// --- Asset Syscalls ---
Syscall::AssetLoad => {
let asset_id = match vm.pop()? {
Value::String(s) => s,
_ => return Err("Expected string asset_id".into()),
};
let asset_type_val = vm.pop_integer()? as u32;
let slot_index = vm.pop_integer()? as usize;
let asset_type = match asset_type_val {
0 => crate::model::BankType::TILES,
1 => crate::model::BankType::SOUNDS,
_ => return Err("Invalid asset type".to_string()),
};
let slot = crate::model::SlotRef { asset_type, index: slot_index };
match hw.assets().load(&asset_id, slot) {
Ok(handle) => {
vm.push(Value::Int64(handle as i64));
Ok(1000)
}
Err(e) => Err(e),
}
}
Syscall::AssetStatus => {
let handle = vm.pop_integer()? as u32;
let status = hw.assets().status(handle);
let status_val = match status {
crate::model::LoadStatus::PENDING => 0,
crate::model::LoadStatus::LOADING => 1,
crate::model::LoadStatus::READY => 2,
crate::model::LoadStatus::COMMITTED => 3,
crate::model::LoadStatus::CANCELED => 4,
crate::model::LoadStatus::ERROR => 5,
};
vm.push(Value::Int64(status_val));
Ok(100)
}
Syscall::AssetCommit => {
let handle = vm.pop_integer()? as u32;
hw.assets().commit(handle);
vm.push(Value::Null);
Ok(100)
}
Syscall::AssetCancel => {
let handle = vm.pop_integer()? as u32;
hw.assets().cancel(handle);
vm.push(Value::Null);
Ok(100)
}
Syscall::BankInfo => {
let asset_type_val = vm.pop_integer()? as u32;
let asset_type = match asset_type_val {
0 => crate::model::BankType::TILES,
1 => crate::model::BankType::SOUNDS,
_ => return Err("Invalid asset type".to_string()),
};
let info = hw.assets().bank_info(asset_type);
let json = serde_json::to_string(&info).unwrap_or_default();
vm.push(Value::String(json));
Ok(500)
}
Syscall::BankSlotInfo => {
let slot_index = vm.pop_integer()? as usize;
let asset_type_val = vm.pop_integer()? as u32;
let asset_type = match asset_type_val {
0 => crate::model::BankType::TILES,
1 => crate::model::BankType::SOUNDS,
_ => return Err("Invalid asset type".to_string()),
};
let slot = crate::model::SlotRef { asset_type, index: slot_index };
let info = hw.assets().slot_info(slot);
let json = serde_json::to_string(&info).unwrap_or_default();
vm.push(Value::String(json));
Ok(500)
}
}
}
}

View File

@ -12,6 +12,7 @@ pub enum Syscall {
GfxDrawCircle = 0x1004,
GfxDrawDisc = 0x1005,
GfxDrawSquare = 0x1006,
GfxSetSprite = 0x1007,
// Input
InputGetPad = 0x2001,
@ -28,6 +29,7 @@ pub enum Syscall {
// Audio
AudioPlaySample = 0x3001,
AudioPlay = 0x3002,
// FS
FsOpen = 0x4001,
@ -41,6 +43,16 @@ pub enum Syscall {
// Log
LogWrite = 0x5001,
LogWriteTag = 0x5002,
// Asset
AssetLoad = 0x6001,
AssetStatus = 0x6002,
AssetCommit = 0x6003,
AssetCancel = 0x6004,
// Bank
BankInfo = 0x6101,
BankSlotInfo = 0x6102,
}
impl Syscall {
@ -54,6 +66,7 @@ impl Syscall {
0x1004 => Some(Self::GfxDrawCircle),
0x1005 => Some(Self::GfxDrawDisc),
0x1006 => Some(Self::GfxDrawSquare),
0x1007 => Some(Self::GfxSetSprite),
0x2001 => Some(Self::InputGetPad),
0x2002 => Some(Self::InputGetPadPressed),
0x2003 => Some(Self::InputGetPadReleased),
@ -65,6 +78,7 @@ impl Syscall {
0x2105 => Some(Self::TouchIsReleased),
0x2106 => Some(Self::TouchGetHold),
0x3001 => Some(Self::AudioPlaySample),
0x3002 => Some(Self::AudioPlay),
0x4001 => Some(Self::FsOpen),
0x4002 => Some(Self::FsRead),
0x4003 => Some(Self::FsWrite),
@ -74,6 +88,12 @@ impl Syscall {
0x4007 => Some(Self::FsDelete),
0x5001 => Some(Self::LogWrite),
0x5002 => Some(Self::LogWriteTag),
0x6001 => Some(Self::AssetLoad),
0x6002 => Some(Self::AssetStatus),
0x6003 => Some(Self::AssetCommit),
0x6004 => Some(Self::AssetCancel),
0x6101 => Some(Self::BankInfo),
0x6102 => Some(Self::BankSlotInfo),
_ => None,
}
}
@ -120,6 +140,7 @@ impl Syscall {
"gfx.drawCircle" | "gfx.draw_circle" => Some(Self::GfxDrawCircle),
"gfx.drawDisc" | "gfx.draw_disc" => Some(Self::GfxDrawDisc),
"gfx.drawSquare" | "gfx.draw_square" => Some(Self::GfxDrawSquare),
"gfx.setSprite" | "gfx.set_sprite" => Some(Self::GfxSetSprite),
"input.getPad" => Some(Self::InputGetPad),
"input.getPadPressed" | "input.get_pad_pressed" => Some(Self::InputGetPadPressed),
"input.getPadReleased" | "input.get_pad_released" => Some(Self::InputGetPadReleased),
@ -131,6 +152,7 @@ impl Syscall {
"touch.isReleased" | "touch.is_released" => Some(Self::TouchIsReleased),
"touch.getHold" | "touch.get_hold" => Some(Self::TouchGetHold),
"audio.playSample" | "audio.play_sample" => Some(Self::AudioPlaySample),
"audio.play" => Some(Self::AudioPlay),
"fs.open" => Some(Self::FsOpen),
"fs.read" => Some(Self::FsRead),
"fs.write" => Some(Self::FsWrite),
@ -140,6 +162,12 @@ impl Syscall {
"fs.delete" => Some(Self::FsDelete),
"log.write" => Some(Self::LogWrite),
"log.writeTag" | "log.write_tag" => Some(Self::LogWriteTag),
"asset.load" => Some(Self::AssetLoad),
"asset.status" => Some(Self::AssetStatus),
"asset.commit" => Some(Self::AssetCommit),
"asset.cancel" => Some(Self::AssetCancel),
"bank.info" => Some(Self::BankInfo),
"bank.slotInfo" | "bank.slot_info" => Some(Self::BankSlotInfo),
_ => {
None
}

View File

@ -8,6 +8,16 @@ pub struct TelemetryFrame {
pub syscalls: u32,
pub host_cpu_time_us: u64,
pub violations: u32,
// GFX Banks
pub gfx_used_bytes: usize,
pub gfx_inflight_bytes: usize,
pub gfx_slots_occupied: u32,
// Audio Banks
pub audio_used_bytes: usize,
pub audio_inflight_bytes: usize,
pub audio_slots_occupied: u32,
}
#[derive(Debug, Clone, Copy, Default)]

View File

@ -666,6 +666,9 @@ impl VirtualMachine {
pub fn pop_integer(&mut self) -> Result<i64, String> {
let val = self.pop()?;
if let Value::Boolean(b) = val {
return Ok(if b { 1 } else { 0 });
}
val.as_integer().ok_or_else(|| "Expected integer".into())
}
@ -688,9 +691,9 @@ impl VirtualMachine {
#[cfg(test)]
mod tests {
use super::*;
use crate::virtual_machine::Value;
use crate::prometeu_os::NativeInterface;
use crate::hardware::HardwareBridge;
use crate::prometeu_os::NativeInterface;
use crate::virtual_machine::Value;
struct MockNative;
impl NativeInterface for MockNative {
@ -709,6 +712,8 @@ mod tests {
fn pad_mut(&mut self) -> &mut crate::hardware::Pad { todo!() }
fn touch(&self) -> &crate::hardware::Touch { todo!() }
fn touch_mut(&mut self) -> &mut crate::hardware::Touch { todo!() }
fn assets(&self) -> &crate::hardware::AssetManager { todo!() }
fn assets_mut(&mut self) -> &mut crate::hardware::AssetManager { todo!() }
}
#[test]

View File

@ -1,5 +1,5 @@
use prometeu_core::hardware::{AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use prometeu_core::hardware::{AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
use ringbuf::traits::{Consumer, Producer, Split};
use ringbuf::HeapRb;
use std::sync::Arc;
@ -69,7 +69,9 @@ impl HostAudio {
pub fn send_commands(&mut self, commands: &mut Vec<AudioCommand>) {
if let Some(producer) = &mut self.producer {
for cmd in commands.drain(..) {
let _ = producer.try_push(cmd);
if let Err(_) = producer.try_push(cmd) {
eprintln!("[HostAudio] Command ringbuffer full, dropping command.");
}
}
}
}
@ -86,6 +88,7 @@ impl HostAudio {
pub struct AudioMixer {
voices: [Channel; MAX_CHANNELS],
pub last_processing_time: Duration,
paused: bool,
}
impl AudioMixer {
@ -93,6 +96,7 @@ impl AudioMixer {
Self {
voices: Default::default(),
last_processing_time: Duration::ZERO,
paused: false,
}
}
@ -108,8 +112,10 @@ impl AudioMixer {
loop_mode,
} => {
if voice_id < MAX_CHANNELS {
println!("[AudioMixer] Playing voice {}: vol={}, pitch={}, loop={:?}", voice_id, volume, pitch, loop_mode);
self.voices[voice_id] = Channel {
sample: Some(sample),
active: true,
pos: 0.0,
pitch,
volume,
@ -121,6 +127,7 @@ impl AudioMixer {
}
AudioCommand::Stop { voice_id } => {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].active = false;
self.voices[voice_id].sample = None;
}
}
@ -139,6 +146,14 @@ impl AudioMixer {
self.voices[voice_id].pitch = pitch;
}
}
AudioCommand::MasterPause => {
println!("[AudioMixer] Master Pause");
self.paused = true;
}
AudioCommand::MasterResume => {
println!("[AudioMixer] Master Resume");
self.paused = false;
}
}
}
@ -149,6 +164,11 @@ impl AudioMixer {
*sample = 0.0;
}
if self.paused {
self.last_processing_time = start.elapsed();
return;
}
for voice in self.voices.iter_mut() {
let sample_data = match &voice.sample {
Some(s) => s,
@ -168,6 +188,7 @@ impl AudioMixer {
let pos_fract = voice.pos - pos_int as f64;
if pos_int >= sample_data.data.len() {
voice.active = false;
voice.sample = None;
break;
}
@ -197,6 +218,7 @@ impl AudioMixer {
let loop_start = sample_data.loop_start.unwrap_or(0) as f64;
voice.pos = loop_start + (voice.pos - end_pos);
} else {
voice.active = false;
voice.sample = None;
break;
}

View File

@ -230,17 +230,32 @@ impl HostDebugger {
// Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events.
if event.tag >= 0xCA01 && event.tag <= 0xCA03 {
let rule = match event.tag {
0xCA01 => "cycles_budget",
0xCA02 => "max_syscalls",
0xCA03 => "max_host_cpu_us",
_ => "unknown"
}.to_string();
let tel = &firmware.os.telemetry_last;
let cert_config = &firmware.os.certifier.config;
let (rule, used, limit) = match event.tag {
0xCA01 => (
"cycles_budget".to_string(),
tel.cycles_used,
cert_config.cycles_budget_per_frame.unwrap_or(0),
),
0xCA02 => (
"max_syscalls".to_string(),
tel.syscalls as u64,
cert_config.max_syscalls_per_frame.unwrap_or(0) as u64,
),
0xCA03 => (
"max_host_cpu_us".to_string(),
tel.host_cpu_time_us,
cert_config.max_host_cpu_us_per_frame.unwrap_or(0),
),
_ => ("unknown".to_string(), 0, 0),
};
self.send_event(DebugEvent::Cert {
rule,
used: 0,
limit: 0,
used,
limit,
frame_index: firmware.os.logical_frame_index,
});
}
@ -261,6 +276,14 @@ impl HostDebugger {
vm_steps: tel.vm_steps,
syscalls: tel.syscalls,
cycles: tel.cycles_used,
host_cpu_time_us: tel.host_cpu_time_us,
violations: tel.violations,
gfx_used_bytes: tel.gfx_used_bytes,
gfx_inflight_bytes: tel.gfx_inflight_bytes,
gfx_slots_occupied: tel.gfx_slots_occupied,
audio_used_bytes: tel.audio_used_bytes,
audio_inflight_bytes: tel.audio_inflight_bytes,
audio_slots_occupied: tel.audio_slots_occupied,
});
self.last_telemetry_frame = current_frame;
}

View File

@ -64,6 +64,8 @@ pub struct HostRunner {
/// The physical audio driver.
audio: HostAudio,
/// Last known pause state to sync with audio.
last_paused_state: bool,
}
impl HostRunner {
@ -99,6 +101,7 @@ impl HostRunner {
debugger: HostDebugger::new(),
overlay_enabled: false,
audio: HostAudio::new(),
last_paused_state: false,
}
}
@ -124,21 +127,31 @@ impl HostRunner {
let color_bg = prometeu_core::model::Color::INDIGO; // Dark blue to stand out
let color_warn = prometeu_core::model::Color::RED;
self.hardware.gfx.fill_rect(5, 5, 140, 65, color_bg);
self.hardware.gfx.fill_rect(5, 5, 140, 100, color_bg);
self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.stats.current_fps), color_text);
self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text);
self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text);
self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text);
self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}", tel.cycles_used), color_text);
self.hardware.gfx.draw_text(10, 50, &format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied), color_text);
if tel.gfx_inflight_bytes > 0 {
self.hardware.gfx.draw_text(10, 58, &format!("LOAD GFX: {}KB", tel.gfx_inflight_bytes / 1024), color_warn);
}
self.hardware.gfx.draw_text(10, 66, &format!("AUD: {}K/32M ({}S)", tel.audio_used_bytes / 1024, tel.audio_slots_occupied), color_text);
if tel.audio_inflight_bytes > 0 {
self.hardware.gfx.draw_text(10, 74, &format!("LOAD AUD: {}KB", tel.audio_inflight_bytes / 1024), color_warn);
}
let cert_color = if tel.violations > 0 { color_warn } else { color_text };
self.hardware.gfx.draw_text(10, 50, &format!("CERT LAST: {}", tel.violations), cert_color);
self.hardware.gfx.draw_text(10, 82, &format!("CERT LAST: {}", tel.violations), cert_color);
if tel.violations > 0 {
if let Some(event) = self.firmware.os.log_service.get_recent(10).into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) {
let mut msg = event.msg.clone();
if msg.len() > 30 { msg.truncate(30); }
self.hardware.gfx.draw_text(10, 58, &msg, color_warn);
self.hardware.gfx.draw_text(10, 90, &msg, color_warn);
}
}
}
@ -266,7 +279,21 @@ impl ApplicationHandler for HostRunner {
while self.accumulator >= self.frame_target_dt {
// Unless the debugger is waiting for a 'start' command, advance the system.
if !self.debugger.waiting_for_start {
self.firmware.step_frame(&self.input.signals, &mut self.hardware);
self.firmware.tick(&self.input.signals, &mut self.hardware);
}
// Sync pause state with audio.
// We do this AFTER firmware.tick to avoid MasterPause/Resume commands
// being cleared by the OS if a new logical frame starts in this tick.
let is_paused = self.firmware.os.paused || self.debugger.waiting_for_start;
if is_paused != self.last_paused_state {
self.last_paused_state = is_paused;
let cmd = if is_paused {
prometeu_core::hardware::AudioCommand::MasterPause
} else {
prometeu_core::hardware::AudioCommand::MasterResume
};
self.hardware.audio.commands.push(cmd);
}
// Sync virtual audio commands to the physical mixer.

View File

@ -1,7 +1,7 @@
use prometeu_core::firmware::Firmware;
use prometeu_core::Hardware;
use std::time::{Duration, Instant};
use winit::window::Window;
use prometeu_core::Hardware;
use prometeu_core::firmware::Firmware;
pub struct HostStats {
pub last_stats_update: Instant,
@ -31,14 +31,12 @@ impl HostStats {
self.audio_load_samples += 1;
}
pub fn update(&mut self, now: Instant, window: Option<&Window>, hardware: &Hardware, firmware: &Firmware) {
pub fn update(&mut self, now: Instant, window: Option<&Window>, _hardware: &Hardware, firmware: &Firmware) {
let stats_elapsed = now.duration_since(self.last_stats_update);
if stats_elapsed >= Duration::from_secs(1) {
self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
if let Some(window) = window {
let kb = hardware.gfx.memory_usage_bytes() as f64 / 1024.0;
// Fixed comparison always against 60Hz, keep even when doing CPU stress tests
let frame_budget_us = 16666.0;
let cpu_load_core = (firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0;
@ -51,7 +49,7 @@ impl HostStats {
let title = format!(
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
kb, self.current_fps, cpu_load_core, cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index
0, self.current_fps, cpu_load_core, cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index
);
window.set_title(&title);
}

View File

@ -51,7 +51,11 @@
},
{
"event": "telemetry",
"fields": ["frame_index", "vm_steps", "syscalls", "cycles"]
"fields": [
"frame_index", "vm_steps", "syscalls", "cycles", "host_cpu_time_us", "violations",
"gfx_used_bytes", "gfx_inflight_bytes", "gfx_slots_occupied",
"audio_used_bytes", "audio_inflight_bytes", "audio_slots_occupied"
]
},
{
"event": "cert",

View File

@ -33,12 +33,15 @@ declare global {
}
interface Gfx {
clear(color: Color565): void;
fillRect(x: number, y: number, w: number, h: number, color: Color565): void;
drawLine(x1: number, y1: number, x2: number, y2: number, color: Color565): void;
drawCircle(x: number, y: number, r: number, color: Color565): void;
drawDisc(x: number, y: number, r: number, borderColor: Color565, fillColor: Color565): void;
drawSquare(x: number, y: number, w: number, h: number, borderColor: Color565, fillColor: Color565): void;
clear(color: Color565): void;
setSprite(assetName: string, id: number, x: number, y: number, tileId: number, paletteId: number, active: boolean, flipX: boolean, flipY: boolean, priority: number): void;
}
interface Color {
@ -64,6 +67,7 @@ declare global {
interface Audio {
playSample(sampleId: number, voiceId: number, volume: number, pan: number, pitch: number): void;
play(assetName: string, sampleId: number, voiceId: number, volume: number, pan: number, pitch: number, loopMode: number): void;
}
interface Fs {

View File

@ -1,4 +1,4 @@
< [Back](chapter-13.md) | [Summary](table-of-contents.md) >
< [Back](chapter-13.md) | [Summary](table-of-contents.md) | [Next](chapter-15.md) >
# Boot Profiles
@ -132,4 +132,4 @@ When `debug == true`:
* New boot modes must be compatible extensions
< [Back](chapter-13.md) | [Summary](table-of-contents.md) >
< [Back](chapter-13.md) | [Summary](table-of-contents.md) | [Next](chapter-15.md) >

View File

@ -0,0 +1,331 @@
< [Back](chapter-14.md) | [Summary](table-of-contents.md) >
# Asset Management
## Bank-Centric Hardware Asset Model
**Scope:** Runtime / Hardware Asset Management (SDK-agnostic)
---
## 1. Fundamental Principles
1. **Every asset in Prometeu resides in a Bank**
2. **A Bank is a hardware memory management system**
3. **Assets are cold binaries stored in the cartridge**
4. **Asset memory belongs to the console, not to the VM**
5. **Loading, residency, and eviction are explicit**
6. **Hardware does not interpret gameplay semantics**
7. **The SDK orchestrates policies; hardware executes contracts**
> In Prometeu, you do not load “data”. You load **residency**.
---
## 2. Asset Origin (Cold Storage)
* All assets initially reside in **cold storage** inside the cartridge.
* Typically, each asset corresponds to a binary file.
* The runtime **never scans the cartridge directly**.
* All access goes through the **Asset Table**.
---
## 3. Asset Table
The Asset Table is an index loaded at cartridge boot.
It describes **content**, not residency.
### Location
* The Asset Table **must be embedded as JSON inside `manifest.json`**.
* Tooling may compile this JSON into a binary table for runtime use, but the source of truth is the manifest.
### Required Fields (conceptual)
* `asset_id` (integer, internal identifier)
* `asset_name` (string, user-facing identifier)
* `asset_type` (TILEBANK, SOUNDBANK, BLOB, TILEMAP, ...)
* `bank_kind` (mandatory, single)
* `offset` (byte offset in cartridge)
* `size` (cold size)
* `decoded_size` (resident size)
* `codec` (RAW, LZ4, ZSTD, ...)
* `asset_metadata` (type-specific)
> `bank_kind` defines **where** an asset may reside in hardware.
---
## 4. Bank — Definition
> **A Bank is the residency and swapping mechanism of the Prometeu hardware.**
A Bank:
* owns **numbered slots**
* enforces **resident memory budgets**
* enforces **staging / inflight budgets**
* accepts only compatible assets
* supports **atomic swap via commit**
* exposes memory and residency metrics
Banks are hardware infrastructure, not assets.
---
## 5. BankKind
Each Bank belongs to a **BankKind**, defining its pipeline and constraints.
### Example BankKinds
* `GFX_TILEBANK`
* `AUDIO_SOUNDBANK`
* `DATA_BLOBBANK`
* `MAP_TILEMAPBANK`
Each BankKind defines:
* slot count
* maximum resident memory
* inflight / staging memory budget
* decode and validation pipeline
* hardware consumer subsystem (renderer, audio mixer, etc.)
---
## 6. Slots and Slot References
* Each BankKind owns a fixed set of **slots**.
* A slot:
* references a resident asset (or is empty)
* never stores data directly
* may expose a **generation counter** (debug)
### Slot Reference
Slots are always referenced with explicit BankKind context:
* `gfxSlot(3)`
* `audioSlot(1)`
* `blobSlot(7)`
This prevents cross-bank ambiguity.
---
## 7. Bank Memory Model
* Bank memory is **console-owned memory**.
* It does not belong to the VM heap.
* It does not participate in GC.
* It can be fully released when the cartridge shuts down.
Each Bank manages:
* total memory
* used memory
* free memory
* inflight (staging) memory
Conceptually, each Bank is a **specialized allocator**.
---
## 8. Unified Loader
### Conceptual API
```text
handle = asset.load(asset_name, slotRef, flags)
```
Load flow:
1. Resolve `asset_name` via Asset Table to get its `asset_id`
2. Read `bank_kind` from asset entry
3. Validate compatibility with `slotRef`
4. Enqueue load request
5. Perform IO + decode on worker
6. Produce materialized asset in staging
### Handle States
* `PENDING` — enqueued
* `LOADING` — IO/decode in progress
* `READY` — staging completed
* `COMMITTED` — installed into slot
* `CANCELED`
* `ERROR`
---
## 9. Commit
```text
asset.commit(handle)
```
* Commit is **explicit** and **atomic**
* Executed at a safe frame boundary
* Performs **pointer swap** in the target slot
* Previous asset is released if no longer referenced
The hardware **never swaps slots automatically**.
---
## 10. Asset Deduplication
* A decoded asset exists **at most once per BankKind**.
* Multiple slots may reference the same resident asset.
* Redundant loads become **install-only** operations.
Memory duplication is forbidden by contract.
---
## 11. Bank Specializations
### 11.1 GFX_TILEBANK
* AssetType: TILEBANK
* Immutable graphical tile + palette structure
* Consumed by the graphics subsystem
### 11.2 AUDIO_SOUNDBANK
* AssetType: SOUNDBANK
* Resident audio samples or streams
* Consumed by the audio mixer
### 11.3 DATA_BLOBBANK
* AssetType: BLOB
* Read-only byte buffers
* Hardware does not interpret contents
Used by the SDK via:
* **Views** (zero-copy, read-only)
* **Decode** (materialization into VM heap)
---
## 12. Views and Decode (SDK Contract)
* **View**
* read-only
* zero-copy
* valid only while the blob remains resident
* **Decode**
* allocates VM-owned entities
* independent from the Bank after creation
Hardware is unaware of Views and Decode semantics.
---
## 13. Manifest and Initial Load
* The cartridge may include a `manifest.json`.
* The manifest may declare:
* assets to preload
* target slot references
These loads occur **before the first frame**.
---
## 14. Shutdown and Eviction
* On cartridge shutdown:
* all Banks are cleared
* all resident memory is released
No implicit persistence exists.
---
## 15. Debugger and Telemetry
Each Bank must expose:
* total memory
* used memory
* free memory
* inflight memory
* occupied slots
* `asset_id` per slot
* `asset_name` per slot
* generation per slot
This enables debuggers to visualize:
* memory stacks per Bank
* memory pressure
* streaming strategies
---
## 16. Minimal Syscall API (Derived)
The following syscalls form the minimal hardware contract for asset management:
```text
asset.load(asset_name, slotRef, flags) -> handle
asset.status(handle) -> LoadStatus
asset.commit(handle)
asset.cancel(handle)
bank.info(bank_kind) -> BankStats
bank.slot_info(slotRef) -> SlotStats
```
Where:
* `LoadStatus` ∈ { PENDING, LOADING, READY, COMMITTED, CANCELED, ERROR }
* `BankStats` exposes memory usage and limits
* `SlotStats` exposes current asset_id, asset_name and generation
---
## 17. Separation of Responsibilities
### Hardware (Prometeu)
* manages memory
* loads bytes
* decodes assets
* swaps pointers
* reports usage
### SDK
* defines packs, scenes, and policies
* interprets blobs
* creates VM entities
### VM
* executes logic
* manages its own heap
* never owns hardware assets
---
## 18. Golden Rule
> **Banks are the foundation of the hardware.**
> **Asset Types describe content.**
> **The SDK orchestrates; the hardware executes.**
< [Back](chapter-14.md) | [Summary](table-of-contents.md) >

View File

@ -14,6 +14,7 @@
- [Chapter 12: Firmware — PrometeuOS (POS) + PrometeuHub](chapter-12.md)
- [Chapter 13: Cartridge](chapter-13.md)
- [Chapter 14: Boot Profiles](chapter-14.md)
- [Chapter 15: Asset Management](chapter-15.md)
---
[Back to README](../README.md)

View File

@ -1,80 +0,0 @@
[
{
"pc": 20,
"file": "temp_test_2.ts",
"line": 1,
"col": 1
},
{
"pc": 22,
"file": "temp_test_2.ts",
"line": 2,
"col": 13
},
{
"pc": 28,
"file": "temp_test_2.ts",
"line": 2,
"col": 17
},
{
"pc": 34,
"file": "temp_test_2.ts",
"line": 2,
"col": 13
},
{
"pc": 46,
"file": "temp_test_2.ts",
"line": 5,
"col": 8
},
{
"pc": 48,
"file": "temp_test_2.ts",
"line": 6,
"col": 10
},
{
"pc": 54,
"file": "temp_test_2.ts",
"line": 6,
"col": 14
},
{
"pc": 60,
"file": "temp_test_2.ts",
"line": 6,
"col": 5
},
{
"pc": 70,
"file": "temp_test_2.ts",
"line": 6,
"col": 5
},
{
"pc": 72,
"file": "temp_test_2.ts",
"line": 7,
"col": 15
},
{
"pc": 78,
"file": "temp_test_2.ts",
"line": 7,
"col": 18
},
{
"pc": 84,
"file": "temp_test_2.ts",
"line": 7,
"col": 5
},
{
"pc": 90,
"file": "temp_test_2.ts",
"line": 7,
"col": 5
}
]

View File

@ -1,23 +0,0 @@
00000000 Call U32(46) U32(0)
0000000A Pop
0000000C FrameSync
0000000E Jmp U32(0)
00000014 PushScope ; temp_test_2.ts:1
00000016 GetLocal U32(0) ; temp_test_2.ts:2
0000001C GetLocal U32(1) ; temp_test_2.ts:2
00000022 Add ; temp_test_2.ts:2
00000024 PopScope
00000026 PushConst U32(0)
0000002C Ret
0000002E PushScope ; temp_test_2.ts:5
00000030 PushI32 U32(10) ; temp_test_2.ts:6
00000036 PushI32 U32(20) ; temp_test_2.ts:6
0000003C Call U32(20) U32(2) ; temp_test_2.ts:6
00000046 Pop ; temp_test_2.ts:6
00000048 PushI32 U32(1) ; temp_test_2.ts:7
0000004E PushConst U32(1) ; temp_test_2.ts:7
00000054 Syscall U32(20481) ; temp_test_2.ts:7
0000005A Pop ; temp_test_2.ts:7
0000005C PopScope
0000005E PushConst U32(0)
00000064 Ret

View File

@ -3,156 +3,170 @@
0000000C FrameSync
0000000E Jmp U32(0)
00000014 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:5
00000016 Call U32(428) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
00000016 Call U32(92) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
00000020 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
00000022 Call U32(236) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
00000022 Call U32(564) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
0000002C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
0000002E Call U32(362) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
0000002E Call U32(702) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
00000038 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
0000003A Call U32(92) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
0000003A Call U32(420) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
00000044 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
00000046 Call U32(644) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
00000046 Call U32(308) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
00000050 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
00000052 PopScope
00000054 PushConst U32(0)
0000005A Ret
0000005C PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:1
0000005E PushConst U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
00000064 Syscall U32(16385) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
0000006A GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
00000070 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
00000076 Gte ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
00000078 JmpIfFalse U32(226) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
0000007E PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
00000080 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
00000086 PushConst U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
0000008C Syscall U32(16387) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
00000092 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
00000094 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
0000009A Syscall U32(16386) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
000000A0 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000A6 JmpIfFalse U32(204) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000AC PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000B2 PushI32 U32(101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000B8 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000BE Syscall U32(20482) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000C4 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000000C6 Jmp U32(204)
000000CC GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
000000D2 Syscall U32(16388) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
000000D8 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
000000DA PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
000000DC Jmp U32(226)
000000E2 PopScope
000000E4 PushConst U32(0)
000000EA Ret
000000EC PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:1
000000EE PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
000000F4 Syscall U32(8193) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
000000FA JmpIfFalse U32(286) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
00000100 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
00000102 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
00000108 PushConst U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
0000010E Syscall U32(20481) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
00000114 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
00000116 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
00000118 Jmp U32(286)
0000011E PushI32 U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
00000124 Syscall U32(8194) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
0000012A JmpIfFalse U32(352) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
00000130 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
00000132 PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000138 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
0000013E PushI32 U32(255) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000144 PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
0000014A PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000150 Syscall U32(12289) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000156 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000158 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
0000015A Jmp U32(352)
00000160 PopScope
00000162 PushConst U32(0)
00000168 Ret
0000016A PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:11
0000016C Syscall U32(8451) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
00000172 JmpIfFalse U32(418) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
00000178 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
0000017A Syscall U32(8449) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000180 Syscall U32(8450) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000186 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
0000018C PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000192 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000198 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
0000019A PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
0000019C Jmp U32(418)
000001A2 PopScope
000001A4 PushConst U32(0)
000001AA Ret
000001AC PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:1
000001AE PushI32 U32(18448) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
000001B4 Syscall U32(4097) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
000001BA Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
000001BC PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001C2 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001C8 PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001CE PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001D4 PushI32 U32(63488) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001DA Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001E0 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
000001E2 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000001E8 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000001EE PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000001F4 PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000001FA PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
00000200 Syscall U32(4099) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
00000206 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
00000208 PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
0000020E PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
00000214 PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
0000021A PushI32 U32(31) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
00000220 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
00000226 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
00000228 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
0000022E PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
00000234 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
0000023A PushI32 U32(2016) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
00000240 PushI32 U32(65504) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
00000246 Syscall U32(4101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
0000024C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
0000024E PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000254 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
0000025A PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000260 PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000266 PushI32 U32(2047) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
0000026C PushI32 U32(63519) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000272 Syscall U32(4102) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000278 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
0000027A PopScope
0000027C PushConst U32(0)
00000282 Ret
00000284 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:10
00000286 PushI32 U32(255) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000028C PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000292 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000294 PushI32 U32(11) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000029A Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000029C PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002A2 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002A8 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002AA PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002B0 Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002B2 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002B4 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002BA PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002C0 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002C2 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
000002C4 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002CA PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002D0 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002D6 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002DC GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002E2 Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002E8 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
000002EA PopScope
000002EC PushConst U32(0)
000002F2 Ret
0000005C PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:1
0000005E PushI32 U32(18448) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
00000064 Syscall U32(4097) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
0000006A Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
0000006C PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
00000072 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
00000078 PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
0000007E PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
00000084 PushI32 U32(63488) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
0000008A Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
00000090 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
00000092 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
00000098 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
0000009E PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000000A4 PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000000AA PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000000B0 Syscall U32(4099) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000000B6 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
000000B8 PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
000000BE PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
000000C4 PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
000000CA PushI32 U32(31) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
000000D0 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
000000D6 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
000000D8 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000DE PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000E4 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000EA PushI32 U32(2016) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000F0 PushI32 U32(65504) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000F6 Syscall U32(4101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000FC Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
000000FE PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000104 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
0000010A PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000110 PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000116 PushI32 U32(2047) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
0000011C PushI32 U32(63519) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000122 Syscall U32(4102) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
00000128 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
0000012A PopScope
0000012C PushConst U32(0)
00000132 Ret
00000134 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:10
00000136 PushI32 U32(255) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000013C PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000142 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000144 PushI32 U32(11) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000014A Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000014C PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000152 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000158 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000015A PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000160 Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000162 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000164 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
0000016A PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000170 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000172 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
00000174 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
0000017A PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
00000180 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
00000186 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
0000018C GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
00000192 Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
00000198 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
0000019A PopScope
0000019C PushConst U32(0)
000001A2 Ret
000001A4 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:1
000001A6 PushConst U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
000001AC Syscall U32(16385) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
000001B2 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
000001B8 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
000001BE Gte ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
000001C0 JmpIfFalse U32(554) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
000001C6 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
000001C8 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
000001CE PushConst U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
000001D4 Syscall U32(16387) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
000001DA Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
000001DC GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
000001E2 Syscall U32(16386) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
000001E8 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000001EE JmpIfFalse U32(532) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000001F4 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
000001FA PushI32 U32(101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
00000200 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
00000206 Syscall U32(20482) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
0000020C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
0000020E Jmp U32(532)
00000214 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
0000021A Syscall U32(16388) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
00000220 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
00000222 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
00000224 Jmp U32(554)
0000022A PopScope
0000022C PushConst U32(0)
00000232 Ret
00000234 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:1
00000236 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
0000023C Syscall U32(8193) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
00000242 JmpIfFalse U32(614) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
00000248 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
0000024A PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
00000250 PushConst U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
00000256 Syscall U32(20481) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
0000025C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
0000025E PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
00000260 Jmp U32(614)
00000266 PushI32 U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
0000026C Syscall U32(8194) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
00000272 JmpIfFalse U32(692) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
00000278 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
0000027A PushConst U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000280 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000286 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
0000028C PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000292 PushI32 U32(127) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
00000298 PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
0000029E PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
000002A4 Syscall U32(12290) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
000002AA Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
000002AC PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
000002AE Jmp U32(692)
000002B4 PopScope
000002B6 PushConst U32(0)
000002BC Ret
000002BE PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:11
000002C0 PushConst U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002C6 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002CC Syscall U32(8449) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002D2 Syscall U32(8450) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002D8 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002DE PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002E4 PushBool Bool(true) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002E7 PushBool Bool(false) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002EA PushBool Bool(false) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002ED PushI32 U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002F3 Syscall U32(4103) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002F9 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
000002FB Syscall U32(8451) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000301 JmpIfFalse U32(817) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000307 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
00000309 Syscall U32(8449) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
0000030F Syscall U32(8450) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
00000315 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
0000031B PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
00000321 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
00000327 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
00000329 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
0000032B Jmp U32(817)
00000331 PopScope
00000333 PushConst U32(0)
00000339 Ret

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -5,5 +5,37 @@
"title": "Color Square",
"app_version": "0.1.0",
"app_mode": "Game",
"entrypoint": "0"
"entrypoint": "0",
"asset_table": [
{
"asset_id": 0,
"asset_name": "bgm_music",
"bank_type": "SOUNDS",
"offset": 0,
"size": 88200,
"decoded_size": 88200,
"codec": "RAW",
"metadata": {
"sample_rate": 44100
}
},
{
"asset_id": 1,
"asset_name": "mouse_cursor",
"bank_type": "TILES",
"offset": 88200,
"size": 2304,
"decoded_size": 2304,
"codec": "RAW",
"metadata": {
"tile_size": 16,
"width": 16,
"height": 16
}
}
],
"preload": [
{ "asset_name": "bgm_music", "slot": 0 },
{ "asset_name": "mouse_cursor", "slot": 1 }
]
}

View File

@ -1,4 +1,5 @@
#!/bin/bash
set -e
./prometeu-sdk/prometeu build .
cp build/program.pbc cartridge

View File

@ -2,7 +2,7 @@ import {do_init_gfx, print_orange} from "./my_gfx";
import {do_pad, do_touch} from "./my_input";
import {do_fs} from "./my_fs";
export function tick(): void {
export function frame(): void {
do_init_gfx();
do_pad();
do_touch();

View File

@ -4,12 +4,13 @@ export function do_pad(): void {
}
if (pad.a.pressed) {
audio.playSample(1, 0, 255, 128, 1.0);
audio.play("bgm_music", 0, 0, 128, 127, 1.0, 1);
}
}
export function do_touch(): void {
gfx.setSprite("mouse_cursor", 0, touch.x, touch.y, 0, 0, true, false, false, 4);
if (touch.button.down) {
gfx.drawCircle(touch.x, touch.y, 5, color.white);
gfx.drawCircle(touch.x, touch.y, 10, color.white);
}
}