pub mod preset;
use crate::authoring::world::{WorldJsonlAsset, load_world};
pub(crate) mod app_config;
pub(crate) mod camera_shot;
pub(crate) mod character_model;
pub(crate) mod companion;
pub(crate) mod companion_specs;
pub(crate) mod light_rig;
pub(crate) mod main_menu;
pub(crate) mod material_palette;
pub(crate) mod menu_defaults;
pub(crate) mod option_select;
pub(crate) mod panel;
pub(crate) mod prefab;
pub(crate) mod room;
pub(crate) mod scene_import;
pub(crate) mod slider;
pub(crate) mod story;
pub use story::validate_story_source;
pub(crate) mod ui_spec;
pub(crate) mod expand;
mod provenance;
pub use provenance::Provenance;
pub(crate) mod shadow;
pub use shadow::merge_args;
pub(crate) use expand::expand_world;
pub use expand::{GeneratedAsset, InjectedAsset, ShadowedAsset, expand_world_from_str};
pub struct LoadedWorld {
pub assets: Vec<WorldJsonlAsset>,
pub injected: Vec<InjectedAsset>,
pub generated: Vec<GeneratedAsset>,
pub shadowed: Vec<ShadowedAsset>,
pub authored: Vec<String>,
}
pub fn prepare_world(
content: &str,
assets_dir: Option<&std::path::Path>,
platform: concinnity_core::platform::Platform,
) -> Result<LoadedWorld, Vec<String>> {
let mut expanded = load_world(content)?;
let authored: Vec<String> = expanded
.iter()
.filter_map(|v| v.get("name").and_then(|n| n.as_str()))
.map(str::to_string)
.collect();
let report = expand_world(&mut expanded, assets_dir).map_err(|e| vec![e])?;
crate::cache::flush();
let assets: Vec<WorldJsonlAsset> = expanded.iter().map(WorldJsonlAsset::from_value).collect();
crate::check::check_world(&assets, platform)?;
Ok(LoadedWorld {
assets,
injected: report.injected,
generated: report.generated,
shadowed: report.shadowed,
authored,
})
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::platform::Platform;
#[test]
fn prepare_world_expands_and_validates() {
let content = r#"{"name":"gfx","type":"GraphicsConfig","args":{}}"#;
let loaded = prepare_world(content, None, Platform::Metal).unwrap();
assert!(loaded.assets.len() > 1);
assert!(
loaded
.assets
.iter()
.any(|a| a.asset_type == "GraphicsConfig")
);
assert_eq!(loaded.authored, vec!["gfx".to_string()]);
}
#[test]
fn prepare_world_reports_an_expansion_failure() {
let content = r#"{"name":"p","type":"Prop","args":{"prefab":"ghost"}}"#;
let errs = prepare_world(content, None, Platform::Metal)
.err()
.unwrap_or_default();
assert_eq!(errs.len(), 1);
assert!(errs[0].contains("ghost"), "{errs:?}");
}
#[test]
fn prepare_world_reports_semantic_errors() {
let content = r#"{"name":"prop","type":"Prop","args":{"mesh":"nope"}}"#;
let errs = prepare_world(content, None, Platform::Metal)
.err()
.unwrap_or_default();
assert!(!errs.is_empty());
assert!(errs.iter().any(|e| e.contains("nope")), "{errs:?}");
}
#[test]
fn prepare_world_expands_presets_from_the_root_it_is_given() {
fn rig_root(intensity: f64) -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let rigs = dir.path().join("light_rigs");
std::fs::create_dir_all(&rigs).unwrap();
std::fs::write(
rigs.join("dusk.json"),
serde_json::to_vec(&serde_json::json!({
"args": {"lights": [{"kind": "directional", "name": "key", "intensity": intensity}]}
}))
.unwrap(),
)
.unwrap();
dir
}
fn key_intensity(loaded: &LoadedWorld) -> Option<f64> {
loaded
.assets
.iter()
.find(|a| a.name == "rig_key")?
.args
.get("intensity")?
.as_f64()
}
let content = r#"{"name":"rig","type":"LightRig","args":{"preset":"dusk"}}"#;
let bright = rig_root(3.5);
let dim = rig_root(0.25);
assert_eq!(
key_intensity(&prepare_world(content, Some(bright.path()), Platform::Metal).unwrap()),
Some(3.5)
);
assert_eq!(
key_intensity(&prepare_world(content, Some(dim.path()), Platform::Metal).unwrap()),
Some(0.25)
);
assert_eq!(
key_intensity(&prepare_world(content, None, Platform::Metal).unwrap()),
None
);
}
}