dev/system-run-cart #34
@ -297,7 +297,7 @@ pub enum Capability {
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CartridgeManifest {
|
||||
pub magic: String,
|
||||
pub cartridge_version: u32,
|
||||
|
||||
@ -13,6 +13,9 @@ pub use services::async_work::{
|
||||
AsyncWorkLaneController, AsyncWorkLaneError, AsyncWorkLaneTelemetry, AsyncWorkPriority,
|
||||
};
|
||||
pub use services::fs;
|
||||
pub use services::game_library::{
|
||||
GameLibrary, GameLibraryDiagnostic, GameLibraryEntry, discover_games_root,
|
||||
};
|
||||
pub use services::memcard::MemcardAsyncLaneOperation;
|
||||
pub use services::process;
|
||||
pub use services::task;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::{CrashReport, SystemOS};
|
||||
use crate::{CrashReport, GameLibrary, SystemOS};
|
||||
use prometeu_hal::color::Color;
|
||||
use prometeu_hal::primitives::Rect;
|
||||
use prometeu_hal::{
|
||||
@ -29,6 +29,7 @@ const COLOR_BUTTON_ACTIVE: Color = Color::rgb(72, 91, 122);
|
||||
/// PrometeuHub: Launcher and system UI environment.
|
||||
pub struct PrometeuHub {
|
||||
home_input_armed: bool,
|
||||
game_library: GameLibrary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -91,13 +92,21 @@ impl Default for PrometeuHub {
|
||||
|
||||
impl PrometeuHub {
|
||||
pub fn new() -> Self {
|
||||
Self { home_input_armed: false }
|
||||
Self { home_input_armed: false, game_library: GameLibrary::empty() }
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.home_input_armed = false;
|
||||
}
|
||||
|
||||
pub fn set_game_library(&mut self, game_library: GameLibrary) {
|
||||
self.game_library = game_library;
|
||||
}
|
||||
|
||||
pub fn game_library(&self) -> &GameLibrary {
|
||||
&self.game_library
|
||||
}
|
||||
|
||||
pub fn gui_update(
|
||||
&mut self,
|
||||
os: &mut SystemOS,
|
||||
|
||||
270
crates/console/prometeu-system/src/services/game_library.rs
Normal file
270
crates/console/prometeu-system/src/services/game_library.rs
Normal file
@ -0,0 +1,270 @@
|
||||
use prometeu_hal::app_mode::AppMode;
|
||||
use prometeu_hal::cartridge::CartridgeManifest;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameLibraryEntry {
|
||||
pub manifest: CartridgeManifest,
|
||||
pub title: String,
|
||||
pub app_id: u32,
|
||||
pub app_version: String,
|
||||
pub path: PathBuf,
|
||||
pub discovered_at: SystemTime,
|
||||
}
|
||||
|
||||
impl GameLibraryEntry {
|
||||
fn new(path: PathBuf, manifest: CartridgeManifest, discovered_at: SystemTime) -> Self {
|
||||
Self {
|
||||
title: manifest.title.clone(),
|
||||
app_id: manifest.app_id,
|
||||
app_version: manifest.app_version.clone(),
|
||||
manifest,
|
||||
path,
|
||||
discovered_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GameLibraryDiagnostic {
|
||||
pub path: PathBuf,
|
||||
pub reason: GameLibraryDiagnosticReason,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum GameLibraryDiagnosticReason {
|
||||
RootNotDirectory,
|
||||
CandidateReadFailed,
|
||||
MissingManifest,
|
||||
InvalidManifest,
|
||||
UnsupportedVersion,
|
||||
MissingProgram,
|
||||
NonGameAppMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct GameLibrary {
|
||||
entries: Vec<GameLibraryEntry>,
|
||||
diagnostics: Vec<GameLibraryDiagnostic>,
|
||||
}
|
||||
|
||||
impl GameLibrary {
|
||||
pub fn empty() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> &[GameLibraryEntry] {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
pub fn diagnostics(&self) -> &[GameLibraryDiagnostic] {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discover_games_root(root: impl AsRef<Path>) -> GameLibrary {
|
||||
let root = root.as_ref();
|
||||
let discovered_at = SystemTime::now();
|
||||
|
||||
if !root.is_dir() {
|
||||
return GameLibrary {
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![GameLibraryDiagnostic {
|
||||
path: root.to_path_buf(),
|
||||
reason: GameLibraryDiagnosticReason::RootNotDirectory,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
let read_dir = match fs::read_dir(root) {
|
||||
Ok(read_dir) => read_dir,
|
||||
Err(_) => {
|
||||
return GameLibrary {
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![GameLibraryDiagnostic {
|
||||
path: root.to_path_buf(),
|
||||
reason: GameLibraryDiagnosticReason::CandidateReadFailed,
|
||||
}],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let mut entries = Vec::new();
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
for item in read_dir {
|
||||
let Ok(item) = item else {
|
||||
diagnostics.push(GameLibraryDiagnostic {
|
||||
path: root.to_path_buf(),
|
||||
reason: GameLibraryDiagnosticReason::CandidateReadFailed,
|
||||
});
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = item.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match discover_candidate(&path, discovered_at) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(reason) => diagnostics.push(GameLibraryDiagnostic { path, reason }),
|
||||
}
|
||||
}
|
||||
|
||||
entries.sort_by(|left, right| {
|
||||
left.title
|
||||
.cmp(&right.title)
|
||||
.then_with(|| left.app_id.cmp(&right.app_id))
|
||||
.then_with(|| left.path.cmp(&right.path))
|
||||
});
|
||||
|
||||
GameLibrary { entries, diagnostics }
|
||||
}
|
||||
|
||||
fn discover_candidate(
|
||||
path: &Path,
|
||||
discovered_at: SystemTime,
|
||||
) -> Result<GameLibraryEntry, GameLibraryDiagnosticReason> {
|
||||
let manifest_path = path.join("manifest.json");
|
||||
if !manifest_path.exists() {
|
||||
return Err(GameLibraryDiagnosticReason::MissingManifest);
|
||||
}
|
||||
|
||||
let manifest_content = fs::read_to_string(manifest_path)
|
||||
.map_err(|_| GameLibraryDiagnosticReason::InvalidManifest)?;
|
||||
let manifest: CartridgeManifest = serde_json::from_str(&manifest_content)
|
||||
.map_err(|_| GameLibraryDiagnosticReason::InvalidManifest)?;
|
||||
|
||||
if manifest.magic != "PMTU" {
|
||||
return Err(GameLibraryDiagnosticReason::InvalidManifest);
|
||||
}
|
||||
if manifest.cartridge_version != 1 {
|
||||
return Err(GameLibraryDiagnosticReason::UnsupportedVersion);
|
||||
}
|
||||
if manifest.app_mode != AppMode::Game {
|
||||
return Err(GameLibraryDiagnosticReason::NonGameAppMode);
|
||||
}
|
||||
if !path.join("program.pbx").exists() {
|
||||
return Err(GameLibraryDiagnosticReason::MissingProgram);
|
||||
}
|
||||
|
||||
Ok(GameLibraryEntry::new(path.to_path_buf(), manifest, discovered_at))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
static TEST_DIR_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct TestRoot {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl TestRoot {
|
||||
fn new() -> Self {
|
||||
let unique = TEST_DIR_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"prometeu-game-library-test-{}-{}",
|
||||
std::process::id(),
|
||||
unique
|
||||
));
|
||||
fs::create_dir_all(&path).expect("test root should be created");
|
||||
Self { path }
|
||||
}
|
||||
|
||||
fn add_candidate(&self, name: &str, manifest: serde_json::Value, with_program: bool) {
|
||||
let candidate = self.path.join(name);
|
||||
fs::create_dir_all(&candidate).expect("candidate should be created");
|
||||
fs::write(candidate.join("manifest.json"), manifest.to_string())
|
||||
.expect("manifest should be written");
|
||||
if with_program {
|
||||
fs::write(candidate.join("program.pbx"), [0_u8])
|
||||
.expect("program should be written");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestRoot {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
fn manifest(title: &str, app_id: u32, app_mode: &str) -> serde_json::Value {
|
||||
json!({
|
||||
"magic": "PMTU",
|
||||
"cartridge_version": 1,
|
||||
"app_id": app_id,
|
||||
"title": title,
|
||||
"app_version": "1.0.0",
|
||||
"app_mode": app_mode
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovery_returns_only_immediate_valid_game_directories() {
|
||||
let root = TestRoot::new();
|
||||
root.add_candidate("zeta", manifest("Zeta", 2, "Game"), true);
|
||||
root.add_candidate("alpha", manifest("Alpha", 1, "Game"), true);
|
||||
root.add_candidate("shell", manifest("Shell", 3, "Shell"), true);
|
||||
root.add_candidate("missing-program", manifest("Missing Program", 4, "Game"), false);
|
||||
fs::write(root.path.join("loose-file.txt"), "ignored").expect("file should be written");
|
||||
|
||||
let nested_parent = root.path.join("nested-parent");
|
||||
fs::create_dir_all(nested_parent.join("nested-game")).expect("nested dir should exist");
|
||||
fs::write(
|
||||
nested_parent.join("nested-game").join("manifest.json"),
|
||||
manifest("Nested", 5, "Game").to_string(),
|
||||
)
|
||||
.expect("nested manifest should be written");
|
||||
fs::write(nested_parent.join("nested-game").join("program.pbx"), [0_u8])
|
||||
.expect("nested program should be written");
|
||||
|
||||
let library = discover_games_root(&root.path);
|
||||
|
||||
assert_eq!(library.entries().len(), 2);
|
||||
assert_eq!(library.entries()[0].title, "Alpha");
|
||||
assert_eq!(library.entries()[0].app_id, 1);
|
||||
assert_eq!(library.entries()[0].app_version, "1.0.0");
|
||||
assert_eq!(library.entries()[0].manifest.app_mode, AppMode::Game);
|
||||
assert!(library.entries()[0].path.ends_with("alpha"));
|
||||
assert!(library.entries()[0].discovered_at >= UNIX_EPOCH);
|
||||
assert_eq!(library.entries()[1].title, "Zeta");
|
||||
|
||||
assert!(library.diagnostics().iter().any(|diagnostic| {
|
||||
diagnostic.path.ends_with("shell")
|
||||
&& diagnostic.reason == GameLibraryDiagnosticReason::NonGameAppMode
|
||||
}));
|
||||
assert!(library.diagnostics().iter().any(|diagnostic| {
|
||||
diagnostic.path.ends_with("missing-program")
|
||||
&& diagnostic.reason == GameLibraryDiagnosticReason::MissingProgram
|
||||
}));
|
||||
assert!(library.diagnostics().iter().any(|diagnostic| {
|
||||
diagnostic.path.ends_with("nested-parent")
|
||||
&& diagnostic.reason == GameLibraryDiagnosticReason::MissingManifest
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_root_returns_diagnostic_and_no_entries() {
|
||||
let root = TestRoot::new();
|
||||
let missing = root.path.join("missing");
|
||||
|
||||
let library = discover_games_root(&missing);
|
||||
|
||||
assert!(library.is_empty());
|
||||
assert_eq!(library.diagnostics().len(), 1);
|
||||
assert_eq!(library.diagnostics()[0].reason, GameLibraryDiagnosticReason::RootNotDirectory);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
pub mod async_work;
|
||||
pub mod fs;
|
||||
pub mod game_library;
|
||||
pub mod memcard;
|
||||
pub mod process;
|
||||
pub mod task;
|
||||
|
||||
@ -33,6 +33,10 @@ struct Cli {
|
||||
#[arg(long, value_name = "PATH")]
|
||||
fs_root: Option<String>,
|
||||
|
||||
/// Root directory for local game cartridge discovery
|
||||
#[arg(long, value_name = "PATH")]
|
||||
games_root: Option<String>,
|
||||
|
||||
/// Path to the capability configuration file
|
||||
#[arg(long, value_name = "PATH")]
|
||||
cap: Option<String>,
|
||||
@ -55,6 +59,9 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
let mut runner = HostRunner::new(fs_root, cap_config);
|
||||
if let Some(games_root) = cli.games_root {
|
||||
runner.set_games_root(games_root);
|
||||
}
|
||||
runner.set_boot_target(boot_target);
|
||||
|
||||
event_loop.run_app(&mut runner)?;
|
||||
|
||||
@ -12,7 +12,7 @@ use prometeu_drivers::{AudioCommand, LocalFramebufferRenderBackend, MemoryBanks}
|
||||
use prometeu_firmware::{BootTarget, Firmware, FirmwareState};
|
||||
use prometeu_hal::RuntimePlatform;
|
||||
use prometeu_hal::telemetry::CertificationConfig;
|
||||
use prometeu_system::{LatestRenderFrameStore, RenderWorkerConfig};
|
||||
use prometeu_system::{LatestRenderFrameStore, RenderWorkerConfig, discover_games_root};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::application::ApplicationHandler;
|
||||
@ -151,6 +151,18 @@ impl HostRunner {
|
||||
self.debugger.setup_boot_target(&boot_target, &mut self.firmware);
|
||||
}
|
||||
|
||||
pub(crate) fn set_games_root(&mut self, games_root: String) {
|
||||
let library = discover_games_root(&games_root);
|
||||
for diagnostic in library.diagnostics() {
|
||||
eprintln!(
|
||||
"games-root: omitted candidate {}: {:?}",
|
||||
diagnostic.path.display(),
|
||||
diagnostic.reason
|
||||
);
|
||||
}
|
||||
self.firmware.hub.set_game_library(library);
|
||||
}
|
||||
|
||||
/// Creates a new desktop runner instance.
|
||||
pub(crate) fn new(fs_root: Option<String>, cap_config: Option<CertificationConfig>) -> Self {
|
||||
let target_fps = 60;
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"discussion/lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
|
||||
{"type":"discussion","id":"DSC-0002","status":"done","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-21","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0037","file":"discussion/lessons/DSC-0002-runtime-edge-test-plan/LSN-0037-domain-gates-must-be-owned-by-the-repository.md","status":"done","created_at":"2026-04-21","updated_at":"2026-04-21"}]}
|
||||
{"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0004","status":"in_progress","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-07-03","tags":[],"agendas":[{"id":"AGD-0003","file":"AGD-0003-system-run-cart.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-07-03"},{"id":"AGD-0045","file":"AGD-0045-systemos-library-root-home-game-launch.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03"}],"decisions":[{"id":"DEC-0035","file":"DEC-0035-remove-userland-system-run-cart-and-preserve-direct-boot.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03","ref_agenda":"AGD-0003"},{"id":"DEC-0036","file":"DEC-0036-systemos-games-root-and-home-game-launch.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03","ref_agenda":"AGD-0045"}],"plans":[{"id":"PLN-0129","file":"PLN-0129-remove-userland-run-cart-abi-surface.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0035"]},{"id":"PLN-0130","file":"PLN-0130-remove-runtime-run-cart-stub-dispatch.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0035"]},{"id":"PLN-0131","file":"PLN-0131-preserve-direct-cartridge-boot-coverage.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0035"]},{"id":"PLN-0132","file":"PLN-0132-specify-games-root-home-launch-contract.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]},{"id":"PLN-0133","file":"PLN-0133-implement-games-root-library-discovery.md","status":"open","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]},{"id":"PLN-0134","file":"PLN-0134-wire-home-game-list-launch-flow.md","status":"open","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]},{"id":"PLN-0135","file":"PLN-0135-validate-games-root-launch-end-to-end.md","status":"open","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0004","status":"in_progress","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-07-03","tags":[],"agendas":[{"id":"AGD-0003","file":"AGD-0003-system-run-cart.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-07-03"},{"id":"AGD-0045","file":"AGD-0045-systemos-library-root-home-game-launch.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03"}],"decisions":[{"id":"DEC-0035","file":"DEC-0035-remove-userland-system-run-cart-and-preserve-direct-boot.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03","ref_agenda":"AGD-0003"},{"id":"DEC-0036","file":"DEC-0036-systemos-games-root-and-home-game-launch.md","status":"accepted","created_at":"2026-07-03","updated_at":"2026-07-03","ref_agenda":"AGD-0045"}],"plans":[{"id":"PLN-0129","file":"PLN-0129-remove-userland-run-cart-abi-surface.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0035"]},{"id":"PLN-0130","file":"PLN-0130-remove-runtime-run-cart-stub-dispatch.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0035"]},{"id":"PLN-0131","file":"PLN-0131-preserve-direct-cartridge-boot-coverage.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0035"]},{"id":"PLN-0132","file":"PLN-0132-specify-games-root-home-launch-contract.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]},{"id":"PLN-0133","file":"PLN-0133-implement-games-root-library-discovery.md","status":"done","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]},{"id":"PLN-0134","file":"PLN-0134-wire-home-game-list-launch-flow.md","status":"open","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]},{"id":"PLN-0135","file":"PLN-0135-validate-games-root-launch-end-to-end.md","status":"open","created_at":"2026-07-03","updated_at":"2026-07-03","ref_decisions":["DEC-0036"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0133
|
||||
ticket: system-run-cart
|
||||
title: Implement Games Root Library Discovery
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-07-03
|
||||
ref_decisions: [DEC-0036]
|
||||
tags: []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user