diff --git a/crates/console/prometeu-system/src/lib.rs b/crates/console/prometeu-system/src/lib.rs index 0d2b441f..d67c855d 100644 --- a/crates/console/prometeu-system/src/lib.rs +++ b/crates/console/prometeu-system/src/lib.rs @@ -10,6 +10,7 @@ pub use services::fs; pub use services::process; pub use services::task; pub use services::vm_runtime::{ + RenderWorkerBackend, RenderWorkerConfig, RenderWorkerController, RenderWorkerFrameSink, RenderWorkerHandoff, RenderWorkerHandoffWait, VirtualMachineRuntime, }; pub use services::windows; diff --git a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs index 66709b9b..2a5813a6 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/mod.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/mod.rs @@ -4,6 +4,7 @@ mod dispatch; mod frame_scheduler; mod lifecycle; pub mod render_manager; +mod render_worker; pub mod render_worker_handoff; #[cfg(test)] pub(crate) mod render_worker_test_harness; @@ -20,6 +21,9 @@ use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier}; use prometeu_hal::{Gfx2dCommand, GfxUiCommand}; use prometeu_vm::VirtualMachine; pub use render_manager::{RenderManager, RenderRuntimeCapabilities}; +pub use render_worker::{ + RenderWorkerBackend, RenderWorkerConfig, RenderWorkerController, RenderWorkerFrameSink, +}; pub use render_worker_handoff::{RenderWorkerHandoff, RenderWorkerHandoffWait}; use std::collections::HashMap; use std::sync::Arc; diff --git a/crates/console/prometeu-system/src/services/vm_runtime/render_worker.rs b/crates/console/prometeu-system/src/services/vm_runtime/render_worker.rs new file mode 100644 index 00000000..b89a8919 --- /dev/null +++ b/crates/console/prometeu-system/src/services/vm_runtime/render_worker.rs @@ -0,0 +1,291 @@ +use crate::services::vm_runtime::{RenderWorkerHandoff, RenderWorkerHandoffWait}; +use prometeu_hal::{ + FrameId, OwnedRgba8888Frame, RenderSubmission, RenderWorkerError, RenderWorkerTelemetry, +}; +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::Duration; + +pub trait RenderWorkerBackend: Send + 'static { + fn render( + &self, + submission: &RenderSubmission, + ) -> Result; +} + +pub trait RenderWorkerFrameSink: Send + 'static { + fn publish(&self, frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError>; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RenderWorkerConfig { + pub shutdown_timeout: Duration, +} + +impl Default for RenderWorkerConfig { + fn default() -> Self { + Self { shutdown_timeout: Duration::from_millis(250) } + } +} + +#[derive(Debug, Default)] +struct RenderWorkerState { + telemetry: RenderWorkerTelemetry, + last_error: Option, +} + +#[derive(Debug)] +pub struct RenderWorkerController { + config: RenderWorkerConfig, + handoff: Arc, + state: Arc>, + handle: Option>, + done_rx: Receiver>, +} + +impl RenderWorkerController { + pub fn start( + config: RenderWorkerConfig, + handoff: Arc, + backend: B, + sink: S, + ) -> Self + where + B: RenderWorkerBackend, + S: RenderWorkerFrameSink, + { + let state = Arc::new(Mutex::new(RenderWorkerState::default())); + let worker_handoff = Arc::clone(&handoff); + let worker_state = Arc::clone(&state); + let panic_state = Arc::clone(&state); + let (done_tx, done_rx) = mpsc::channel(); + + let handle = thread::spawn(move || { + let result = catch_unwind(AssertUnwindSafe(|| { + run_worker_loop(worker_handoff, worker_state, backend, sink); + })) + .map_err(|_| RenderWorkerError::WorkerPanic); + if let Err(error) = result { + record_error(&panic_state, error, FrameId::ZERO); + } + let _ = done_tx.send(result); + }); + + Self { config, handoff, state, handle: Some(handle), done_rx } + } + + pub fn stop(&mut self) -> Result<(), RenderWorkerError> { + self.handoff.request_shutdown(); + match self.done_rx.recv_timeout(self.config.shutdown_timeout) { + Ok(result) => { + if let Some(handle) = self.handle.take() { + if handle.join().is_err() { + record_error(&self.state, RenderWorkerError::WorkerPanic, FrameId::ZERO); + return Err(RenderWorkerError::WorkerPanic); + } + } + result + } + Err(RecvTimeoutError::Timeout) => { + record_error(&self.state, RenderWorkerError::ShutdownTimeout, FrameId::ZERO); + Err(RenderWorkerError::ShutdownTimeout) + } + Err(RecvTimeoutError::Disconnected) => { + record_error(&self.state, RenderWorkerError::InternalFailure, FrameId::ZERO); + Err(RenderWorkerError::InternalFailure) + } + } + } + + pub fn telemetry(&self) -> RenderWorkerTelemetry { + let mut telemetry = self.handoff.telemetry(); + let state = self.state.lock().unwrap(); + telemetry.published_frames = state.telemetry.published_frames; + telemetry.render_failures = state.telemetry.render_failures; + telemetry.publish_failures = state.telemetry.publish_failures; + telemetry.present_failures = state.telemetry.present_failures; + telemetry.shutdown_timeouts = state.telemetry.shutdown_timeouts; + telemetry.worker_panics = state.telemetry.worker_panics; + telemetry.last_published_frame_id = state.telemetry.last_published_frame_id; + telemetry.last_error_frame_id = state.telemetry.last_error_frame_id; + telemetry + } + + pub fn last_error(&self) -> Option { + self.state.lock().unwrap().last_error + } +} + +fn run_worker_loop( + handoff: Arc, + state: Arc>, + backend: B, + sink: S, +) where + B: RenderWorkerBackend, + S: RenderWorkerFrameSink, +{ + loop { + let submission = match handoff.wait_take() { + RenderWorkerHandoffWait::Submission(submission) => submission, + RenderWorkerHandoffWait::Shutdown => return, + }; + let frame_id = submission.frame_id; + + let render_result = catch_unwind(AssertUnwindSafe(|| backend.render(&submission))) + .map_err(|_| RenderWorkerError::WorkerPanic); + let frame = match render_result { + Ok(Ok(frame)) => frame, + Ok(Err(error)) | Err(error) => { + record_error(&state, error, frame_id); + continue; + } + }; + + let publish_result = catch_unwind(AssertUnwindSafe(|| sink.publish(frame))) + .map_err(|_| RenderWorkerError::WorkerPanic); + match publish_result { + Ok(Ok(())) => record_published(&state, frame_id), + Ok(Err(error)) | Err(error) => record_error(&state, error, frame_id), + } + } +} + +fn record_published(state: &Arc>, frame_id: FrameId) { + state.lock().unwrap().telemetry.record_published(frame_id.get()); +} + +fn record_error( + state: &Arc>, + error: RenderWorkerError, + frame_id: FrameId, +) { + let mut state = state.lock().unwrap(); + state.last_error = Some(error); + state.telemetry.record_error(error, frame_id.get()); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::services::vm_runtime::render_worker_test_harness::{ + FakeRenderBackend, RenderGate, game_submission, + }; + use prometeu_hal::{FrameId, OwnedRgba8888Frame}; + + impl RenderWorkerBackend for Arc { + fn render( + &self, + submission: &RenderSubmission, + ) -> Result { + FakeRenderBackend::render(self, submission) + } + } + + impl RenderWorkerFrameSink for Arc { + fn publish(&self, frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError> { + FakeRenderBackend::publish(self, frame); + Ok(()) + } + } + + #[derive(Debug, Default)] + struct PanickingBackend; + + impl RenderWorkerBackend for PanickingBackend { + fn render( + &self, + _submission: &RenderSubmission, + ) -> Result { + panic!("backend panic") + } + } + + #[derive(Debug, Default)] + struct NoopSink; + + impl RenderWorkerFrameSink for NoopSink { + fn publish(&self, _frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError> { + Ok(()) + } + } + + #[test] + fn render_worker_controller_starts_and_stops_waiting_worker() { + let handoff = Arc::new(RenderWorkerHandoff::default()); + let backend = Arc::new(FakeRenderBackend::default()); + let mut controller = RenderWorkerController::start( + RenderWorkerConfig::default(), + Arc::clone(&handoff), + Arc::clone(&backend), + backend, + ); + + assert_eq!(controller.stop(), Ok(())); + assert_eq!(controller.last_error(), None); + } + + #[test] + fn render_worker_controller_renders_and_publishes_submission() { + let handoff = Arc::new(RenderWorkerHandoff::default()); + let backend = Arc::new(FakeRenderBackend::default()); + let mut controller = RenderWorkerController::start( + RenderWorkerConfig::default(), + Arc::clone(&handoff), + Arc::clone(&backend), + Arc::clone(&backend), + ); + + handoff.publish(game_submission(11, 1, 1)); + assert_eq!(controller.stop(), Ok(())); + + let frames = backend.published_frames(); + assert_eq!(frames.len(), 1); + assert_eq!(frames[0].frame_id, FrameId::new(11)); + assert_eq!(controller.telemetry().published_frames, 1); + } + + #[test] + fn render_worker_controller_reports_shutdown_timeout() { + let handoff = Arc::new(RenderWorkerHandoff::default()); + let gate = RenderGate::default(); + let backend = Arc::new(FakeRenderBackend::with_gate(gate.clone())); + let mut controller = RenderWorkerController::start( + RenderWorkerConfig { shutdown_timeout: Duration::from_millis(1) }, + Arc::clone(&handoff), + Arc::clone(&backend), + Arc::clone(&backend), + ); + + handoff.publish(game_submission(12, 1, 1)); + gate.wait_for_entries(1); + + assert_eq!(controller.stop(), Err(RenderWorkerError::ShutdownTimeout)); + assert_eq!(controller.last_error(), Some(RenderWorkerError::ShutdownTimeout)); + assert_eq!(controller.telemetry().shutdown_timeouts, 1); + + gate.release_one(); + controller.config.shutdown_timeout = Duration::from_millis(250); + assert_eq!(controller.stop(), Ok(())); + } + + #[test] + fn render_worker_controller_captures_backend_panic_as_typed_failure() { + let handoff = Arc::new(RenderWorkerHandoff::default()); + let mut controller = RenderWorkerController::start( + RenderWorkerConfig::default(), + Arc::clone(&handoff), + PanickingBackend, + NoopSink, + ); + + handoff.publish(game_submission(13, 1, 1)); + assert_eq!(controller.stop(), Ok(())); + + assert_eq!(controller.last_error(), Some(RenderWorkerError::WorkerPanic)); + assert_eq!(controller.telemetry().worker_panics, 1); + assert_eq!(controller.telemetry().last_error_frame_id, 13); + } +} diff --git a/discussion/index.ndjson b/discussion/index.ndjson index a73aa74f..cbce166c 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -2,7 +2,7 @@ {"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."} {"type":"discussion","id":"DSC-0040","status":"done","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-06","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0040-vm-render-parallel-execution-boundary/LSN-0048-render-workers-need-a-closed-packet-contract.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-06"}]} {"type":"discussion","id":"DSC-0041","status":"open","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-06-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0042","status":"in_progress","ticket":"real-render-worker-establishment","title":"Real Render Worker Establishment","created_at":"2026-06-06","updated_at":"2026-06-15","tags":["runtime","renderer","worker","concurrency","host","hal","architecture"],"agendas":[{"id":"AGD-0042","file":"AGD-0042-host-hardware-render-boundary-preparation.md","status":"accepted","created_at":"2026-06-06","updated_at":"2026-06-06"},{"id":"AGD-0043","file":"AGD-0043-real-render-worker-establishment.md","status":"accepted","created_at":"2026-06-06","updated_at":"2026-06-15"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-platform-layer-and-hardwarebridge-elimination.md","status":"accepted","created_at":"2026-06-06","updated_at":"2026-06-06","ref_agenda":"AGD-0042"},{"id":"DEC-0033","file":"DEC-0033-real-render-worker-contract.md","status":"accepted","created_at":"2026-06-15","updated_at":"2026-06-15","ref_agenda":"AGD-0043"}],"plans":[{"id":"PLN-0098","file":"PLN-0098-define-platform-facade-contracts.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0099","file":"PLN-0099-introduce-owned-render-submission-sink.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0100","file":"PLN-0100-migrate-local-render-publication-to-sink.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0101","file":"PLN-0101-remove-immediate-gfx-syscall-rendering.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0102","file":"PLN-0102-introduce-game2d-frame-composer-service.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0103","file":"PLN-0103-introduce-runtime-platform-and-testplatform.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0104","file":"PLN-0104-migrate-vm-runtime-hostcontext-to-platform.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0105","file":"PLN-0105-migrate-firmware-and-hub-to-platform-services.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0106","file":"PLN-0106-migrate-desktop-host-to-platform-services.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0107","file":"PLN-0107-migrate-remaining-platform-domains.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0108","file":"PLN-0108-migrate-tests-to-testplatform.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0109","file":"PLN-0109-remove-hardwarebridge-and-update-specs.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0110","file":"PLN-0110-define-owned-rgba8888-frame-contract.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0111","file":"PLN-0111-define-read-only-render-resource-access.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0112","file":"PLN-0112-define-render-worker-errors-and-telemetry.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0113","file":"PLN-0113-implement-thread-safe-latest-wins-handoff.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0114","file":"PLN-0114-add-deterministic-render-worker-test-harness.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0115","file":"PLN-0115-implement-render-worker-controller-lifecycle.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0116","file":"PLN-0116-implement-fake-local-render-backend.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0117","file":"PLN-0117-integrate-runtime-submission-with-worker.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0118","file":"PLN-0118-add-stale-epoch-and-repeat-frame-behavior.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0119","file":"PLN-0119-integrate-desktop-host-worker-presentation.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0120","file":"PLN-0120-update-real-render-worker-specs.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0121","file":"PLN-0121-final-worker-path-validation-and-hardening.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0042","status":"in_progress","ticket":"real-render-worker-establishment","title":"Real Render Worker Establishment","created_at":"2026-06-06","updated_at":"2026-06-15","tags":["runtime","renderer","worker","concurrency","host","hal","architecture"],"agendas":[{"id":"AGD-0042","file":"AGD-0042-host-hardware-render-boundary-preparation.md","status":"accepted","created_at":"2026-06-06","updated_at":"2026-06-06"},{"id":"AGD-0043","file":"AGD-0043-real-render-worker-establishment.md","status":"accepted","created_at":"2026-06-06","updated_at":"2026-06-15"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-platform-layer-and-hardwarebridge-elimination.md","status":"accepted","created_at":"2026-06-06","updated_at":"2026-06-06","ref_agenda":"AGD-0042"},{"id":"DEC-0033","file":"DEC-0033-real-render-worker-contract.md","status":"accepted","created_at":"2026-06-15","updated_at":"2026-06-15","ref_agenda":"AGD-0043"}],"plans":[{"id":"PLN-0098","file":"PLN-0098-define-platform-facade-contracts.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0099","file":"PLN-0099-introduce-owned-render-submission-sink.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0100","file":"PLN-0100-migrate-local-render-publication-to-sink.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0101","file":"PLN-0101-remove-immediate-gfx-syscall-rendering.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0102","file":"PLN-0102-introduce-game2d-frame-composer-service.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0103","file":"PLN-0103-introduce-runtime-platform-and-testplatform.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0104","file":"PLN-0104-migrate-vm-runtime-hostcontext-to-platform.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0105","file":"PLN-0105-migrate-firmware-and-hub-to-platform-services.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0106","file":"PLN-0106-migrate-desktop-host-to-platform-services.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0107","file":"PLN-0107-migrate-remaining-platform-domains.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0108","file":"PLN-0108-migrate-tests-to-testplatform.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0109","file":"PLN-0109-remove-hardwarebridge-and-update-specs.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-15","ref_decisions":["DEC-0032"]},{"id":"PLN-0110","file":"PLN-0110-define-owned-rgba8888-frame-contract.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0111","file":"PLN-0111-define-read-only-render-resource-access.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0112","file":"PLN-0112-define-render-worker-errors-and-telemetry.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0113","file":"PLN-0113-implement-thread-safe-latest-wins-handoff.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0114","file":"PLN-0114-add-deterministic-render-worker-test-harness.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0115","file":"PLN-0115-implement-render-worker-controller-lifecycle.md","status":"done","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0116","file":"PLN-0116-implement-fake-local-render-backend.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0117","file":"PLN-0117-integrate-runtime-submission-with-worker.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0118","file":"PLN-0118-add-stale-epoch-and-repeat-frame-behavior.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0119","file":"PLN-0119-integrate-desktop-host-worker-presentation.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0120","file":"PLN-0120-update-real-render-worker-specs.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]},{"id":"PLN-0121","file":"PLN-0121-final-worker-path-validation-and-hardening.md","status":"open","created_at":"2026-06-15","updated_at":"2026-06-15","ref_decisions":["DEC-0033"]}],"lessons":[]} {"type":"discussion","id":"DSC-0038","status":"done","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0047","file":"discussion/lessons/DSC-0038-render-frame-packet-boundary/LSN-0047-typed-render-submissions-preserve-domain-boundaries.md","status":"done","created_at":"2026-06-04","updated_at":"2026-06-04"}]} {"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"}]} diff --git a/discussion/workflow/plans/PLN-0115-implement-render-worker-controller-lifecycle.md b/discussion/workflow/plans/PLN-0115-implement-render-worker-controller-lifecycle.md index 509fe667..97bb37b3 100644 --- a/discussion/workflow/plans/PLN-0115-implement-render-worker-controller-lifecycle.md +++ b/discussion/workflow/plans/PLN-0115-implement-render-worker-controller-lifecycle.md @@ -2,7 +2,7 @@ id: PLN-0115 ticket: real-render-worker-establishment title: Implement Render Worker Controller Lifecycle -status: open +status: done created: 2026-06-15 completed: ref_decisions: [DEC-0033]