From ef9d8b67d5ff4c07531d8e49ec0b5a8941bafbf3 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 3 Nov 2026 12:07:54 -0500 Subject: [PATCH] Transport terrain resource-pack material maps to shader samplers Proposed independently developed replacement for closed #7800, addressing #7747. Preserve colour-layer identity across split arrays, nearest encoded data transport, neutral defaults, resource ownership and colour-selected companion frames. Add original CPU and actual-GPU controls with a pinned input/exposure ledger. Frozen candidate production hunks preserved; integration adjusts a test-module append and clarifies comments. Co-Authored-By: OpenAI Codex --- crates/horizon-client-render/src/gfx.rs | 146 +++-- .../src/gfx/pack_atlas.rs | 33 +- .../src/gfx/pack_material_tests.rs | 229 ++++++++ .../src/gfx/pack_post.rs | 51 +- crates/horizon-client-render/src/packs.rs | 76 ++- .../horizon-client-render/src/packs/cache.rs | 2 +- crates/horizon-client-render/src/sprites.rs | 188 +++++-- crates/horizon-client/src/game.rs | 2 +- .../horizon-client/src/game/shader_packs.rs | 11 +- .../terrain-material-inputs-20261103.json | 505 ++++++++++++++++++ 10 files changed, 1136 insertions(+), 107 deletions(-) create mode 100644 crates/horizon-client-render/src/gfx/pack_material_tests.rs create mode 100644 docs/research/terrain-material-inputs-20261103.json diff --git a/crates/horizon-client-render/src/gfx.rs b/crates/horizon-client-render/src/gfx.rs index 539756e43e..b9d0d9b8fc 100644 --- a/crates/horizon-client-render/src/gfx.rs +++ b/crates/horizon-client-render/src/gfx.rs @@ -967,8 +967,8 @@ pub struct Renderer { entity_skin: Option, group: wgpu::BindGroup, globals: wgpu::Buffer, - /// The resource packs' LabPBR maps (normals, specular), for native shader packs. - pbr_maps: Option<[wgpu::Texture; 2]>, + /// Resource-pack normal/specular maps, split like the colour sprite arrays. + pbr_maps: Option>, lightmap: wgpu::Texture, /// The block sprites, `sprite_split` layers per array (the first is /// `sprites` in terrain.wgsl, the others `sprites_1`...). @@ -997,6 +997,7 @@ pub struct Renderer { lightmap_view: wgpu::TextureView, lightmap_sampler: wgpu::Sampler, animations: animate::SpriteAnimator, + material_animations: Vec, active_animated_layers: Vec, uploads: upload::Uploads, /// Section meshes are staged through their own belt into @@ -1137,6 +1138,7 @@ pub struct Renderer { pack_weather: Option, /// The block sprites as one atlas, while a shader pack is loaded. pack_atlas: Option, + pack_material_atlases: Option<[pack_atlas::PackAtlas; 2]>, /// The pack's terrain program reads flat draws' vertices (`pack_terrain_inputs`). pack_terrain_flat: bool, /// OptiFine/Iris separate ambient occlusion: pass vertex alpha to hz_Color.a. @@ -2623,39 +2625,48 @@ impl Renderer { }; let mut mip_levels = 1; let gl = adapter.get_info().backend == wgpu::Backend::Gl; - // The resource packs' LabPBR maps, for native shader packs: one array each, cut to - // the layers the GPU has (`block_normal_map` gives the default past them). + // Keep every colour layer's companion, split by the same device limit. Native + // material stages read the first array; terrain packs stitch all of them. let pbr_maps = prepared .pbr - .map(|(normals, specular, levels, layers, size)| { - let kept = layers.min(device.limits().max_texture_array_layers); - let count = array_layers(kept, gl); - [("LabPBR normals", normals), ("LabPBR specular", specular)].map( - |(label, mut data)| { - // The extra layers are empty. - let layer_bytes = data.len() / layers as usize; - data.resize(layer_bytes * count as usize, 0); - device.create_texture_with_data( - &queue, - &wgpu::TextureDescriptor { - label: Some(label), - size: wgpu::Extent3d { - width: size, - height: size, - depth_or_array_layers: count, - }, - mip_level_count: levels, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], + .map(|(normals, specular, levels, map_layers, size)| { + let layer_bytes = normals.len() / map_layers as usize; + (0..map_layers.div_ceil(split)) + .map(|array| { + let first = array * split; + let used = (map_layers - first).min(split); + let count = array_layers(used, gl); + [("LabPBR normals", &normals), ("LabPBR specular", &specular)].map( + |(label, data)| { + let start = first as usize * layer_bytes; + let mut data = + data[start..start + used as usize * layer_bytes].to_vec(); + data.resize(layer_bytes * count as usize, 0); + device.create_texture_with_data( + &queue, + &wgpu::TextureDescriptor { + label: Some(label), + size: wgpu::Extent3d { + width: size, + height: size, + depth_or_array_layers: count, + }, + mip_level_count: levels, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &data, + ) }, - wgpu::util::TextureDataOrder::LayerMajor, - &data, ) - }, - ) + }) + .collect() }); let sprite_textures: Vec = prepared .sprites @@ -2693,6 +2704,11 @@ impl Renderer { ) }) .collect(); + let material_animations = animations + .iter() + .filter(|a| a.has_materials()) + .cloned() + .collect(); let animations = animate::SpriteAnimator::new( &device, animations, @@ -4131,6 +4147,7 @@ impl Renderer { lightmap_view, lightmap_sampler, animations, + material_animations, active_animated_layers: vec![0; active_layer_words], uploads, section_uploads, @@ -4202,6 +4219,7 @@ impl Renderer { cloud_bindings, pack_weather: None, pack_atlas: None, + pack_material_atlases: None, pack_terrain_flat: false, separate_ao: false, hud, @@ -5663,6 +5681,7 @@ impl Renderer { ) -> Option<(Vec, Vec)> { if pack.is_none() { self.pack_atlas = None; + self.pack_material_atlases = None; } let instance = wgpu::vertex_attr_array![5=>Float32x4]; let vertex = wgpu::VertexBufferLayout { @@ -5680,6 +5699,10 @@ impl Renderer { let post = pack.map(|post| { let world = pack_post::World { atlas: self.pack_atlas.as_ref().map(|a| (a.view.clone(), a.size())), + material_maps: self + .pack_material_atlases + .as_ref() + .map(|a| [a[0].view.clone(), a[1].view.clone()]), lightmap: self.lightmap_view.clone(), gui_size: self .hud @@ -5691,6 +5714,7 @@ impl Renderer { table: self.flat.as_ref().filter(|_| flat).map(|f| &f.draw_layout), depth_write: true, atlas: None, + material_maps: true, ids: None, cull: true, }), @@ -5765,7 +5789,7 @@ impl Renderer { sprite_materials: &sprite_materials, entities: &self.entity_bindings, weather_groups: [&self.weather.entity_layout, &self.weather.time_layout], - pbr: self.pbr_maps.as_ref(), + pbr: self.pbr_maps.as_ref().and_then(|arrays| arrays.first()), }; let (pack, labels) = native_pack::NativePack::new( &self.device, @@ -5779,6 +5803,7 @@ impl Renderer { if self.native.is_some() { self.pack_post = None; self.pack_atlas = None; + self.pack_material_atlases = None; } Ok(labels) } @@ -5833,6 +5858,20 @@ impl Renderer { if let Some(atlas) = &mut atlas { atlas.separate_ao = self.separate_ao; } + self.pack_material_atlases = atlas.as_ref().and_then(|_| { + let maps = self.pbr_maps.as_ref()?; + let make = |kind: usize, encoder: &mut wgpu::CommandEncoder| { + let arrays: Vec<_> = maps.iter().map(|pair| pair[kind].clone()).collect(); + pack_atlas::PackAtlas::material( + &self.device, + encoder, + &arrays, + self.sprite_split, + layers, + ) + }; + Some([make(0, &mut encoder)?, make(1, &mut encoder)?]) + }); self.pack_atlas = atlas; self.queue.submit([encoder.finish()]); } @@ -6064,6 +6103,49 @@ impl Renderer { active, self.own_timing.as_mut(), ); + if let Some(arrays) = &self.pbr_maps { + for animation in &self.material_animations { + if !self.animations.changed().contains(&animation.layer) { + continue; + } + let Some(pair) = arrays.get((animation.layer / self.sprite_split) as usize) else { + continue; + }; + for kind in 0..2 { + let Some(pixels) = animation.material_pixels(kind, tick) else { + continue; + }; + let texture = &pair[kind]; + let from = (pixels.len() / 4).isqrt() as u32; + for level in 0..texture.mip_level_count() { + let size = texture.width() >> level; + let pixels = crate::sprites::resample(pixels, from, size); + self.uploads.texture( + encoder, + wgpu::TexelCopyTextureInfo { + texture, + mip_level: level, + origin: wgpu::Origin3d { + x: 0, + y: 0, + z: animation.layer % self.sprite_split, + }, + aspect: wgpu::TextureAspect::All, + }, + size, + size, + &pixels, + ); + } + } + } + if let Some(atlases) = &self.pack_material_atlases { + for kind in 0..2 { + let arrays: Vec<_> = arrays.iter().map(|pair| pair[kind].clone()).collect(); + atlases[kind].copy(encoder, &arrays, self.animations.changed().iter().copied()); + } + } + } if let Some(atlas) = &self.pack_atlas { atlas.copy( encoder, diff --git a/crates/horizon-client-render/src/gfx/pack_atlas.rs b/crates/horizon-client-render/src/gfx/pack_atlas.rs index d012871539..3e3cc735cc 100644 --- a/crates/horizon-client-render/src/gfx/pack_atlas.rs +++ b/crates/horizon-client-render/src/gfx/pack_atlas.rs @@ -26,6 +26,37 @@ impl PackAtlas { split: u32, layers: u32, sprite: u32, + ) -> Option { + Self::with_levels( + device, + encoder, + arrays, + split, + layers, + sprite, + arrays[0].mip_level_count(), + ) + } + + /// Encoded material channels must select an existing texel, including at distance. + pub fn material( + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + arrays: &[wgpu::Texture], + split: u32, + layers: u32, + ) -> Option { + Self::with_levels(device, encoder, arrays, split, layers, arrays[0].width(), 1) + } + + fn with_levels( + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + arrays: &[wgpu::Texture], + split: u32, + layers: u32, + sprite: u32, + levels: u32, ) -> Option { let layers = layers.max(1); let columns = (f64::from(layers).sqrt().ceil() as u32).max(1); @@ -35,7 +66,7 @@ impl PackAtlas { return None; } // Levels past a sprite's single pixel would mix neighbours: stop there. - let levels = arrays[0].mip_level_count().min(sprite.ilog2() + 1); + let levels = levels.min(sprite.ilog2() + 1); let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Shader pack block atlas"), size: wgpu::Extent3d { diff --git a/crates/horizon-client-render/src/gfx/pack_material_tests.rs b/crates/horizon-client-render/src/gfx/pack_material_tests.rs new file mode 100644 index 0000000000..8cf4edd785 --- /dev/null +++ b/crates/horizon-client-render/src/gfx/pack_material_tests.rs @@ -0,0 +1,229 @@ +//! Original synthetic material transport oracle; no external shader algorithms or assets. +use super::*; +use wgpu::util::DeviceExt; + +#[test] +#[ignore = "requires a host GPU; missing adapter is a failure"] +fn terrain_material_samplers_preserve_surface_and_encoded_bytes() { + let instance = crate::gfx::test_gpu::instance(); + let adapter = pollster::block_on(instance.request_adapter(&Default::default())).unwrap(); + eprintln!("Material transport GPU: {:?}", adapter.get_info()); + assert_ne!(adapter.get_info().device_type, wgpu::DeviceType::Cpu); + let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { + required_limits: adapter.limits(), + ..Default::default() + })) + .unwrap(); + let root = std::env::temp_dir().join(format!("horizon-material-pack-{}", std::process::id())); + std::fs::create_dir_all(root.join("shaders")).unwrap(); + std::fs::write( + root.join("shaders/gbuffers_terrain.vsh"), + "#version 120\nvoid main(){gl_Position=gl_Vertex;}", + ) + .unwrap(); + std::fs::write( + root.join("shaders/gbuffers_terrain.fsh"), + r#"#version 120 +uniform sampler2D gtexture; +uniform sampler2D normals; +uniform sampler2D specular; +void main() { + int x = int(floor(gl_FragCoord.x)); + int layer = x - (x / 5) * 5; + // Interior of source texel (1,1), away from a nearest-sampling boundary. + vec2 uv = (vec2(float(layer - (layer / 3) * 3), float(layer / 3)) + 0.375) / vec2(3.0, 2.0); + if (x < 5) gl_FragData[0] = texture2DLod(specular, uv, 12.0); + else if (x < 10) gl_FragData[0] = texture2DLod(normals, uv, 12.0); + else gl_FragData[0] = texture2DLod(gtexture, uv, 0.0); +} +"#, + ) + .unwrap(); + let (split, layers) = (2u32, 5u32); + let mut encoder = device.create_command_encoder(&Default::default()); + let atlases: Vec<_> = (0..3) + .map(|kind| { + let side = if kind == 0 { 4 } else { 2 }; + let arrays: Vec<_> = (0..layers.div_ceil(split)) + .map(|array| { + let count = (layers - array * split).min(split); + let pixels: Vec = (0..count) + .flat_map(|l| { + let layer = (array * split + l) as u8; + (0..side * side).flat_map(move |pixel| match kind { + 0 if layer == 3 => crate::sprites::NO_SPECULAR, + 0 => [ + 17 + layer, + if pixel % 2 == 0 { 229 } else { 230 }, + 65, + if pixel % 2 == 0 { 254 } else { 255 }, + ], + 1 if layer == 2 => crate::sprites::FLAT_NORMAL, + 1 => [128 + layer, 126, 255, 19 + layer], + _ => [40 + layer, 50, 60, 255], + }) + }) + .collect(); + device.create_texture_with_data( + &queue, + &wgpu::TextureDescriptor { + label: Some("Original fixture companion array"), + size: wgpu::Extent3d { + width: side, + height: side, + depth_or_array_layers: count, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &pixels, + ) + }) + .collect(); + super::super::pack_atlas::PackAtlas::material( + &device, + &mut encoder, + &arrays, + split, + layers, + ) + .unwrap() + }) + .collect(); + queue.submit([encoder.finish()]); + let pack = horizon_shaders::Pack::open(&root).unwrap(); + let macros = horizon_shaders::Macros::standard(); + // Encoded zero alpha is data here and must remain observable in readback. + let properties = horizon_shaders::Properties::parse("alphaTest.gbuffers_terrain=off", ¯os); + let inputs = horizon_shaders::GameInputs { + terrain: Some(horizon_shaders::GbuffersInputs { + body: "vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2)); hz_Vertex=vec4(p*2.0-1.0,0.0,1.0);".into(), + ..Default::default() + }), + ..Default::default() + }; + let post = horizon_shaders::load_post(&pack, "", &properties, ¯os, &inputs); + assert!(post.terrain.is_some(), "{:?}", post.notes); + let world = World { + atlas: Some((atlases[2].view.clone(), atlases[2].size())), + material_maps: Some([atlases[1].view.clone(), atlases[0].view.clone()]), + lightmap: atlases[2].view.clone(), + terrain: Some(Geometry { + buffers: &[], + table: None, + depth_write: true, + atlas: None, + material_maps: true, + ids: None, + cull: false, + }), + block_ids: &[], + celestial: None, + gui_size: [1, 1], + clouds: None, + weather: false, + hand: false, + }; + let mut post = PackPost::new( + &device, + &queue, + &adapter, + post, + wgpu::TextureFormat::Rgba8Unorm, + world, + ); + let mut pass = post + .terrain + .take() + .unwrap_or_else(|| panic!("terrain pipeline must compile: {:?}", post.notes)); + // Request the existing copy usage for the fixture's colour readback. + post.copied |= 1; + // First draw preserves production Geometry -> Pass scope, so a routing regression fails. + for case in 0..3 { + if case == 1 { + pass.material_maps = false; + } + if case == 2 { + pass.material_maps = true; + post.material_maps = None; + } + let maps_expected = case == 0; + pass.groups.clear(); + post.resize(&device, [15, 1]); + let read = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + let mut encoder = device.create_command_encoder(&Default::default()); + { + let _clear = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Initialize fixture depth"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &post.targets.as_ref().unwrap().depth, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + ..Default::default() + }); + } + let group = post.bind(&device, &queue, &mut encoder, &mut pass, [15, 1]); + { + let mut render = post.begin(&mut encoder, &pass); + render.set_pipeline(pass.render()); + render.set_bind_group(0, &group, &[]); + if pass.draw { + post.draws.bind(&mut render, "TERRAIN_SOLID"); + } + render.draw(0..3, 0..1); + } + let output = post.targets.as_ref().unwrap().color[0].as_ref().unwrap()[0].texture(); + encoder.copy_texture_to_buffer( + output.as_image_copy(), + wgpu::TexelCopyBufferInfo { + buffer: &read, + layout: wgpu::TexelCopyBufferLayout { + bytes_per_row: Some(256), + ..Default::default() + }, + }, + wgpu::Extent3d { + width: 15, + height: 1, + depth_or_array_layers: 1, + }, + ); + queue.submit([encoder.finish()]); + read.slice(..).map_async(wgpu::MapMode::Read, |_| {}); + device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); + let data = read.slice(..).get_mapped_range().unwrap(); + for i in 0..15usize { + let layer = (i % 5) as u8; + let expected = if i >= 10 { + [40 + layer, 50, 60, 255] + } else if i >= 5 && (!maps_expected || layer == 2) { + crate::sprites::FLAT_NORMAL + } else if i >= 5 { + [128 + layer, 126, 255, 19 + layer] + } else if !maps_expected || layer == 3 { + crate::sprites::NO_SPECULAR + } else { + [17 + layer, 230, 65, 255] + }; + assert_eq!(&data[i * 4..i * 4 + 4], &expected, "case={case}, pixel={i}"); + } + eprintln!("Material oracle case={case}: 15 exact RGBA pixels pass"); + } + std::fs::remove_dir_all(root).unwrap(); +} diff --git a/crates/horizon-client-render/src/gfx/pack_post.rs b/crates/horizon-client-render/src/gfx/pack_post.rs index b15091e3cc..9a01031be4 100644 --- a/crates/horizon-client-render/src/gfx/pack_post.rs +++ b/crates/horizon-client-render/src/gfx/pack_post.rs @@ -93,13 +93,7 @@ enum SamplerKind { impl SamplerKind { fn of(source: Source, filtering: bool) -> Self { match source { - Source::Color(_) - | Source::ShadowColor(_) - | Source::Dummy - | Source::Normals - | Source::Specular - if !filtering => - { + Source::Color(_) | Source::ShadowColor(_) | Source::Dummy if !filtering => { Self::Nearest } Source::Depth | Source::Depth1 | Source::Depth2 | Source::ShadowDepth(_, false) => { @@ -108,7 +102,7 @@ impl SamplerKind { Source::Noise => Self::Repeat, Source::Shadow | Source::ShadowDepth(_, true) => Self::Comparison, // Blocks look as the game draws them: nearest texels. - Source::Atlas => Self::NearestRepeat, + Source::Atlas | Source::Normals | Source::Specular => Self::NearestRepeat, Source::Image(_) if filtering => Self::Linear, Source::Image(_) => Self::Nearest, Source::Custom { blur, clamp, .. } => match (blur && filtering, clamp) { @@ -117,12 +111,9 @@ impl SamplerKind { (true, false) => Self::Repeat, (false, false) => Self::NearestRepeat, }, - Source::Color(_) - | Source::ShadowColor(_) - | Source::Dummy - | Source::Normals - | Source::Specular - | Source::Lightmap => Self::Linear, + Source::Color(_) | Source::ShadowColor(_) | Source::Dummy | Source::Lightmap => { + Self::Linear + } } } @@ -363,6 +354,7 @@ struct Pass { draw: bool, /// What `texture` reads instead of the block atlas. atlas: Option, + material_maps: bool, /// Reads `mc_Entity`'s table (`PackPost::block_ids`). ids: bool, /// Bind groups by the sides read (bits 0–15), the buffers whose mip chain is current (bits @@ -432,6 +424,7 @@ struct Targets { pub(super) struct World<'a> { /// The block atlas and its size. pub atlas: Option<(wgpu::TextureView, [u32; 2])>, + pub material_maps: Option<[wgpu::TextureView; 2]>, pub lightmap: wgpu::TextureView, /// Terrain's geometry, when the pack draws it. pub terrain: Option>, @@ -564,6 +557,7 @@ pub struct PackPost { /// `shaders.properties`' custom uniforms, after the frame's in `uniforms.values`. custom: horizon_shaders::Custom, atlas: Option, + material_maps: Option<[wgpu::TextureView; 2]>, lightmap: wgpu::TextureView, deferred_pre: u16, composite_pre: u16, @@ -1024,6 +1018,8 @@ pub(super) struct Geometry<'a> { pub depth_write: bool, /// What `texture` reads instead of the block atlas. pub atlas: Option, + /// This draw uses the block atlas's surface identities. + pub material_maps: bool, /// `mc_Entity`'s table at set 3 (flat terrain). pub ids: Option<&'a wgpu::BindGroupLayout>, /// Back faces are culled, as for the game's own terrain. @@ -1606,6 +1602,7 @@ fn build_pass( mipmaps: p.mipmaps, draw, atlas: geometry.and_then(|g| g.atlas.clone()), + material_maps: geometry.is_some_and(|g| g.material_maps), ids: geometry.is_some_and(|g| g.ids.is_some()), groups: HashMap::new(), copies: image_buffers(&images) & reads, @@ -1740,6 +1737,7 @@ fn build_compute( mipmaps: 0, draw: false, atlas: None, + material_maps: false, ids: false, groups: HashMap::new(), buffers: !interface.buffers.is_empty(), @@ -1970,6 +1968,7 @@ impl PackPost { table: g.table, depth_write: g.depth_write, atlas: g.atlas.clone(), + material_maps: g.material_maps, ids: block_ids.as_ref().map(|i| &i.0), cull: g.cull, }); @@ -1997,6 +1996,7 @@ impl PackPost { table: Some(&geometry.layout), depth_write: false, atlas: None, + material_maps: false, ids: None, cull: false, }; @@ -2006,6 +2006,7 @@ impl PackPost { table: None, depth_write: false, atlas: world.celestial.clone(), + material_maps: false, ids: None, cull: false, }; @@ -2042,6 +2043,7 @@ impl PackPost { table: Some(&e.table), depth_write: true, atlas: world.celestial.clone(), + material_maps: false, ids: None, cull: false, }); @@ -2053,6 +2055,7 @@ impl PackPost { table: g.table, depth_write: false, atlas: g.atlas.clone(), + material_maps: g.material_maps, ids: g.ids, cull: false, }; @@ -2080,6 +2083,7 @@ impl PackPost { table: Some(layout), depth_write: true, atlas: world.celestial.clone(), + material_maps: false, ids: None, cull: false, }); @@ -2089,6 +2093,7 @@ impl PackPost { table: None, depth_write: true, atlas: None, + material_maps: false, ids: None, cull: false, }); @@ -2099,6 +2104,7 @@ impl PackPost { table: Some(&geometry.layout), depth_write: true, atlas: world.celestial.clone(), + material_maps: false, ids: None, cull: false, }; @@ -2441,6 +2447,7 @@ impl PackPost { center_depth: None, custom, atlas: world.atlas.as_ref().map(|a| a.0.clone()), + material_maps: world.material_maps, lightmap: world.lightmap, deferred_pre: mask(&post.deferred_pre), composite_pre: mask(&post.composite_pre), @@ -2718,8 +2725,16 @@ impl PackPost { Source::Dummy if s.dimension == wgpu::TextureViewDimension::D1 => { &self.dummy_1d } - Source::Normals => &self.dummy_normal, - Source::Specular => &self.dummy_black, + Source::Normals => self + .material_maps + .as_ref() + .filter(|_| pass.material_maps) + .map_or(&self.dummy_normal, |maps| &maps[0]), + Source::Specular => self + .material_maps + .as_ref() + .filter(|_| pass.material_maps) + .map_or(&self.dummy_black, |maps| &maps[1]), Source::Dummy => &self.dummy, Source::Image(k) if pass.image_copies & 1 << k != 0 => { self.images[k].copy.as_ref().unwrap_or(&self.dummy) @@ -4678,3 +4693,7 @@ mod tests { #[cfg(test)] mod destination_tests; + +#[cfg(test)] +#[path = "pack_material_tests.rs"] +mod material_tests; diff --git a/crates/horizon-client-render/src/packs.rs b/crates/horizon-client-render/src/packs.rs index 1627608534..1302d90e74 100644 --- a/crates/horizon-client-render/src/packs.rs +++ b/crates/horizon-client-render/src/packs.rs @@ -636,7 +636,7 @@ fn build_overlay_uncached( } // FallbackResourceManager: a file's `.mcmeta` counts only from the pack that // supplies the file or one above it; vanilla's own sits below every pack. - let hidden: BTreeSet = files + let mut hidden: BTreeSet = files .iter() .filter(|(name, _)| !name.ends_with(".mcmeta")) .filter_map(|(name, (owner, _))| { @@ -647,6 +647,25 @@ fn build_overlay_uncached( } }) .collect(); + // A companion from below the winning colour would describe another surface. + // A map-only pack above it is intentional and remains valid. + for (name, (owner, _)) in &files { + let Some(stem) = name.strip_suffix(".png") else { + continue; + }; + if stem.ends_with("_n") || stem.ends_with("_s") { + continue; + } + for suffix in ["_n.png", "_s.png"] { + let map = format!("{stem}{suffix}"); + if files + .get(&map) + .is_none_or(|(map_owner, _)| map_owner < owner) + { + hidden.insert(map); + } + } + } for name in &hidden { files.remove(name); } @@ -1469,6 +1488,61 @@ mod tests { assert_eq!(merge_kind("assets/minecraft/models/block/stone.json"), None); } #[test] + fn companion_maps_follow_the_winning_colour_or_a_higher_map_only_pack() { + let root = + std::env::temp_dir().join(format!("horizon-material-owner-{}", std::process::id())); + let vanilla = root.join("vanilla"); + let relative = "assets/minecraft/textures/block"; + fs::create_dir_all(vanilla.join(relative)).unwrap(); + let names = ["stone.png", "stone_n.png", "stone_s.png"]; + for name in names { + fs::write(vanilla.join(relative).join(name), b"base").unwrap(); + } + for (pack, files) in [ + ("low", vec!["stone_n.png", "stone_s.png"]), + ("colour", vec!["stone.png"]), + ("maps", vec!["stone_n.png"]), + ] { + let dir = root.join("packs").join(pack); + fs::create_dir_all(dir.join(relative)).unwrap(); + fs::write( + dir.join("pack.mcmeta"), + br#"{"pack":{"description":"fixture","pack_format":97}}"#, + ) + .unwrap(); + for name in files { + fs::write(dir.join(relative).join(name), pack.as_bytes()).unwrap(); + } + } + let all = discover(&vanilla, &root.join("packs")); + let order = |names: &[&str]| { + names + .iter() + .map(|name| { + all.iter() + .find(|p| p.id == format!("file/{name}")) + .unwrap() + .clone() + }) + .collect::>() + }; + let out = root.join("out"); + build_overlay(&vanilla, &order(&["low", "colour"]), &out).unwrap(); + assert_eq!( + fs::read(out.join(relative).join("stone.png")).unwrap(), + b"colour" + ); + assert!(!out.join(relative).join("stone_n.png").exists()); + assert!(!out.join(relative).join("stone_s.png").exists()); + build_overlay(&vanilla, &order(&["low", "colour", "maps"]), &out).unwrap(); + assert_eq!( + fs::read(out.join(relative).join("stone_n.png")).unwrap(), + b"maps" + ); + assert!(!out.join(relative).join("stone_s.png").exists()); + fs::remove_dir_all(root).unwrap(); + } + #[test] fn overlay_layers_packs_over_vanilla() { // `&` would split an unquoted `cmd /C mklink` line. let base = std::env::temp_dir().join(format!("horizon-packs-&{}", std::process::id())); diff --git a/crates/horizon-client-render/src/packs/cache.rs b/crates/horizon-client-render/src/packs/cache.rs index 5e51f5c14b..e042589cf5 100644 --- a/crates/horizon-client-render/src/packs/cache.rs +++ b/crates/horizon-client-render/src/packs/cache.rs @@ -13,7 +13,7 @@ use std::{collections::BTreeSet, fs, io, path::Path, path::PathBuf}; // Version 5 read every vanilla file to stamp it on Windows. // Version 6 stamps vanilla's files by their metadata on Windows too. // Version 7 stacks blockstate files (every pack's definition, bottom first). -const VERSION: u32 = 7; +const VERSION: u32 = 8; #[derive(Default, Serialize, Deserialize)] pub(super) struct Dependencies { diff --git a/crates/horizon-client-render/src/sprites.rs b/crates/horizon-client-render/src/sprites.rs index 27807065f1..ebf9ac9329 100644 --- a/crates/horizon-client-render/src/sprites.rs +++ b/crates/horizon-client-render/src/sprites.rs @@ -8,6 +8,7 @@ pub struct SpriteAnimation { pub layer: u32, frames: Arc>>, clip: Arc, + materials: [Option>>>; 2], } /// What an animated sprite shows: `frame`, mixed `millis` thousandths of the /// way towards `next` (0 for a single frame). @@ -18,6 +19,14 @@ pub struct FrameBlend { pub millis: u32, } impl SpriteAnimation { + pub(crate) fn has_materials(&self) -> bool { + self.materials.iter().any(Option::is_some) + } + /// Material categories follow the selected colour frame, without colour interpolation. + pub(crate) fn material_pixels(&self, kind: usize, tick: u64) -> Option<&[u8]> { + let frames = self.materials[kind].as_ref()?; + Some(&frames[self.frame(tick) % frames.len()]) + } pub fn frame(&self, tick: u64) -> usize { self.clip.sample(tick).frame as usize } @@ -79,9 +88,9 @@ pub struct Sprites { /// Each layer's frames at the texture's own resolution (square), and the /// frame shown first. native: Vec<(u32, Arc>>, usize)>, - /// Each layer's LabPBR maps (`_n` normals, `_s` specular) beside its texture: their first - /// frame at their own resolution (square). - pbr: Vec<[Option<(u32, Vec)>; 2]>, + /// Each layer's LabPBR maps (`_n` normals, `_s` specular) beside its texture: their + /// frames at their own resolution (square). + pbr: Vec<[Option<(u32, Vec>)>; 2]>, /// The layers' normal and specular maps at `pbr_size`, layer after layer, set by /// [`Sprites::finish`]: empty when no texture has either. pub normals: Vec, @@ -117,7 +126,7 @@ pub fn set_texture_budget(mib: u32) { /// Whether resource loads read the resource packs' LabPBR maps (`set_labpbr_maps`). static LABPBR_MAPS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); /// Whether resource loads from now on read the resource packs' LabPBR maps beside their block -/// textures. Only a native shader pack's material stage reads them, so without one they cost +/// textures. Native material stages and terrain shader packs read them; without a pack they cost /// neither loading time nor video memory. pub fn set_labpbr_maps(on: bool) { LABPBR_MAPS.store(on, std::sync::atomic::Ordering::Relaxed); @@ -126,7 +135,7 @@ pub fn set_labpbr_maps(on: bool) { pub fn labpbr_maps() -> bool { LABPBR_MAPS.load(std::sync::atomic::Ordering::Relaxed) } -/// Largest array of each LabPBR map, which only a native shader pack reads. +/// Budget for either complete normal or specular image set. const MAX_MAP_BYTES: u64 = DEFAULT_TEXTURE_BUDGET / 4; /// Fewer layers than any game has (vanilla's are about 1300): what a map is kept at while /// loading assumes at least these many share the array. @@ -144,30 +153,12 @@ fn side_within(start: u32, layers: u64, bytes: u64) -> u32 { } size } -/// A LabPBR map's square `frame` of `side` averaged down by whole powers of two to at most -/// `max`, channel by channel: they are data, not colour. +/// Reduce data maps by selecting whole texels: material IDs and sentinel bytes cannot be averaged. fn shrink_map(frame: Vec, side: u32, max: u32) -> (u32, Vec) { if side <= max || !side.is_power_of_two() || !max.is_power_of_two() { return (side, frame); } - let (out_side, f, side) = (max as usize, (side / max) as usize, side as usize); - let mut out = Vec::with_capacity(out_side * out_side * 4); - for y in 0..out_side { - for x in 0..out_side { - let mut sum = [0u32; 4]; - for dy in 0..f { - let row = ((y * f + dy) * side + x * f) * 4; - for texel in frame[row..row + f * 4].as_chunks::<4>().0 { - for (s, &v) in sum.iter_mut().zip(texel) { - *s += u32::from(v); - } - } - } - let n = (f * f) as u32; - out.extend(sum.map(|s| ((s + n / 2) / n) as u8)); - } - } - (max, out) + (max, resample(&frame, side, max)) } /// Nearest-resample a square RGBA frame to another square size. pub(crate) fn resample(frame: &[u8], from: u32, to: u32) -> Vec { @@ -280,14 +271,28 @@ impl Sprites { if largest_map.is_some() { let pbr_size = self.pbr_size; log::info!("Horizon: LabPBR maps at {pbr_size} px, {layers} layers"); - for maps in std::mem::take(&mut self.pbr) { + for animation in &mut self.animations { + animation.materials = self.pbr[animation.layer as usize].each_ref().map(|map| { + map.as_ref().map(|(from, frames)| { + Arc::new( + frames + .iter() + .map(|frame| resample(frame, *from, pbr_size)) + .collect(), + ) + }) + }); + } + for (layer, maps) in std::mem::take(&mut self.pbr).into_iter().enumerate() { for (map, (out, none)) in maps.into_iter().zip([ (&mut self.normals, FLAT_NORMAL), (&mut self.specular, NO_SPECULAR), ]) { match map { - Some((from, pixels)) => { - let (from, pixels) = shrink_map(pixels, from, pbr_size); + Some((from, frames)) => { + let initial = self.native[layer].2 % frames.len(); + let (from, pixels) = + shrink_map(frames[initial].clone(), from, pbr_size); out.extend(resample(&pixels, from, pbr_size)); } None => out.extend(none.repeat((pbr_size * pbr_size) as usize)), @@ -629,8 +634,10 @@ impl Sprites { let pbr = match name.split_once(':') { Some(_) if labpbr_maps() => { let path = crate::atlas_sources::texture_path(root, name, "textures"); - ["_n", "_s"].map(|suffix| pbr_map(&path, suffix, count)) + ["_n", "_s"].map(|suffix| pbr_map(&path, suffix, count, animation.as_ref())) } + None if labpbr_maps() && !name.starts_with("fusion/") => ["_n", "_s"] + .map(|suffix| pbr_map(&root.join(name), suffix, count, animation.as_ref())), _ => [None, None], }; let settings = crate::mipmap::Settings { @@ -680,6 +687,7 @@ impl Sprites { layer, frames: frames.clone(), clip: Arc::new(clip), + materials: [None, None], }); } self.mips.push(settings); @@ -723,32 +731,64 @@ fn read_rgba(path: &Path) -> Result<(u32, u32, Vec)> { Ok((info.width, info.height, pixels)) } -/// The LabPBR map with `suffix` beside the texture at `path`, whose frames lie in a column of -/// `count`: its first frame, square, no larger than the maps' array allows any game's -/// textures (a 512x pack's maps would otherwise fill the memory while loading). -fn pbr_map(path: &Path, suffix: &str, count: u32) -> Option<(u32, Vec)> { +/// Companion frames use the colour image's grid; a single-frame map stays static. +fn pbr_map( + path: &Path, + suffix: &str, + count: u32, + animation: Option<&AnimationMetadata>, +) -> Option<(u32, Vec>)> { let stem = path.file_stem()?.to_str()?; let map = path.with_file_name(format!("{stem}{suffix}.png")); let (map_width, map_height, pixels) = read_rgba(&map).ok()?; - let (w, h) = (map_width, map_height / count.max(1)); + let (width, height, _) = read_rgba(path).ok()?; + let [frame_width, frame_height] = animation.map_or([width as i32, height as i32], |a| { + a.frame_size(width as i32, height as i32) + }); + let (frame_width, frame_height) = ( + u32::try_from(frame_width).ok()?, + u32::try_from(frame_height).ok()?, + ); + if frame_width == 0 || frame_height == 0 { + return None; + } + let (columns, rows) = if count <= 1 || (map_width == frame_width && map_height == frame_height) + { + (1, 1) + } else if u64::from(map_width) * u64::from(height) == u64::from(map_height) * u64::from(width) { + (width / frame_width, height / frame_height) + } else if u64::from(map_width) * u64::from(frame_height) + == u64::from(map_height) * u64::from(frame_width) + { + (1, 1) + } else { + return None; + }; + if columns == 0 || rows == 0 || map_width % columns != 0 || map_height % rows != 0 { + return None; + } + let (w, h) = (map_width / columns, map_height / rows); if w == 0 || h == 0 { return None; } let side = w.max(h); - let mut frame = Vec::with_capacity((side * side * 4) as usize); - for row in 0..side { - for column in 0..side { - let offset = (((row * h / side) * map_width + column * w / side) * 4) as usize; - frame.extend_from_slice(&pixels[offset..offset + 4]); - } - } - Some(shrink_map( - frame, - side, - side_within(side, LAYERS_AT_LEAST, MAX_MAP_BYTES), - )) + let output_side = side_within(side, LAYERS_AT_LEAST, MAX_MAP_BYTES).min(side); + let frames = (0..columns * rows) + .map(|index| { + let (x, y) = (index % columns * w, index / columns * h); + let mut frame = Vec::with_capacity((side * side * 4) as usize); + for row in 0..side { + for column in 0..side { + let offset = + (((y + row * h / side) * map_width + x + column * w / side) * 4) as usize; + frame.extend_from_slice(&pixels[offset..offset + 4]); + } + } + resample(&frame, side, output_side) + }) + .collect(); + Some((output_side, frames)) } - /// SpriteContents: the `.mcmeta` `texture` section (TextureMetadataSection's /// mipmap strategy and alpha cutoff bias). fn texture_section( @@ -779,6 +819,7 @@ pub(crate) fn test_frames(layer: u32, frames: Vec>, metadata: &str) -> S layer, clip: Arc::new(metadata.clip(frames.len() as u32).unwrap().unwrap()), frames: Arc::new(frames), + materials: [None, None], } } @@ -804,8 +845,8 @@ mod tests { assert_eq!(side_within(512, 1300, 1), SPRITE_SIZE); } #[test] - fn a_labpbr_map_shrinks_by_plain_means_and_a_layer_without_one_gets_the_default() { - // Alpha is data here: it does not weigh the other channels. + fn a_labpbr_map_selects_whole_texels_and_missing_layers_get_defaults() { + // Selecting a texel preserves encoded channels, including categorical values. let frame = [ [200, 100, 0, 255], [0, 0, 0, 0], @@ -813,14 +854,14 @@ mod tests { [0, 0, 0, 0], ] .concat(); - assert_eq!(shrink_map(frame, 2, 1), (1, vec![100, 50, 0, 128])); + assert_eq!(shrink_map(frame, 2, 1), (1, vec![200, 100, 0, 255])); let mut sprites = Sprites { native: vec![ (SPRITE_SIZE, Arc::new(vec![vec![0; 16 * 16 * 4]]), 0), (SPRITE_SIZE, Arc::new(vec![vec![0; 16 * 16 * 4]]), 0), ], pbr: vec![ - [Some((16, [9, 8, 7, 6].repeat(16 * 16))), None], + [Some((16, vec![[9, 8, 7, 6].repeat(16 * 16)])), None], [None, None], ], ..Default::default() @@ -833,6 +874,50 @@ mod tests { assert_eq!(sprites.specular.len(), 2 * 16 * 16 * 4); } #[test] + fn companion_grid_and_static_map_follow_colour_frame_selection() { + let root = + std::env::temp_dir().join(format!("horizon-companion-grid-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + let write = |name: &str, width, height, pixels: &[u8]| { + let mut encoder = + png::Encoder::new(File::create(root.join(name)).unwrap(), width, height); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + encoder + .write_header() + .unwrap() + .write_image_data(pixels) + .unwrap(); + }; + write("grid.png", 4, 4, &[200, 200, 200, 255].repeat(16)); + let maps: Vec = (0..16) + .flat_map(|i| { + let cell = (i / 8) * 2 + (i % 4) / 2; + [10 + cell as u8, 230, 65, 255] + }) + .collect(); + write("grid_s.png", 4, 4, &maps); + write("grid_n.png", 2, 2, &[128, 128, 255, 255].repeat(4)); + let metadata = + r#"{"width":2,"height":2,"frametime":2,"interpolate":true,"frames":[3,1,2,0]}"#; + let animation = AnimationMetadata::from_json(metadata).unwrap(); + let (side, frames) = pbr_map(&root.join("grid.png"), "_s", 4, Some(&animation)).unwrap(); + assert_eq!(side, 2); + assert_eq!( + frames.iter().map(|f| f[0]).collect::>(), + [10, 11, 12, 13] + ); + let (_, normals) = pbr_map(&root.join("grid.png"), "_n", 4, Some(&animation)).unwrap(); + assert_eq!(normals.len(), 1); + let mut sprite = test_frames(0, vec![vec![0; 16]; 4], metadata); + sprite.materials = [Some(Arc::new(normals)), Some(Arc::new(frames))]; + for (tick, expected) in [(0, 13), (1, 13), (2, 11), (4, 12), (6, 10)] { + assert_eq!(sprite.material_pixels(1, tick).unwrap()[0], expected); + assert_eq!(&sprite.material_pixels(0, tick).unwrap()[..4], &FLAT_NORMAL); + } + fs::remove_dir_all(root).unwrap(); + } + #[test] fn textures_without_labpbr_maps_keep_none() { let mut sprites = Sprites { native: vec![(SPRITE_SIZE, Arc::new(vec![vec![0; 16 * 16 * 4]]), 0)], @@ -1273,6 +1358,7 @@ mod tests { fn animation_boundaries_and_repeated_frames_do_not_depend_on_render_rate() { let animation = SpriteAnimation { layer: 0, + materials: [None, None], frames: Arc::new(vec![vec![], vec![], vec![]]), clip: Arc::new(AnimationMetadata::from_json(r#"{"frames":[{"index":2,"time":8},{"index":1,"time":3},{"index":1,"time":2},0]}"#).unwrap().clip(3).unwrap().unwrap()), }; diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs index 95c40f42c1..4c79101d40 100644 --- a/crates/horizon-client/src/game.rs +++ b/crates/horizon-client/src/game.rs @@ -19914,7 +19914,7 @@ fn load_resources( // server thread's (horizon_threads::jobs), sized before any world opens. horizon_threads::jobs::configure(threads); horizon_client::sprites::set_texture_budget(settings.horizon.texture_budget_mib); - horizon_client::sprites::set_labpbr_maps(shader_packs::picks_native_pack(settings)); + horizon_client::sprites::set_labpbr_maps(shader_packs::picks_material_pack(settings)); // Stage A: the menu bundle (atlas and panorama). With a loading screen it is // published at once so the title screen can appear while the world bundle // loads; the synchronous launch instead carries it in `Loaded`. diff --git a/crates/horizon-client/src/game/shader_packs.rs b/crates/horizon-client/src/game/shader_packs.rs index cc9a042ad1..84bf05febd 100644 --- a/crates/horizon-client/src/game/shader_packs.rs +++ b/crates/horizon-client/src/game/shader_packs.rs @@ -108,16 +108,14 @@ fn active_pack<'a>(picked: &'a str, mods: &horizon_client::mod_list::ModList) -> } } -/// Whether the pack `settings` pick (or `HORIZON_SHADER_PACK`) is one of Horizon's own: only -/// then do resource loads read the resource packs' LabPBR maps (`sprites::set_labpbr_maps`). -pub(super) fn picks_native_pack(settings: &horizon_client::settings::Settings) -> bool { +/// A selected pack may read resource-pack material maps, whether native or GLSL. +pub(super) fn picks_material_pack(settings: &horizon_client::settings::Settings) -> bool { let dev = std::env::var("HORIZON_SHADER_PACK").ok(); let picked = active_pack( dev.as_deref().unwrap_or(&settings.horizon.shader_pack), &settings.mods, ); !picked.is_empty() - && horizon_shader_api::package::is_pack(&Game::shader_packs_dir().join(picked)) } /// The ids a pack gives entities and block entities (4.1, 5.3), for the spans of the entity @@ -178,6 +176,11 @@ impl Game<'_> { if picked.is_empty() { return PackInputs::default(); } + if !horizon_client::sprites::labpbr_maps() { + horizon_client::sprites::set_labpbr_maps(true); + self.reload_packs(); + return PackInputs::default(); + } let name = picked; let dimension = match self.dim { Dim::Overworld => 0, diff --git a/docs/research/terrain-material-inputs-20261103.json b/docs/research/terrain-material-inputs-20261103.json new file mode 100644 index 0000000000..753e711d58 --- /dev/null +++ b/docs/research/terrain-material-inputs-20261103.json @@ -0,0 +1,505 @@ +{ + "purpose": "Proposed independently developed replacement for closed #7800, addressing #7747; not an eligibility certification. Maintainer process/code acceptance remains separate.", + "authoring_inputs": { + "external_inputs": [ + { + "url": "https://raw.githubusercontent.com/aCannedFish/Shader/bd9be6f1b8e5c00e47d922d14d8cc96e961397a2/shaders/gbuffers_terrain.fsh", + "revision": "bd9be6f1b8e5c00e47d922d14d8cc96e961397a2", + "path": "shaders/gbuffers_terrain.fsh", + "sha256": "b18f4ee180e77e543bb1fb2ea354c26ffc72a1f64c81ceb1437e474529f7472a", + "license": "MIT", + "permission": "Root MIT; per-file ownership/license comments inspected separately before code exposure" + }, + { + "url": "https://raw.githubusercontent.com/aCannedFish/Shader/bd9be6f1b8e5c00e47d922d14d8cc96e961397a2/shaders/program/gbuffers_terrain.fsh.glsl", + "revision": "bd9be6f1b8e5c00e47d922d14d8cc96e961397a2", + "path": "shaders/program/gbuffers_terrain.fsh.glsl", + "sha256": "e4dc9322f2e61e869560b4b7dad23dce01ed2079502fde72aba581a26579d5eb", + "license": "MIT", + "permission": "Root MIT; per-file ownership/license comments inspected separately before code exposure" + }, + { + "url": "https://raw.githubusercontent.com/aCannedFish/Shader/bd9be6f1b8e5c00e47d922d14d8cc96e961397a2/shaders/lib/labpbr_decode.glsl", + "revision": "bd9be6f1b8e5c00e47d922d14d8cc96e961397a2", + "path": "shaders/lib/labpbr_decode.glsl", + "sha256": "def324ffe56cd4aef911cea3fea8508dd72e32e347288177bf28e2d5f5030ad8", + "license": "MIT", + "permission": "Root MIT; per-file ownership/license comments inspected separately before code exposure" + }, + { + "url": "https://github.com/aCannedFish/Shader/blob/bd9be6f1b8e5c00e47d922d14d8cc96e961397a2/README.md", + "revision": "bd9be6f1b8e5c00e47d922d14d8cc96e961397a2", + "path": "README.md", + "sha256": "1df3cce191e6f41cbea55b5e284f944d412498d9ed58ae48ea50fe6751661bf0", + "license": "MIT", + "exposure": "ownership/license sections only; complete bytes archived but remainder not shown to model", + "permission": "explicit code MIT and aCannedFish author assertion" + }, + { + "url": "https://github.com/aCannedFish/Shader/blob/bd9be6f1b8e5c00e47d922d14d8cc96e961397a2/THIRD_PARTY.md", + "revision": "bd9be6f1b8e5c00e47d922d14d8cc96e961397a2", + "path": "THIRD_PARTY.md", + "sha256": "b5464af623d15a2e999c63242971472b18431abdb685eeaf994619dadd809496", + "license": "metadata only", + "exposure": "licensing/ownership metadata", + "permission": "moon data exception excluded; interface references not followed" + }, + { + "url": "https://github.com/BelmuTM/Noble/blob/60fed66056e0d6ff4610618356a95b9e6a363e16/LICENSE.txt", + "revision": "60fed66056e0d6ff4610618356a95b9e6a363e16", + "path": "LICENSE.txt", + "sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986", + "license": "GPL-3.0", + "exposure": "license metadata only", + "accepted": false, + "reason": "not in assignment accepted permissive license set; shader code not read" + }, + { + "url": "https://github.com/aCannedFish/LuculentShader/blob/bd9be6f1b8e5c00e47d922d14d8cc96e961397a2/LICENSE", + "revision": "bd9be6f1b8e5c00e47d922d14d8cc96e961397a2", + "path": "LICENSE", + "sha256": "a9bc89f5f7db3c48720212f1a8a121b92fbec8bedf72da1d352b40a941990eca", + "license": "MIT", + "exposure": "license metadata only", + "accepted": true, + "reason": "MIT explicitly permits modification and public redistribution" + }, + { + "url": "https://github.com/sixthsurge/photon/blob/2cf68ad0c432f81510a36f5de19f988769fb9a2f/LICENSE", + "revision": "2cf68ad0c432f81510a36f5de19f988769fb9a2f", + "path": "LICENSE", + "sha256": "514402d18eedb0afe374344ca9f0fe87b7399779518184b8fc64e31674555fc5", + "license": "NOASSERTION", + "exposure": "license metadata only", + "accepted": false, + "reason": "not in assignment accepted permissive license set; shader code not read" + } + ], + "unread_rejected_endpoints": [ + "CaptTatsu/BSLShaders404", + "TheCMK/Mellow404" + ], + "discovery": "Web search for GitHub Minecraft shader pack MIT license normal specular; metadata snippets only. Search result also exposed license change metadata for AuraLite (NC-SA), rejected without source access. No external loader implementation/docs or held PR sources read.", + "baseline": { + "public_revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "local_baseline_commit": "c9cc721a10a3871e868b13f47ce586a0f0f6b0bb", + "license": "MIT" + }, + "incidental_search_exposure": "Generic initial discovery search automatically returned non-pack snippets, including third-party shader lists and a Reddit companion-filename anecdote. They were not opened or relied on for implementation. Record retained to avoid claiming narrower exposure than occurred.", + "metadata_artifacts": [ + { + "path": "BelmuTM-Noble-license.json", + "sha256": "cdc6da15faafac43609525c8f22a7e64def9a5c07ddfad63017dad07785bf4a9", + "exposure": "licensing metadata or filename tree only; no additional shader code inspected" + }, + { + "path": "aCannedFish-Shader-license.json", + "sha256": "33aa8597f2ce34be81734316822272066a48dd4683c0b0d88e88ea71e62c0bdf", + "exposure": "licensing metadata or filename tree only; no additional shader code inspected" + }, + { + "path": "photon-license.json", + "sha256": "dae18198fd32215d6a50f2ec9340c5d0af9b9e87d2ff0e3ea1486b926ca0f360", + "exposure": "licensing metadata or filename tree only; no additional shader code inspected" + }, + { + "path": "photon-tree.json", + "sha256": "967f6295e1110fd1ab169cf984ca675280a7c6353dad5ffeeda4d76b384444ee", + "exposure": "filename tree only" + }, + { + "path": "pack-tree.json", + "sha256": "9c90e3ebe783c5779e904541e85f639852a5e0eba6f27348d956efa3f4374c7d", + "exposure": "filename tree only" + } + ] + }, + "authoring_baseline_file_ledger": [ + { + "path": "AGENTS.md", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/AGENTS.md", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "44ca4ef689231da0601a6faef3ecda891f3459686c8697006a00061f6a7351f1", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "CONTRIBUTING.md", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/CONTRIBUTING.md", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "69d36f19de42931e35a2995b552cc638df1f77e7c5b6afda6ad092982f636dd6", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "LICENSE", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/LICENSE", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "b4b374ae9d0f2d8f7be35c6783e41327431da3b5662605da9ba31493630621df", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/Cargo.toml", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/Cargo.toml", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "bacc9d5d48b8d63419568155c4bf251f5ab02a703ea066dfdd7e8b7001bee7c1", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "4c4a12e9e8b5fb3f39b1956135137c7f3784ad993564081c7c0d78c767cb7e50", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/atlas_sources.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/atlas_sources.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "48a7ba284ca77ea45ac3b2620b1e6cbfa30fef93f1503e54aeb07e3316a30ff0", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/sprites.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/sprites.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "b851a31863deb48498ddb714a5eff5ca88c2fcc75ea68c0bf04ea4f64fca430e", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx/pack_atlas.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx/pack_atlas.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "ba51bf515abdce5ca7b754581ebb8f36f0c6698e9f3674818d97d4735b4d8cdc", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx/pack_post.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx/pack_post.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "12cd801eb958c45adc565243d8f904aaf72de31ae568fed78a456f6199c91706", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx/pack_textures.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx/pack_textures.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "5747745d3a57d9ef59e7d7a1daa97d24a35fb5d5d1fa29d03be7ea2214489878", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client/src/game.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client/src/game.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "747afdbdd282f9dd4dc838f6428a7c5a866bb32831ece99739d69b3b3e27e64a", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client/src/game/shader_packs.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client/src/game/shader_packs.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "bf3c878e04591c069e0d23eec050dbf3c636731af44a56248ed8165bc61f7c0d", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client/src/game/native_shaders.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client/src/game/native_shaders.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "7cde9404cf0425064731eb1ea9bf680ffbdf094e2d5a1bd031cd2d840dc3699d", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/packs.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/packs.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "93603e85fd49232a6dcc948c6bc701d4b8e9ef3c8ba63cd3fc0631b82dd085bb", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/packs/cache.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/packs/cache.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "4eae2e9b3955a08824134b998158a9e1905a1e844eb01baa9e51840647bc98a0", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/hud.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/hud.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "ce3c434bafa734c8279b02f149cad4d4808cde5670a82b71ecfc62f86e744c12", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/model_library.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/model_library.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "a29f0a3735eb03f1fcc80d8fcc1a7a7938f09b3b8afc5ea28792efa20be025b3", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx/animate.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx/animate.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "ee8c47dc1b42b211d6cbc6e4dd993c5ac94c054010de84b5d97354a6c70ebee5", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx/upload.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx/upload.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "283734fcb6e9c7b8c49a1acc6eb1983109d129ac1d974aa468e247f95b837f59", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/src/gfx/test_gpu.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/src/gfx/test_gpu.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "0abca5d8fbc71d96af431390117729d50b7a6f6ff3305ade80740333b1a4ae2a", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-shaders/src/post.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-shaders/src/post.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "72383d80a17f0d50ddfd63e366430c8dde27b791244a36fb7b19293378273095", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-shaders/src/translate/mod.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-shaders/src/translate/mod.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "37325ad22dfb79db6028d67a1f1ae0d283b4dcbf4a634a86bbfe47fe6bf761a0", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-shaders/src/config.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-shaders/src/config.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "4f20b09074c57677e0c19a187f52c617284d5ea6c160bd921db0e9706e45b818", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "crates/horizon-client-render/tests/sunrise_shader.rs", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/crates/horizon-client-render/tests/sunrise_shader.rs", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "4f0aeb8aec342a831660dee74c421ac9a88cf873d25efa4e024a0338193ed5a4", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + }, + { + "path": "tools/check_public_tree.py", + "url": "https://github.com/horizon-rust/horizon/blob/bc761767d35d4fdc1f4b1144fa8ab75168ed4d14/tools/check_public_tree.py", + "revision": "bc761767d35d4fdc1f4b1144fa8ab75168ed4d14", + "sha256": "f39f800a1dcfc9196a6919495267aa3a84415ae751b36d427df99d0c86d73854", + "license": "MIT", + "permission": "supplied permitted public baseline; implementation/tooling input only", + "exposure": "bounded snippets, searches or baseline tool execution; not necessarily full file" + } + ], + "verification_inputs": { + "purpose": "Verification of two explicitly permitted new candidates; supplied public MIT baseline/build/compiler dependencies only. Targeted portions/search excerpts rather than full contents where applicable. No new external behavior source opened.", + "incidental_exposure": "Mandatory initial generic memory registry search exposed historical topic labels and file pointers only; no old implementation, test, old PR body/diff/branch or source-derived audit report opened or used. Disclosed promptly to parent.", + "files": [ + { + "candidate": "7800", + "path": "AGENTS.md", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "44ca4ef689231da0601a6faef3ecda891f3459686c8697006a00061f6a7351f1", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "6887ddfe54fa2b5e8de9b75f0786d08c83fd00b064b1084b6ba2c5125bea8c64", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx/pack_post.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "054cb3bcc1c6d6ba37629e5add8bd7fef31bdd8c758598499c407006c671529c", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx/pack_atlas.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "8d6651972f997b2ee3e48df21792b7d175f91cc8d143ecdfb8848eb92f663797", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx/pack_material_tests.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "0be6fcbeb27d2360895f5e05b246f879e93aff1df4e2dc99b79e18a58df99fa1", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx/test_gpu.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "0abca5d8fbc71d96af431390117729d50b7a6f6ff3305ade80740333b1a4ae2a", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx/animate.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "ee8c47dc1b42b211d6cbc6e4dd993c5ac94c054010de84b5d97354a6c70ebee5", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/gfx/upload.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "283734fcb6e9c7b8c49a1acc6eb1983109d129ac1d974aa468e247f95b837f59", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/sprites.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "24116850e0e6192ab31c0e21b44cdb0d7eaab446941203c985b39c9ff5e3694d", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client-render/src/packs.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "9d5a5ec39caa04fa0f75c2393fb35d4ae8fc6a2dcec6aa5af51d1f592ef7080d", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client/src/game.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "b476bfef967a02492dbf7209d8808040f6acda1194cb3e9899813857a237fb32", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client/src/game/shader_packs.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "ccb14e8005b963615e5e94beef83037c3b24a1c02abbcd26a8706ef3d991b18e", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-client/src/game/packs.rs", + "head": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "working_file_sha256": "e539f1a4e96ad8bfc37ef9a8d0646a5379896ef7da63b286d25881a8926ce96c", + "scope": "allowed independent candidate/baseline implementation/tooling; targeted read/search" + }, + { + "candidate": "7800", + "path": "crates/horizon-shaders/src/post.rs", + "head": "a50844d94aec0e7eb77928aacc72df20157c593d", + "working_file_sha256": "72383d80a17f0d50ddfd63e366430c8dde27b791244a36fb7b19293378273095", + "scope": "permitted baseline tool/source; targeted alpha-observation fixture precondition review" + }, + { + "candidate": "7800", + "path": "crates/horizon-shaders/src/translate/mod.rs", + "head": "a50844d94aec0e7eb77928aacc72df20157c593d", + "working_file_sha256": "37325ad22dfb79db6028d67a1f1ae0d283b4dcbf4a634a86bbfe47fe6bf761a0", + "scope": "permitted baseline tool/source; targeted alpha-observation fixture precondition review" + } + ] + }, + "frozen_independent_candidate": { + "head": "a50844d94aec0e7eb77928aacc72df20157c593d", + "initial_candidate": "fc143dfe8bc0ed6c05ccefe77331da8546199fb2", + "production_changes_during_verification": false, + "patch_sha256": "33282176d60ae24ca243e4c29fa7092756463acd7b7bfb8182a673ae454da3d2", + "source_files": { + "crates/horizon-client-render/src/gfx.rs": "6887ddfe54fa2b5e8de9b75f0786d08c83fd00b064b1084b6ba2c5125bea8c64", + "crates/horizon-client-render/src/gfx/pack_atlas.rs": "8d6651972f997b2ee3e48df21792b7d175f91cc8d143ecdfb8848eb92f663797", + "crates/horizon-client-render/src/gfx/pack_post.rs": "054cb3bcc1c6d6ba37629e5add8bd7fef31bdd8c758598499c407006c671529c", + "crates/horizon-client-render/src/packs.rs": "9d5a5ec39caa04fa0f75c2393fb35d4ae8fc6a2dcec6aa5af51d1f592ef7080d", + "crates/horizon-client-render/src/packs/cache.rs": "0df3ed83d3203beac80ccec039ad731e68b0b66369bfab9fc03ae25b8af46c16", + "crates/horizon-client-render/src/sprites.rs": "24116850e0e6192ab31c0e21b44cdb0d7eaab446941203c985b39c9ff5e3694d", + "crates/horizon-client/src/game.rs": "b476bfef967a02492dbf7209d8808040f6acda1194cb3e9899813857a237fb32", + "crates/horizon-client/src/game/shader_packs.rs": "ccb14e8005b963615e5e94beef83037c3b24a1c02abbcd26a8706ef3d991b18e", + "crates/horizon-client-render/src/gfx/pack_material_tests.rs": "a3f060e0e25aada11bce283bd727c5923a1f6ce7ae3644ea8e9ec62caecaa12d" + } + }, + "public_main_integration": { + "revision": "8dffb823a45d307988dcc0e9d0e1767d665510fb", + "license": "MIT", + "scope": "Public main was fetched after the uniform candidate freeze for its separate API integration. PBR production source remained unchanged while only the original fixture was corrected from approved baseline inputs. PBR integration began after its own freeze. No old PR refs/history fetched. All production hunks applied unchanged; only the original test-module append moved after a new main test-module declaration." + }, + "independent_baseline_validation": { + "rust": "1.99.0; default debug tests; no custom RUSTFLAGS; serialized builds, jobs=2", + "sprite_tests_passed": 18, + "resource_pack_tests_passed": 12, + "gpu": { + "adapter": "Intel Arc A770 / Mesa26.2.4 / Vulkan", + "binary_sha256": "d914ab62fe002dbc94ed93a034dd15dec0e5e5f1e323fbf90a4d7b42e9e35c6e", + "passed": true, + "controls": [ + "terrain material binding:15 exact RGBA pixels across5layers/3split arrays", + "disabled geometry material scope:15 exact RGBA pixels", + "whole-material-atlas absence:15 exact RGBA pixels" + ], + "fixture_corrections": [ + "request adapter device limits, reject CPU adapter", + "enable existing copy-usage mask for readback", + "sample definite source-texel interior, retaining expected bytes", + "turn off cutout alpha test in synthetic pack so encoded zero alpha remains observable" + ] + }, + "limits": [ + "No whole-pack/player gameplay/performance acceptance.", + "CPU animation fixture verifies colour-selected grid/static frames; live animation upload remains untested.", + "Companion animation follows the colour timeline; independent companion metadata is not implemented.", + "Ambiguous grids and unverified generated/tiled origins retain missing-map defaults.", + "Final-main qualification is reported separately in the PR." + ] + } +}