diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index dd1526e4..b931c981 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -483,6 +483,7 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() { }; assert_eq!(packet.commands.len(), 1); assert!(matches!(packet.commands[0], prometeu_hal::GfxUiCommand::Clear { .. })); + assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } #[test] @@ -782,6 +783,82 @@ fn tick_draw_text_survives_no_scene_frame_path() { assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } +#[test] +fn tick_game_submissions_are_immutable_and_latest_complete_wins() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut log_service = LogService::new(4096); + let mut fs = VirtualFS::new(); + let mut fs_state = FsState::Unmounted; + let mut memcard = MemcardService::new(); + let mut open_files: HashMap = HashMap::new(); + let mut next_handle = 1; + let mut vm = VirtualMachine::default(); + let signals = InputSignals::default(); + let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nFRAME_SYNC\nHALT").expect("assemble"); + let program = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfx2d".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }], + ); + let cartridge = cartridge_with_program(program, caps::GFX); + let mut hardware = Hardware::new(); + + runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); + let first_report = runtime.tick( + &mut log_service, + &mut fs, + &mut fs_state, + &mut memcard, + &mut open_files, + &mut next_handle, + &mut vm, + &signals, + &mut hardware, + ); + assert!(first_report.is_none()); + let first_submission = + runtime.render_manager.latest_complete_submission().expect("first submission").clone(); + let prometeu_hal::RenderSubmissionPacket::Game2D(first_packet) = &first_submission.packet + else { + panic!("expected first Game2D submission"); + }; + assert_eq!(first_packet.gfx2d.len(), 1); + assert!(matches!( + first_packet.gfx2d[0], + prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344) + )); + assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); + + let second_packet = prometeu_hal::Game2DFramePacket::new( + Default::default(), + vec![prometeu_hal::Gfx2dCommand::Clear { color: Color::from_raw(0x55667788) }], + ); + runtime + .render_manager + .close_frame_with_packet(prometeu_hal::RenderSubmissionPacket::Game2D(second_packet)) + .expect("manual Game2D packet matches active mode"); + let latest = runtime.render_manager.latest_complete_submission().expect("latest submission"); + assert_ne!(latest.frame_id, first_submission.frame_id); + let prometeu_hal::RenderSubmissionPacket::Game2D(latest_packet) = &latest.packet else { + panic!("expected latest Game2D submission"); + }; + let prometeu_hal::Gfx2dCommand::Clear { color: latest_color } = latest_packet.gfx2d[0] else { + panic!("expected latest clear command"); + }; + assert_eq!(latest_color, Color::from_raw(0x55667788)); + assert!(matches!( + first_packet.gfx2d[0], + prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344) + )); + prometeu_hal::HardwareBridge::publish_render_submission(&mut hardware, latest); + assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x55667788).raw()); +} + #[test] fn initialize_vm_success_clears_previous_crash_report() { let mut runtime = VirtualMachineRuntime::new(None); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index f6144c2e..e00a58c9 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} diff --git a/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md b/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md index e148e987..acc9350b 100644 --- a/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md +++ b/discussion/workflow/plans/PLN-0086-end-to-end-render-boundary-validation.md @@ -2,7 +2,7 @@ id: PLN-0086 ticket: render-frame-packet-boundary title: End-to-End Render Boundary Validation -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline]