implements PR014 a PR016

This commit is contained in:
bQUARKz 2026-03-03 11:25:19 +00:00
parent a75df486ce
commit 24c81d27a9
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
24 changed files with 386 additions and 116 deletions

View File

@ -1,4 +1,4 @@
.PHONY: fmt fmt-check clippy test ci
.PHONY: fmt fmt-check clippy test test-debugger-socket ci
fmt:
cargo fmt
@ -12,4 +12,7 @@ clippy:
test:
cargo test --workspace --all-targets --all-features --no-fail-fast
test-debugger-socket:
cargo test -p prometeu-host-desktop-winit --lib -- --ignored
ci: fmt-check clippy test

View File

@ -166,6 +166,7 @@ impl Audio {
}
}
#[allow(clippy::too_many_arguments)]
pub fn play(
&mut self,
bank_id: u8,
@ -202,6 +203,7 @@ impl Audio {
// }
}
#[allow(clippy::too_many_arguments)]
pub fn play_sample(
&mut self,
sample: Arc<Sample>,

View File

@ -857,7 +857,7 @@ impl Gfx {
for (row_idx, row) in glyph.iter().enumerate() {
for col_idx in 0..3 {
if (row >> (2 - col_idx)) & 1 == 1 {
let px = x + col_idx as i32;
let px = x + col_idx;
let py = y + row_idx as i32;
self.draw_pixel(px, py, color);
}

View File

@ -35,6 +35,12 @@ pub struct Hardware {
pub assets: AssetManager,
}
impl Default for Hardware {
fn default() -> Self {
Self::new()
}
}
impl HardwareBridge for Hardware {
fn gfx(&self) -> &dyn GfxBridge {
&self.gfx

View File

@ -40,6 +40,12 @@ pub struct MemoryBanks {
sound_bank_pool: Arc<RwLock<[Option<Arc<SoundBank>>; 16]>>,
}
impl Default for MemoryBanks {
fn default() -> Self {
Self::new()
}
}
impl MemoryBanks {
/// Creates a new set of memory banks with empty slots.
pub fn new() -> Self {

View File

@ -12,8 +12,8 @@ impl Touch {
/// Transient flags should last only 1 frame.
pub fn begin_frame(&mut self, signals: &InputSignals) {
self.f.begin_frame(signals.f_signal);
self.x = signals.x_pos.clone();
self.y = signals.y_pos.clone();
self.x = signals.x_pos;
self.y = signals.y_pos;
}
}

View File

@ -1,11 +1,10 @@
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum BootTarget {
#[default]
Hub,
Cartridge { path: String, debug: bool, debug_port: u16 },
}
impl Default for BootTarget {
fn default() -> Self {
Self::Hub
}
Cartridge {
path: String,
debug: bool,
debug_port: u16,
},
}

View File

@ -1,4 +1,5 @@
mod boot_target;
#[allow(clippy::module_inception)]
mod firmware;
pub mod firmware_state;

View File

@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod prometeu_hub;
mod window_manager;

View File

@ -1,15 +1,21 @@
use crate::programs::prometeu_hub::window_manager::WindowManager;
use crate::VirtualMachineRuntime;
use crate::programs::prometeu_hub::window_manager::WindowManager;
use prometeu_hal::HardwareBridge;
use prometeu_hal::color::Color;
use prometeu_hal::log::{LogLevel, LogSource};
use prometeu_hal::window::Rect;
use prometeu_hal::HardwareBridge;
/// PrometeuHub: Launcher and system UI environment.
pub struct PrometeuHub {
pub window_manager: WindowManager,
}
impl Default for PrometeuHub {
fn default() -> Self {
Self::new()
}
}
impl PrometeuHub {
pub fn new() -> Self {
Self { window_manager: WindowManager::new() }

View File

@ -13,6 +13,12 @@ pub struct VirtualFS {
backend: Option<Box<dyn FsBackend>>,
}
impl Default for VirtualFS {
fn default() -> Self {
Self::new()
}
}
impl VirtualFS {
pub fn new() -> Self {
Self { backend: None }
@ -103,8 +109,8 @@ impl VirtualFS {
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Default)]
struct CallCounters {

View File

@ -1,6 +1,6 @@
use crate::fs::{FsBackend, FsState, VirtualFS};
use crate::CrashReport;
use prometeu_bytecode::{Value, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE};
use crate::fs::{FsBackend, FsState, VirtualFS};
use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value};
use prometeu_hal::asset::{BankType, LoadStatus, SlotRef};
use prometeu_hal::button::Button;
use prometeu_hal::cartridge::{AppMode, Cartridge};
@ -11,8 +11,8 @@ use prometeu_hal::syscalls::Syscall;
use prometeu_hal::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
use prometeu_hal::tile::Tile;
use prometeu_hal::vm_fault::VmFault;
use prometeu_hal::{expect_bool, expect_int, HostContext, HostReturn, NativeInterface, SyscallId};
use prometeu_hal::{HardwareBridge, InputSignals};
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int};
use prometeu_vm::{LogicalFrameEndingReason, VirtualMachine};
use std::collections::HashMap;
use std::time::Instant;
@ -152,16 +152,14 @@ impl VirtualMachineRuntime {
}
fn update_fs(&mut self) {
if self.fs_state == FsState::Mounted {
if !self.fs.is_healthy() {
self.log(
LogLevel::Error,
LogSource::Fs,
0,
"Filesystem became unhealthy, unmounting".to_string(),
);
self.unmount_fs();
}
if self.fs_state == FsState::Mounted && !self.fs.is_healthy() {
self.log(
LogLevel::Error,
LogSource::Fs,
0,
"Filesystem became unhealthy, unmounting".to_string(),
);
self.unmount_fs();
}
}
@ -652,7 +650,7 @@ impl NativeInterface for VirtualMachineRuntime {
// gfx.set_sprite(asset_name, id, x, y, tile_id, palette_id, active, flip_x, flip_y, priority)
Syscall::GfxSetSprite => {
let asset_name = match args
.get(0)
.first()
.ok_or_else(|| VmFault::Panic("Missing asset_name".into()))?
{
Value::String(s) => s.clone(),
@ -942,7 +940,7 @@ impl NativeInterface for VirtualMachineRuntime {
// audio.play(asset_name, sample_id, voice_id, volume, pan, pitch, loop_mode)
Syscall::AudioPlay => {
let asset_name = match args
.get(0)
.first()
.ok_or_else(|| VmFault::Panic("Missing asset_name".into()))?
{
Value::String(s) => s.clone(),
@ -978,10 +976,11 @@ impl NativeInterface for VirtualMachineRuntime {
// FS_OPEN(path) -> handle
Syscall::FsOpen => {
let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
let path =
match args.first().ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
if self.fs_state != FsState::Mounted {
ret.push_int(-1);
return Ok(());
@ -1037,10 +1036,11 @@ impl NativeInterface for VirtualMachineRuntime {
}
// FS_LIST_DIR(path)
Syscall::FsListDir => {
let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
let path =
match args.first().ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
match self.fs.list_dir(&path) {
Ok(entries) => {
let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
@ -1052,19 +1052,21 @@ impl NativeInterface for VirtualMachineRuntime {
}
// FS_EXISTS(path)
Syscall::FsExists => {
let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
let path =
match args.first().ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
ret.push_bool(self.fs.exists(&path));
Ok(())
}
// FS_DELETE(path)
Syscall::FsDelete => {
let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
let path =
match args.first().ok_or_else(|| VmFault::Panic("Missing path".into()))? {
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())),
};
match self.fs.delete(&path) {
Ok(_) => ret.push_bool(true),
Err(_) => ret.push_bool(false),
@ -1107,7 +1109,7 @@ impl NativeInterface for VirtualMachineRuntime {
// --- Asset Syscalls ---
Syscall::AssetLoad => {
let asset_id = match args
.get(0)
.first()
.ok_or_else(|| VmFault::Panic("Missing asset_id".into()))?
{
Value::String(s) => s.clone(),
@ -1190,13 +1192,13 @@ impl NativeInterface for VirtualMachineRuntime {
#[cfg(test)]
mod tests {
use super::*;
use prometeu_bytecode::TRAP_TYPE;
use prometeu_bytecode::assembler::assemble;
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl};
use prometeu_bytecode::TRAP_TYPE;
use prometeu_drivers::hardware::Hardware;
use prometeu_hal::InputSignals;
use prometeu_hal::cartridge::Cartridge;
use prometeu_hal::syscalls::caps;
use prometeu_hal::InputSignals;
use prometeu_vm::VmInitError;
fn cartridge_with_program(program: Vec<u8>, capabilities: u64) -> Cartridge {

View File

@ -2,20 +2,20 @@ use crate::call_frame::CallFrame;
use crate::heap::{CoroutineState, Heap};
use crate::lookup_intrinsic_by_id;
use crate::object::ObjectKind;
use crate::roots::{visit_value_for_roots, RootVisitor};
use crate::roots::{RootVisitor, visit_value_for_roots};
use crate::scheduler::Scheduler;
use crate::verifier::Verifier;
use crate::vm_init_error::{LoaderPatchError, VmInitError};
use crate::{HostContext, NativeInterface};
use prometeu_bytecode::decode_next;
use prometeu_bytecode::isa::core::CoreOpCode as OpCode;
use prometeu_bytecode::model::BytecodeModule;
use prometeu_bytecode::HeapRef;
use prometeu_bytecode::ProgramImage;
use prometeu_bytecode::Value;
use prometeu_bytecode::decode_next;
use prometeu_bytecode::isa::core::CoreOpCode as OpCode;
use prometeu_bytecode::model::BytecodeModule;
use prometeu_bytecode::{
TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_EXPLICIT, TRAP_INVALID_FUNC,
TRAP_INVALID_INTRINSIC, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE,
TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_EXPLICIT, TRAP_INVALID_FUNC, TRAP_INVALID_INTRINSIC,
TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE, TrapInfo,
};
use prometeu_hal::syscalls::caps::NONE;
use prometeu_hal::vm_fault::VmFault;
@ -1369,7 +1369,7 @@ impl VirtualMachine {
// Copy return values (preserving order: pop return_slots values, then reverse to push back)
let mut return_vals = Vec::with_capacity(return_slots);
for _ in 0..return_slots {
return_vals.push(self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?);
return_vals.push(self.pop().map_err(LogicalFrameEndingReason::Panic)?);
}
return_vals.reverse();
@ -1799,8 +1799,8 @@ mod tests {
use crate::HostReturn;
use prometeu_bytecode::model::{BytecodeModule, SyscallDecl};
use prometeu_bytecode::{
assemble, disassemble, FunctionMeta, TRAP_EXPLICIT, TRAP_INVALID_LOCAL, TRAP_OOB,
TRAP_STACK_UNDERFLOW, TRAP_TYPE,
FunctionMeta, TRAP_EXPLICIT, TRAP_INVALID_LOCAL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE,
assemble, disassemble,
};
use prometeu_hal::expect_int;

View File

@ -1,8 +1,8 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use prometeu_drivers::{AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
use prometeu_hal::LoopMode;
use ringbuf::traits::{Consumer, Producer, Split};
use ringbuf::HeapRb;
use ringbuf::traits::{Consumer, Producer, Split};
use std::error::Error;
use std::fmt;
use std::sync::Arc;
@ -93,6 +93,12 @@ pub struct HostAudio {
_stream: Option<Box<dyn AudioStreamHandle>>,
}
impl Default for HostAudio {
fn default() -> Self {
Self::new()
}
}
impl HostAudio {
pub fn new() -> Self {
Self { producer: None, perf_consumer: None, _stream: None }
@ -150,7 +156,7 @@ impl HostAudio {
pub fn send_commands(&mut self, commands: &mut Vec<AudioCommand>) {
if let Some(producer) = &mut self.producer {
for cmd in commands.drain(..) {
if let Err(_) = producer.try_push(cmd) {
if producer.try_push(cmd).is_err() {
eprintln!("[HostAudio] Command ringbuffer full, dropping command.");
}
}
@ -301,6 +307,12 @@ pub struct AudioMixer {
paused: bool,
}
impl Default for AudioMixer {
fn default() -> Self {
Self::new()
}
}
impl AudioMixer {
pub fn new() -> Self {
Self { voices: Default::default(), last_processing_time: Duration::ZERO, paused: false }

View File

@ -27,6 +27,12 @@ pub struct HostDebugger {
last_fault_summary: Option<String>,
}
impl Default for HostDebugger {
fn default() -> Self {
Self::new()
}
}
impl HostDebugger {
/// Creates a new debugger interface in an idle state.
pub fn new() -> Self {
@ -70,21 +76,21 @@ impl HostDebugger {
/// Sends a structured response to the connected debugger client.
fn send_response(&mut self, resp: DebugResponse) {
if let Some(stream) = &mut self.stream {
if let Ok(json) = serde_json::to_string(&resp) {
let _ = stream.write_all(json.as_bytes());
let _ = stream.write_all(b"\n");
}
if let Some(stream) = &mut self.stream
&& let Ok(json) = serde_json::to_string(&resp)
{
let _ = stream.write_all(json.as_bytes());
let _ = stream.write_all(b"\n");
}
}
/// Sends an asynchronous event to the connected debugger client.
fn send_event(&mut self, event: DebugEvent) {
if let Some(stream) = &mut self.stream {
if let Ok(json) = serde_json::to_string(&event) {
let _ = stream.write_all(json.as_bytes());
let _ = stream.write_all(b"\n");
}
if let Some(stream) = &mut self.stream
&& let Ok(json) = serde_json::to_string(&event)
{
let _ = stream.write_all(json.as_bytes());
let _ = stream.write_all(b"\n");
}
}
@ -92,31 +98,31 @@ impl HostDebugger {
/// It handles new connections and processes incoming commands.
pub fn check_commands(&mut self, firmware: &mut Firmware, hardware: &mut Hardware) {
// 1. Accept new client connections.
if let Some(listener) = &self.listener {
if let Ok((stream, _addr)) = listener.accept() {
// Currently, only one debugger client is supported at a time.
if self.stream.is_none() {
println!("[Debugger] Connection received!");
stream.set_nonblocking(true).expect("Cannot set non-blocking on stream");
if let Some(listener) = &self.listener
&& let Ok((stream, _addr)) = listener.accept()
{
// Currently, only one debugger client is supported at a time.
if self.stream.is_none() {
println!("[Debugger] Connection received!");
stream.set_nonblocking(true).expect("Cannot set non-blocking on stream");
self.stream = Some(stream);
self.last_fault_summary = None;
self.stream = Some(stream);
self.last_fault_summary = None;
// Immediately send the Handshake message to identify the Runtime and App.
let handshake = DebugResponse::Handshake {
protocol_version: DEVTOOLS_PROTOCOL_VERSION,
runtime_version: "0.1".to_string(),
cartridge: HandshakeCartridge {
app_id: firmware.os.current_app_id,
title: firmware.os.current_cartridge_title.clone(),
app_version: firmware.os.current_cartridge_app_version.clone(),
app_mode: firmware.os.current_cartridge_app_mode,
},
};
self.send_response(handshake);
} else {
println!("[Debugger] Connection refused: already connected.");
}
// Immediately send the Handshake message to identify the Runtime and App.
let handshake = DebugResponse::Handshake {
protocol_version: DEVTOOLS_PROTOCOL_VERSION,
runtime_version: "0.1".to_string(),
cartridge: HandshakeCartridge {
app_id: firmware.os.current_app_id,
title: firmware.os.current_cartridge_title.clone(),
app_version: firmware.os.current_cartridge_app_version.clone(),
app_mode: firmware.os.current_cartridge_app_mode,
},
};
self.send_response(handshake);
} else {
println!("[Debugger] Connection refused: already connected.");
}
}

View File

@ -8,6 +8,12 @@ pub struct HostInputHandler {
pub signals: InputSignals,
}
impl Default for HostInputHandler {
fn default() -> Self {
Self::new()
}
}
impl HostInputHandler {
pub fn new() -> Self {
Self { signals: InputSignals::default() }

View File

@ -4,6 +4,12 @@ pub struct HostConsoleSink {
last_seq: Option<u64>,
}
impl Default for HostConsoleSink {
fn default() -> Self {
Self::new()
}
}
impl HostConsoleSink {
pub fn new() -> Self {
Self { last_seq: None }

View File

@ -7,8 +7,8 @@ use crate::stats::HostStats;
use crate::utilities::draw_rgb565_to_rgba8;
use pixels::wgpu::PresentMode;
use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use prometeu_drivers::hardware::Hardware;
use prometeu_drivers::AudioCommand;
use prometeu_drivers::hardware::Hardware;
use prometeu_firmware::{BootTarget, Firmware};
use prometeu_hal::color::Color;
use prometeu_hal::telemetry::CertificationConfig;
@ -190,8 +190,8 @@ impl HostRunner {
let cert_color = if tel.violations > 0 { color_warn } else { color_text };
self.hardware.gfx.draw_text(10, 82, &format!("CERT LAST: {}", tel.violations), cert_color);
if tel.violations > 0 {
if let Some(event) = self
if tel.violations > 0
&& let Some(event) = self
.firmware
.os
.log_service
@ -199,13 +199,12 @@ impl HostRunner {
.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, 90, &msg, color_warn);
{
let mut msg = event.msg.clone();
if msg.len() > 30 {
msg.truncate(30);
}
self.hardware.gfx.draw_text(10, 90, &msg, color_warn);
}
if let Some(report) = self.firmware.os.last_crash_report.as_ref() {
@ -315,11 +314,11 @@ impl ApplicationHandler for HostRunner {
// 2. Maintain filesystem connection if it was lost (e.g., directory removed).
if let Some(root) = &self.fs_root {
use prometeu_system::fs::FsState;
if matches!(self.firmware.os.fs_state, FsState::Unmounted | FsState::Error(_)) {
if std::path::Path::new(root).exists() {
let backend = HostDirBackend::new(root);
self.firmware.os.mount_fs(Box::new(backend));
}
if matches!(self.firmware.os.fs_state, FsState::Unmounted | FsState::Error(_))
&& std::path::Path::new(root).exists()
{
let backend = HostDirBackend::new(root);
self.firmware.os.mount_fs(Box::new(backend));
}
}
@ -401,6 +400,7 @@ mod tests {
use std::net::TcpStream;
#[test]
#[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"]
fn test_debug_port_opens() {
let mut runner = HostRunner::new(None, None);
let port = 9999;
@ -460,6 +460,7 @@ mod tests {
}
#[test]
#[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"]
fn test_debug_reconnection() {
let mut runner = HostRunner::new(None, None);
let port = 9998;
@ -502,6 +503,7 @@ mod tests {
}
#[test]
#[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"]
fn test_debug_refuse_second_connection() {
let mut runner = HostRunner::new(None, None);
let port = 9997;
@ -537,6 +539,7 @@ mod tests {
}
#[test]
#[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"]
fn test_get_state_returns_response() {
let mut runner = HostRunner::new(None, None);
let port = 9996;
@ -581,6 +584,7 @@ mod tests {
}
#[test]
#[ignore = "requires localhost TCP bind/connect; run via `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`"]
fn test_debug_resume_on_disconnect() {
let mut runner = HostRunner::new(None, None);
let port = 9995;

View File

@ -11,6 +11,12 @@ pub struct HostStats {
pub audio_load_samples: u64,
}
impl Default for HostStats {
fn default() -> Self {
Self::new()
}
}
impl HostStats {
pub fn new() -> Self {
Self {

View File

@ -89,7 +89,7 @@ pub fn generate() -> Result<()> {
}
#[allow(dead_code)]
fn heavy_load(mut rom: &mut Vec<u8>) {
fn heavy_load(rom: &mut Vec<u8>) {
// Single function 0: main
// Everything runs here — no coroutines, no SPAWN, no YIELD.
//
@ -192,13 +192,13 @@ fn heavy_load(mut rom: &mut Vec<u8>) {
buf[imm_offset..imm_offset + 4].copy_from_slice(&target.to_le_bytes());
};
patch(&mut rom, jif_disc_end_offset, disc_loop_end);
patch(&mut rom, jmp_disc_loop_offset, disc_loop_start);
patch(rom, jif_disc_end_offset, disc_loop_end);
patch(rom, jmp_disc_loop_offset, disc_loop_start);
patch(&mut rom, jif_text_end_offset, text_loop_end);
patch(&mut rom, jif_text_alt_offset, text_alt_target);
patch(&mut rom, jmp_text_join_offset, text_join_target);
patch(&mut rom, jmp_text_loop_offset, text_loop_start);
patch(rom, jif_text_end_offset, text_loop_end);
patch(rom, jif_text_alt_offset, text_alt_target);
patch(rom, jmp_text_join_offset, text_join_target);
patch(rom, jmp_text_loop_offset, text_loop_start);
patch(&mut rom, jif_log_offset, after_log);
patch(rom, jif_log_offset, after_log);
}

View File

@ -0,0 +1,57 @@
# PR-014 [QUALITY]: Stabilize Workspace Formatting
## Briefing
O workspace ja tem uma trilha clara de qualidade com `fmt-check`, `clippy` e `test` no fluxo de CI. Hoje, porem, a barra minima de formatacao nao esta fechando: `cargo fmt -- --check` falha por divergencias de ordenacao de imports e estilo em arquivos ja versionados.
Esta PR trata esse problema como higiene de base. O objetivo nao e "embelezar codigo", e sim restaurar o contrato de estilo para que a arvore volte a ter um estado formatado, previsivel e mecanico.
## Problema
- `cargo fmt -- --check` falha no estado atual do workspace;
- ha arquivos versionados que divergem do estilo canonical do `rustfmt`;
- enquanto isso existir, o target `ci` deixa de ser confiavel como gate minimo de merge;
- a equipe perde sinal: falhas de estilo misturam ruido de higiene com problemas reais de implementacao.
## Alvo
- workspace Rust inteiro;
- arquivos tocados pelo `rustfmt` que hoje geram diff;
- fluxo `fmt-check` definido no `Makefile`.
## Escopo
- Rodar `cargo fmt` no workspace e revisar o diff gerado.
- Confirmar que as mudancas sao estritamente de formatacao, sem alteracao semantica.
- Restaurar `cargo fmt -- --check` para estado verde.
- Se necessario, ajustar qualquer arquivo novo ou recente que tenha escapado do padrao.
## Fora de Escopo
- Refatorar APIs.
- Renomear modulos.
- Resolver warnings de `clippy`.
- Alterar comportamento de testes ou runtime.
## Abordagem
1. Identificar exatamente quais arquivos fazem `fmt-check` falhar.
2. Aplicar `cargo fmt` no workspace.
3. Revisar diff para garantir ausencia de mudanca comportamental.
4. Revalidar com `cargo fmt -- --check`.
## Criterios de Aceite
- `cargo fmt -- --check` passa no workspace.
- O diff da PR e apenas de formatacao.
- Nenhuma logica, assinatura publica ou comportamento de teste muda por causa desta PR.
- O target `fmt-check` do `Makefile` volta a ser um gate confiavel.
## Tests
- Rodar `cargo fmt -- --check`.
- Rodar ao menos `cargo test --workspace --all-targets --all-features --no-fail-fast` se a PR tocar arquivos em crates com testes relevantes, apenas para confirmar ausencia de regressao acidental.
## Risco
Baixo. O risco principal e misturar formatacao com mudanca semantica no mesmo diff. A revisao deve ser severa: qualquer alteracao nao mecanica deve sair desta PR.

View File

@ -0,0 +1,68 @@
# PR-015 [QUALITY]: Burn Down Clippy Warnings
## Briefing
O workspace compila e a suite principal de testes esta saudavel, mas `cargo clippy --workspace --all-features` ainda retorna uma quantidade relevante de warnings. Isso nao e detalhe cosmetico: warnings recorrentes enfraquecem o valor do lint e escondem sinais novos que deveriam chamar atencao imediata.
Esta PR fecha a divida de lint com foco em warnings claros, mecanicos e de baixo risco. A meta e tornar `clippy` um gate com alta relacao sinal/ruido.
## Problema
- `cargo clippy --workspace --all-features` termina com warnings em multiplas crates;
- ha uma mistura de problemas triviais e repetitivos:
- `new_without_default`;
- `collapsible_if`;
- `redundant_closure`;
- `clone_on_copy`;
- `needless_borrow`;
- `unnecessary_cast`;
- `too_many_arguments`;
- `module_inception`;
- enquanto o baseline permanecer ruidoso, novos warnings entram sem friccao.
## Alvo
- crates do workspace que hoje emitem warnings de `clippy`;
- principalmente `prometeu-vm`, `prometeu-system`, `prometeu-host-desktop-winit`, `prometeu-drivers` e `pbxgen-stress`.
## Escopo
- Eliminar warnings de `clippy` que tenham correcao local, objetiva e de baixo risco.
- Introduzir `Default` onde a recomendacao fizer sentido sem distorcer o modelo de construcao.
- Simplificar fluxo de controle e remocoes de ruido sintatico.
- Decidir explicitamente o tratamento de warnings que indiquem tradeoff arquitetural real.
## Fora de Escopo
- Grandes refactors de design apenas para satisfazer lint.
- Mudancas arquiteturais em API publica sem discussao previa.
- Reescrever modulos inteiros por motivos esteticos.
- Alterar a taxonomia arquitetural do runtime.
## Abordagem
1. Classificar warnings em dois grupos:
- mecanicos e sem risco;
- warnings que tocam modelagem publica ou ergonomia de API.
2. Corrigir primeiro o grupo mecanico.
3. Para warnings que implicarem decisao de design, escolher uma de duas saidas:
- corrigir com justificativa clara;
- silenciar localmente com comentario curto e motivo tecnico defensavel.
4. Reexecutar `cargo clippy --workspace --all-features` ate baseline limpo ou explicitamente justificado.
## Criterios de Aceite
- `cargo clippy --workspace --all-features` termina sem warnings, ou com excecoes locais raras e justificadas no codigo.
- Nenhuma mudanca amplia API ou comportamento sem necessidade.
- O resultado final aumenta, e nao reduz, a legibilidade do codigo.
- Warnings mecanicos recorrentes deixam de existir no baseline.
## Tests
- Rodar `cargo clippy --workspace --all-features`.
- Rodar `cargo test --workspace --all-targets --all-features --no-fail-fast`.
- Se alguma correcao tocar caminho quente do runtime, rodar tambem os testes da crate afetada de forma isolada para facilitar triagem.
## Risco
Medio. O risco nao esta no `clippy`; esta em usar o lint como desculpa para refatorar demais. Esta PR deve ser conservadora: eliminar ruido sem inventar arquitetura nova.

View File

@ -0,0 +1,66 @@
# PR-016 [CI]: Separate Network-Dependent Debugger Tests
## Briefing
Os testes do host debugger passam quando executados em ambiente com permissao normal de socket, mas falham em contexto sandboxado que bloqueia bind de porta TCP. Isso cria um problema de engenharia: a suite parece instavel quando, na pratica, o comportamento observado e uma restricao do ambiente, nao um bug do runtime.
Esta PR organiza esses testes para que continuem existindo, continuem sendo exigidos, mas sejam executados na pista correta.
## Problema
- testes de debugger que dependem de abrir porta TCP falham em ambientes com restricao de bind;
- o comando de workspace pode acusar falso negativo quando rodado em sandbox local;
- sem separacao de trilha, fica dificil distinguir:
- falha real do debugger;
- limitacao do ambiente de execucao;
- regressao de CI.
## Alvo
- testes de `prometeu-host-desktop-winit` que abrem listener TCP;
- fluxo de execucao de testes local e de CI;
- documentacao de como rodar a suite completa com requisitos de ambiente.
## Escopo
- Identificar os testes realmente dependentes de socket.
- Separar esses testes por estrategia clara, por exemplo:
- suite dedicada;
- `#[ignore]` com comando explicito de execucao;
- gate por feature ou por condicao de ambiente;
- job dedicado de CI fora de sandbox restritivo.
- Preservar cobertura funcional do debugger.
- Documentar a forma oficial de rodar esses testes.
## Fora de Escopo
- Reescrever o debugger para nao usar TCP.
- Redesenhar o protocolo de debug.
- Remover testes de integracao do debugger.
- Introduzir heuristicas silenciosas que "engulam" falhas reais de rede.
## Abordagem
1. Catalogar os testes que fazem bind de porta e verificar se todos sao realmente de integracao.
2. Escolher uma estrategia explicita de separacao, preferencialmente uma que mantenha os testes presentes no repo e visiveis na CI.
3. Ajustar o comando ou job de CI para executar a suite correta no ambiente correto.
4. Documentar localmente como um desenvolvedor deve rodar:
- suite padrao;
- suite completa com debugger/socket.
## Criterios de Aceite
- O fluxo padrao de testes nao produz falso negativo por restricao de socket do ambiente.
- Os testes de debugger continuam existindo e continuam sendo executados em algum caminho oficial.
- A documentacao deixa claro quando e onde a suite de socket deve rodar.
- Falhas reais do debugger continuam visiveis e nao sao mascaradas por skip silencioso sem criterio.
## Tests
- Rodar o fluxo padrao de testes no ambiente comum do projeto.
- Rodar explicitamente a suite de debugger/socket no ambiente habilitado para bind.
- Validar que o job ou comando documentado realmente cobre os testes de reconexao, porta aberta, segunda conexao e resume apos disconnect.
## Risco
Medio. O risco principal e esconder regressao real atras de condicionais ambientais mal projetadas. A PR deve tratar ambiente como requisito explicito, nunca como desculpa para reduzir cobertura.

View File

@ -25,6 +25,13 @@ Running the layered suite
- Run just the layered tests: `cargo test -p prometeu-layer-tests`
- Run the entire workspace: `cargo test`
Host debugger socket tests
- The host debugger integration tests in `crates/host/prometeu-host-desktop-winit/src/runner.rs` open localhost TCP ports and are marked `#[ignore]` in the default test flow.
- This keeps `cargo test --workspace --all-targets --all-features --no-fail-fast` green in restricted environments that forbid `TcpListener::bind`.
- Run the socket-dependent suite explicitly with `make test-debugger-socket` or `cargo test -p prometeu-host-desktop-winit --lib -- --ignored`.
- These tests must still run in at least one official environment that permits localhost bind/connect.
Separation of concerns
- No crosslayer assumptions in these tests.
@ -34,4 +41,4 @@ Separation of concerns
DRY helpers
- Shared utilities should live in `crates/dev/prometeu-test-support` when needed. The current minimal suite avoids duplication by using tiny local helpers.
- Shared utilities should live in `crates/dev/prometeu-test-support` when needed. The current minimal suite avoids duplication by using tiny local helpers.