From f706a9ce55c335e29e54a02eaa337518cb62c9fa Mon Sep 17 00:00:00 2001 From: m3ultra Date: Wed, 29 Jul 2026 14:16:29 +1000 Subject: [PATCH] Bundles carry pre-baked backgrounds (paintings survive filesystem-less boots) RoomDoc gains baked_visual (quantized indices, exporter-written, skipped when empty); bake() falls back to it when pics/ can't load; --export-bundle folds every room's painting in via World::bake_backgrounds_for_bundle. A six-painting game bundles to 1.5MB. Tests green; alley renders from bundle alone with no pics/ on disk. Co-Authored-By: Claude Fable 5 --- mrpci-core/src/bin/mrpci-headless/main.rs | 6 ++++- mrpci-core/src/room.rs | 30 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/mrpci-core/src/bin/mrpci-headless/main.rs b/mrpci-core/src/bin/mrpci-headless/main.rs index 0b7df36..7544139 100644 --- a/mrpci-core/src/bin/mrpci-headless/main.rs +++ b/mrpci-core/src/bin/mrpci-headless/main.rs @@ -76,7 +76,11 @@ fn main() { let mut gs = GameState::new(world); if let Some(path) = get("--export-bundle") { - let bundle = gs.world.to_bundle(); + let mut bundle = gs.world.to_bundle(); + // Paintings ride inside the bundle as pre-quantized indices, so the + // wasm build (no filesystem) keeps its backgrounds. + gs.world.bake_backgrounds_for_bundle(&mut bundle.rooms); + let bundle = bundle; // Through Value first, so the output is byte-stable. // // A bundle is full of HashMaps — rooms, scripts, sprites, every diff --git a/mrpci-core/src/room.rs b/mrpci-core/src/room.rs index a82ad81..670549f 100644 --- a/mrpci-core/src/room.rs +++ b/mrpci-core/src/room.rs @@ -154,6 +154,12 @@ pub struct RoomDoc { /// `-ctl.png` companions). Applied before `ops`. #[serde(default)] pub background: String, + /// The background pre-quantized to visual indices (PIC_W*PIC_H), written + /// by the bundle exporter so filesystem-less builds (wasm) keep their + /// paintings. Only consulted when the `pics/` file can't be loaded; + /// travels with `palette`, which the exporter sets to the quantized one. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub baked_visual: Vec, /// Procedural paint ops (replayed after the background, if any). #[serde(default)] pub ops: Vec, @@ -207,6 +213,7 @@ impl Default for RoomDoc { name: String::new(), enter_text: String::new(), background: String::new(), + baked_visual: Vec::new(), ops: Vec::new(), palette: None, cycles: Vec::new(), @@ -395,6 +402,10 @@ impl World { let dir = self.base.join("pics"); if let Some(rgba) = assets::load_rgba_scaled(&dir.join(format!("{}.png", self.doc.background)), PIC_W, PIC_H) { assets::bake_background(&rgba, &mut self.palette, &mut self.screens); + } else if self.doc.baked_visual.len() == PIC_W * PIC_H { + // No filesystem (or the art moved): the exporter pre-baked + // the painting into the doc. Palette came from doc.palette. + self.screens.visual.copy_from_slice(&self.doc.baked_visual); } if let Some(rgba) = assets::load_rgba_scaled(&dir.join(format!("{}-pri.png", self.doc.background)), PIC_W, PIC_H) { assets::bake_pri_mask(&rgba, &mut self.screens); @@ -518,6 +529,25 @@ impl World { WorldBundle { manifest: self.manifest.clone(), rooms, scripts, sprites } } + /// Fold each room's background painting into its doc (quantized indices + /// + palette), so the bundle plays where there is no filesystem. The + /// exporter calls this; docs already carrying a bake are left alone. + pub fn bake_backgrounds_for_bundle(&self, rooms: &mut BTreeMap) { + for doc in rooms.values_mut() { + if doc.background.is_empty() || doc.baked_visual.len() == PIC_W * PIC_H { + continue; + } + let dir = self.base.join("pics"); + if let Some(rgba) = assets::load_rgba_scaled(&dir.join(format!("{}.png", doc.background)), PIC_W, PIC_H) { + let mut pal = doc.palette.clone().unwrap_or_else(Palette::base); + let mut probe = Screens::new(); + assets::bake_background(&rgba, &mut pal, &mut probe); + doc.palette = Some(pal); + doc.baked_visual = probe.visual; + } + } + } + /// Replace the loaded world with a bundle. A bundle that carries /// sprites replaces the art wholesale (the wasm/bootstrap path); one /// that doesn't keeps whatever this World already loaded.