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 <[email protected]>
This commit is contained in:
OpenAI Codex committed 2026-11-03 12:07:54 -05:00
1 parent 8dffb823a4
commit ef9d8b67d5
10 files changed
+1136 -107

No files matched your search

+114 -32
View File
@@ -967,8 +967,8 @@ pub struct Renderer {
entity_skin: Option<entity_skin::EntitySkin>,
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<Vec<[wgpu::Texture; 2]>>,
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<SpriteAnimation>,
active_animated_layers: Vec<u64>,
uploads: upload::Uploads,
/// Section meshes are staged through their own belt into
@@ -1137,6 +1138,7 @@ pub struct Renderer {
pack_weather: Option<pack_weather::PackWeather>,
/// The block sprites as one atlas, while a shader pack is loaded.
pack_atlas: Option<pack_atlas::PackAtlas>,
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<wgpu::Texture> = 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<String>, Vec<String>)> {
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,
@@ -26,6 +26,37 @@ impl PackAtlas {
split: u32,
layers: u32,
sprite: u32,
) -> Option<Self> {
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> {
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<Self> {
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 {
@@ -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<u8> = (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", &macros);
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, &macros, &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();
}
@@ -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<wgpu::TextureView>,
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<Geometry<'a>>,
@@ -564,6 +557,7 @@ pub struct PackPost {
/// `shaders.properties`' custom uniforms, after the frame's in `uniforms.values`.
custom: horizon_shaders::Custom,
atlas: Option<wgpu::TextureView>,
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<wgpu::TextureView>,
/// 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;
+75 -1
View File
@@ -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<String> = files
let mut hidden: BTreeSet<String> = 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::<Vec<_>>()
};
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()));
@@ -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 {
+137 -51
View File
@@ -8,6 +8,7 @@ pub struct SpriteAnimation {
pub layer: u32,
frames: Arc<Vec<Vec<u8>>>,
clip: Arc<AnimationClip>,
materials: [Option<Arc<Vec<Vec<u8>>>>; 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<Vec<Vec<u8>>>, 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<u8>)>; 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<Vec<u8>>)>; 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<u8>,
@@ -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<u8>, side: u32, max: u32) -> (u32, Vec<u8>) {
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<u8> {
@@ -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<u8>)> {
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<u8>)> {
/// 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<Vec<u8>>)> {
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<Vec<u8>>, 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<u8> = (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::<Vec<_>>(),
[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()),
};
+1 -1
View File
@@ -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`.
@@ -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,
@@ -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."
]
}
}