aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 08:36:29 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 08:36:29 +0300
commit697cbbfd36bb8d82dd2b419bc181889cc3c46f6a (patch)
tree0af8dc6b98b634bc804e700746d0c1582e9eea45
parentf75601a86a9202f8f226ac04a1238b3660071915 (diff)
downloadfparkan-697cbbfd36bb8d82dd2b419bc181889cc3c46f6a.tar.xz
fparkan-697cbbfd36bb8d82dd2b419bc181889cc3c46f6a.zip
feat(runtime): bound static preview asset loading
-rw-r--r--README.md2
-rw-r--r--apps/fparkan-game/src/main.rs18
-rw-r--r--crates/fparkan-runtime/src/lib.rs77
-rw-r--r--docs/rendering/renderer_truth_table.md10
-rw-r--r--docs/tomes/05-render.md8
5 files changed, 94 insertions, 21 deletions
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<String, String> {
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<usize>,
+ 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<LoadedMission, EngineError> {
+ 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<R: ResourceRepository>(
+ asset_manager: &AssetManager<R>,
+ root_spans: &[std::ops::Range<usize>],
+ prototypes: &[fparkan_prototype::EffectivePrototype],
+) -> Result<MissionAssets, AssetPreparationError> {
+ 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