diff --git a/crates/skate-game/src/retail_depth.wgsl b/crates/skate-game/src/retail_depth.wgsl index 4730bf54..41aedc41 100644 --- a/crates/skate-game/src/retail_depth.wgsl +++ b/crates/skate-game/src/retail_depth.wgsl @@ -5,7 +5,7 @@ #ifdef MOTION_VECTOR_PREPASS #import bevy_pbr::pbr_prepass_functions::calculate_motion_vector #endif -struct WorldParams { mode: vec4, surface: vec4, family: vec4, fog_ramp: vec4, fog_color: vec4, shadow_color: vec4, sun_direction: vec4 } +struct WorldParams { mode: vec4, foliage_debug: vec4, surface: vec4, family: vec4, fog_ramp: vec4, fog_color: vec4, shadow_color: vec4, sun_direction: vec4 } @group(#{MATERIAL_BIND_GROUP}) @binding(0) var p: WorldParams; @group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d; @group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler; diff --git a/crates/skate-game/src/retail_render.rs b/crates/skate-game/src/retail_render.rs index cc2cb68d..a78a8263 100644 --- a/crates/skate-game/src/retail_render.rs +++ b/crates/skate-game/src/retail_render.rs @@ -18,6 +18,9 @@ use std::collections::BTreeMap; pub(crate) struct RetailRenderPlugin; impl Plugin for RetailRenderPlugin { fn build(&self, app: &mut App) { + if std::env::var_os("SKATE_DEBUG_FOLIAGE").is_some_and(|v| v == "1") { + eprintln!("SKATE_FOLIAGE_DEBUG: solid cyan tree-wall cards, magenta other foliage; alpha rejection disabled for foliage only"); + } embedded_asset!(app, "retail_world.wgsl"); embedded_asset!(app, "retail_tone.wgsl"); embedded_asset!(app, "retail_depth.wgsl"); @@ -58,6 +61,8 @@ impl FullscreenMaterial for RetailTone { pub(crate) struct WorldParams { // family, texture flags, alpha cutoff (-1 for opaque), exposure pub mode: Vec4, + // Diagnostic solid foliage colour; w=0 keeps retail shading. + pub foliage_debug: Vec4, // macro UV scale, opacity, detail UV scale, material multiplier pub surface: Vec4, // tree LM scale/floor/tint, proxy multiplier: reference day capture @@ -254,9 +259,15 @@ impl Definition { | (u32::from(detail.is_some()) << 7); // scene.hlsl's retail world ALPHAREF is 30, not the portable 0.5. let cutoff = 30. / 255.; - let alpha = match m.alpha_mode { - 1 => AlphaMode::Mask(cutoff), - 2 => AlphaMode::Blend, + let debug_foliage = matches!(self.family, 9 | 10) + && std::env::var_os("SKATE_DEBUG_FOLIAGE").is_some_and(|v| v == "1"); + let tree_wall = m.retail_definition.as_deref().is_some_and(|bytes| { + bytes.windows(b"TreeWall".len()).any(|s| s == b"TreeWall") + }); + let alpha = match (debug_foliage, m.alpha_mode) { + (true, _) => AlphaMode::Opaque, + (_, 1) => AlphaMode::Mask(cutoff), + (_, 2) => AlphaMode::Blend, _ => AlphaMode::Opaque, }; RetailWorldMaterial { @@ -264,9 +275,16 @@ impl Definition { mode: Vec4::new( self.family as f32, flags as f32, - if m.alpha_mode == 1 { cutoff } else { -1. }, + if m.alpha_mode == 1 && !debug_foliage { cutoff } else { -1. }, 2.5, ), + foliage_debug: if !debug_foliage { + Vec4::ZERO + } else if tree_wall { + Vec4::new(0., 1., 1., 1.) + } else { + Vec4::new(1., 0., 1., 1.) + }, surface: Vec4::new(macro_scale, macro_opacity, detail_scale, 1.), family: Vec4::new(0.3435, 0.02, 1., 0.45), fog_ramp: Vec4::new(0., 0., 1., 0.), diff --git a/crates/skate-game/src/retail_world.wgsl b/crates/skate-game/src/retail_world.wgsl index 4f0d740f..b9e6b2b7 100644 --- a/crates/skate-game/src/retail_world.wgsl +++ b/crates/skate-game/src/retail_world.wgsl @@ -1,7 +1,7 @@ #import bevy_pbr::{forward_io::VertexOutput, mesh_view_bindings as frame} struct WorldParams { - mode: vec4, surface: vec4, family: vec4, + mode: vec4, foliage_debug: vec4, surface: vec4, family: vec4, fog_ramp: vec4, fog_color: vec4, shadow_color: vec4, sun_direction: vec4, } @group(#{MATERIAL_BIND_GROUP}) @binding(0) var p: WorldParams; @@ -130,6 +130,7 @@ fn fragment(i: VertexOutput) -> @location(0) vec4 { var xe = max((lin*fog_a+p.fog_color.rgb*f)*p.mode.w,vec3(0.0)); // Reduced curve is the full curve with the linear input capped at one. if fam == 8u { xe = min(xe,vec3(1.0)); } + if p.foliage_debug.w != 0.0 { return vec4(p.foliage_debug.rgb, 1.0); } if a.a < p.mode.z { discard; } return vec4(xe,alpha); } diff --git a/docs/retail-renderer.md b/docs/retail-renderer.md index cb3c78c6..ea47ad62 100644 --- a/docs/retail-renderer.md +++ b/docs/retail-renderer.md @@ -115,3 +115,31 @@ there has been no matched-camera pixel comparison against the recomp. The shaders are embedded in the executable. Sky and map payloads are extracted from the user's game and must never be committed or bundled with releases. + +### Treeline investigation and diagnostic (2026-09-08) + +The user's mega-park screenshots refer to the bowl/cliffs in University, not +DIST_MegaPark (the separate create-a-park venue). A read-only decode of the installed +University SKATE14 package found 8,546 materials, 1,645,617 render triangles, +1,180 tree-family mesh materials and all 86 TreeWall meshes from the source cache. +TreeWall diffuse texture 0x2c70170a00053a88 retains its RGBA cutout, and the tree +materials retain masked alpha and two-sided flags. A TOC audit of all 479 full +models found no extra vertex or face buffers omitted by the parser. + +Proxy comparisons must resolve mesh material GUIDs with +`_bind_material_groups_by_guid`; `mdl_parser.Mesh.name` is in material-table order +and can label proxy terrain as trees. With GUID bindings, the 14 proxy foliage +meshes intersecting x=200..550, z=-850..-600 contain 3,878 distinct triangles; +3,813 match full geometry after unordered-vertex quantization to 1 mm. The small +remainder does not establish absent trees (different triangulation/rounding also +changes these keys). DMO_University and DMO_Global contain movable props, not a +second background forest. These checks do not prove visual parity or a fix. + +For a user-run discriminating capture, `SKATE_DEBUG_FOLIAGE=1` renders tree-wall +cards cyan and other tree-family surfaces magenta, opaque and unlit. Geometry, +camera, depth testing and occlusion remain authored. This bypasses texture alpha +in both colour and depth passes without adding proxy geometry. Return to the +reported viewpoint: solid foliage appearing in the missing area implicates the +normal shading/alpha path; an empty area requires checking placement, camera or +source selection. Unset the variable to restore normal rendering. This diagnostic +has been prepared without launching the game.