diff --git a/README.md b/README.md index 011eb88..bdc0d78 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Open source проект с реализацией компонентов игр - [apps/fparkan-cli](apps/fparkan-cli) — CLI для архивов, графов и acceptance-отчетов. - [apps/fparkan-viewer](apps/fparkan-viewer) — inspection-only CLI для archive/model/texture/map без live Vulkan draw path. - [apps/fparkan-headless](apps/fparkan-headless) — headless runtime composition root. -- [apps/fparkan-game](apps/fparkan-game) — mission composition root: по умолчанию выдаёт planning report; opt-in `--backend static-vulkan` открывает native Vulkan окно для первой подготовленной MSH-модели миссии. +- [apps/fparkan-game](apps/fparkan-game) — mission composition root: по умолчанию выдаёт planning report; opt-in `--backend static-vulkan` использует bounded preview loader и открывает native Vulkan окно для первой mesh-backed MSH-модели. ## Текущий статус рендера diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index d8958f2..0da0ba8 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -34,8 +34,8 @@ use fparkan_render_vulkan::{ VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanStaticMesh, }; use fparkan_runtime::{ - create, frame, load_mission, loaded_mission_assets, EngineConfig, EngineMode, EngineServices, - MissionAssets, MissionObjectDraft, MissionRequest, + create, frame, load_mission, load_mission_static_preview, loaded_mission_assets, EngineConfig, + EngineMode, EngineServices, MissionAssets, MissionObjectDraft, MissionRequest, }; use fparkan_vfs::DirectoryVfs; #[cfg(test)] @@ -74,12 +74,14 @@ fn run(args: &[String]) -> Result { services, ) .map_err(|err| err.to_string())?; - let loaded = load_mission( - &mut engine, - MissionRequest { - key: args.mission.clone(), - }, - ) + let request = MissionRequest { + key: args.mission.clone(), + }; + let loaded = if args.backend == RenderBackendMode::StaticVulkan { + load_mission_static_preview(&mut engine, request) + } else { + load_mission(&mut engine, request) + } .map_err(|err| err.to_string())?; if args.backend == RenderBackendMode::StaticVulkan { diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index 6a4e478..a7c9932 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -31,7 +31,7 @@ use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy}; use fparkan_prototype::{ build_prototype_graph_report, PrototypeGraph, PrototypeGraphFailure, PrototypeGraphReport, }; -use fparkan_resource::{resource_name, CachedResourceRepository}; +use fparkan_resource::{resource_name, CachedResourceRepository, ResourceRepository}; use fparkan_vfs::{Vfs, VfsError}; use fparkan_world::{ construct_object, new as new_world, register_object, step, InputSnapshot, ObjectDraft, @@ -182,6 +182,17 @@ pub struct MissionObjectDraft { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] struct MissionLoadOptions { fail_after_registered_objects: Option, + asset_scope: MissionAssetScope, +} + +/// Selects how much of the resolved mission asset graph to prepare. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +enum MissionAssetScope { + /// Prepare every reachable asset; required by normal runtime loading. + #[default] + Full, + /// Prepare one root at a time until a mesh-backed preview model is found. + FirstMeshPreview, } /// Loaded mission. @@ -462,6 +473,33 @@ pub fn load_mission( load_mission_with_trace(engine, request).map(|(loaded, _trace)| loaded) } +/// Loads a mission using the bounded static-preview asset scope. +/// +/// This mode preserves map, TMA, graph, construction and registration work, +/// but prepares only the first root that supplies a mesh-backed model. It is +/// intended solely for the opt-in static Vulkan preview and must not be used +/// for normal gameplay, where all reachable assets remain required. +/// +/// # Errors +/// +/// Returns [`EngineError`] under the same decoding, graph, terrain and world +/// conditions as [`load_mission`], plus asset errors for the roots examined by +/// the preview scope. +pub fn load_mission_static_preview( + engine: &mut Engine, + request: MissionRequest, +) -> Result { + load_mission_with_options( + engine, + request, + MissionLoadOptions { + asset_scope: MissionAssetScope::FirstMeshPreview, + ..MissionLoadOptions::default() + }, + ) + .map(|(loaded, _trace)| loaded) +} + /// Loads mission transactionally and returns a diagnostic trace. /// /// # Errors @@ -564,15 +602,22 @@ fn load_mission_with_options( failures: prototype_report.failures.clone(), }); } - let mission_assets = AssetManager::new(repository) - .prepare_mission_assets( + let asset_manager = AssetManager::new(repository); + let mission_assets = match options.asset_scope { + MissionAssetScope::Full => asset_manager.prepare_mission_assets( &prototype_graph.root_prototype_request_spans, &resolved_prototypes, - ) - .map_err(|source| EngineError::AssetPreparation { - mission: request.key.clone(), - source, - })?; + ), + MissionAssetScope::FirstMeshPreview => prepare_first_mesh_preview_assets( + &asset_manager, + &prototype_graph.root_prototype_request_spans, + &resolved_prototypes, + ), + } + .map_err(|source| EngineError::AssetPreparation { + mission: request.key.clone(), + source, + })?; let mission_asset_plan = mission_assets.to_plan(); let object_drafts: Vec<_> = mission .objects @@ -672,6 +717,21 @@ fn load_mission_with_options( Ok((summary, trace)) } +fn prepare_first_mesh_preview_assets( + asset_manager: &AssetManager, + root_spans: &[std::ops::Range], + prototypes: &[fparkan_prototype::EffectivePrototype], +) -> Result { + for span in root_spans { + let assets = + asset_manager.prepare_mission_assets(std::slice::from_ref(span), prototypes)?; + if !assets.models.is_empty() { + return Ok(assets); + } + } + Ok(MissionAssets::default()) +} + /// Steps headless mode. /// /// # Errors @@ -1063,6 +1123,7 @@ mod tests { }, MissionLoadOptions { fail_after_registered_objects: Some(1), + ..MissionLoadOptions::default() }, ) .expect_err("forced registration failure"); diff --git a/docs/rendering/renderer_truth_table.md b/docs/rendering/renderer_truth_table.md index 26b113a..14cf198 100644 --- a/docs/rendering/renderer_truth_table.md +++ b/docs/rendering/renderer_truth_table.md @@ -35,10 +35,12 @@ - `apps/fparkan-game` по умолчанию выдает `render-planning` JSON report поверх synthetic window descriptor и `VulkanPlanningBackend`. Opt-in `--backend static-vulkan` уже создаёт native `winit` window и передаёт первую подготовленную MSH модели миссии в - `VulkanSmokeRenderer` с пустым списком materials (явный white-fallback). Это не является - rendered acceptance: fresh GOG `MISSIONS/Autodemo.00/data.tma` test не дошёл до окна за - локальный 120-second runner limit, поэтому нет live report, pixel artifact или validation - evidence именно для game path. + `VulkanSmokeRenderer` с пустым списком materials (явный white-fallback). Режим использует + отдельный bounded preview loader: normal `load_mission` по-прежнему готовит все reachable + assets, тогда как preview останавливает asset preparation после первого root с mesh-backed + model. Это не является rendered acceptance: fresh GOG `MISSIONS/Autodemo.00/data.tma` test + с этим scope всё равно не дошёл до окна за локальный 120-second runner limit, поэтому нет + live report, pixel artifact или validation evidence именно для game path. - `apps/fparkan-viewer` сейчас inspection-only CLI и не открывает live Vulkan asset viewer. - Следующий реальный milestone для rendered acceptance: `VulkanAssetRenderer` diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index be60d30..34bf873 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -951,6 +951,14 @@ WEAR/MAT0/TEXM material data, or establish a game camera. A GOG limit before the window/report, so this mode has implementation and unit/lint evidence only, not a corpus GPU acceptance claim. +The preview now asks `load_mission_static_preview` for assets. Normal mission +loading remains full and transactional; the preview scope walks root spans in +TMA order and stops asset preparation after the first mesh-backed model. It is +therefore not a hidden relaxation of gameplay validation. A second GOG +`Autodemo.00` attempt with this narrower asset scope still exceeded 120 seconds +before opening a window, which locates the remaining startup cost before the +Vulkan draw loop but does not identify it as terrain versus graph work. + `Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28` сохраняет source triangle order, а его positions и packed UV0 попадают в тот же static vertex/index upload path. Для текущего диагностического viewer XZ bounds