Fix HD atlas sampling and protected terrain joins

Add rational atlas cells and bounded authenticated Original-detail retention with standalone synthetic regression checks. Rebuild Linux and Windows runtimes; validate all 5384 supplied frames and preserve three exact 1080p regression controls.
This commit is contained in:
raptorreplica committed 2026-10-17 19:22:20 +11:00
1 parent ef8a649e03
commit a073f73f3a
14 files changed
+1086 -40

No files matched your search

BIN
View File
Binary file not shown.
Binary file not shown.
+17 -4
View File
@@ -12,12 +12,13 @@ A complete PNG for either an opaque or transparent original may use dimensions p
All policies below default to disabled. They require explicit frame metadata and source/art review; loader validation alone does not approve a remaster.
- `atlas_grid: {"columns": 4, "rows": 2, "column": 0, "row": 0}` selects one equal cell of an unchanged atlas. It is mutually exclusive with `crop`. The complete atlas must have the exact aspect ratio implied by the grid and original logical canvas, with at least 2× detail per cell. Fractional boundaries use an isolated Lanczos3 filter without truncating edge strips. Pixels wholly outside the selected cell are excluded; boundary-straddling pixels contribute only their overlapping area. Grid selections have separate cache identities; frames without this option retain their existing sampling and cache keys.
- `preserve_reserved_coverage: true` retains an original gameplay palette label 250–255 at a feathered edge only when its resident logical cell has mean alpha of at least 128 and at least half its area has alpha of 128 or greater. Missing or sparse artwork stays transparent. Ordinary colors, water/fire classes and static-card rendering keep their existing behavior.
- `native_nearest_cues: [[x, y], ...]` samples the unchanged resident RGBA at the nearest pixel for up to 64 explicitly listed original cells, only at gameplay scale 1. Coordinates must be sorted lexicographically, unique, within an opaque original canvas, and refer to ordinary source indices below 240. This can preserve a small raw-supported warning color that averaging dulls. It does not recolor artwork or reconstruct absent details; other cells, higher scales and static-card rendering keep their existing sampler.
- `original_detail_cells: [[x, y], ...]` retains explicitly listed **Original** logical details at gameplay scales 1–4. Up to 64 cells must be sorted, unique, in bounds, painted with authentic Source indices 0–255 and disjoint from `native_nearest_cues`; transparent Source cells are rejected. The runtime copies the exact Original ScaleNx footprint into the generated replacement only in SourceClasses/gameplay mode. Terrain starts with pristine HD for its neighbor joins, then restores the listed Original footprints from the authentic joined Original control after every join. Neighboring HD and Original-fallback cells remain unchanged. Static cards continue to sample the PNG. Raw PNG bytes, alpha, framing and loader eligibility do not change.
- `original_detail_cells: [[x, y], ...]` retains explicitly listed **Original** logical details at gameplay scales 1–4. Legacy lists of up to 64 cells keep their existing behavior. Lists of 65–128 cells additionally require `4 * retained_cell_count <= painted_Source_cell_count`, counted from the decoded authentic Original payload (indices 0–255, excluding transparency), never canvas area or generated PNG alpha. All cells must remain sorted, unique, in bounds, painted and disjoint from `native_nearest_cues`; transparent Source cells are rejected. The runtime copies the exact Original ScaleNx footprint into the generated replacement only in SourceClasses/gameplay mode. Terrain conditions retained samples with the authentic joined Original control before blending neighbors, then restores their exact footprints after all joins. This can also change neighboring pixels within the existing join bands; unrelated pixels and fallback behavior remain unchanged. Static cards continue to sample the PNG. Raw PNG bytes, alpha, framing and loader eligibility do not change.
Retained pixels are Original details, not generated HD. Original palette cycling, water/fire and blinking labels remain live Source indices; ScaleNx may also contain transparent subpixels shaped by Original neighbors. Coarse detail or palette effects can form visible seams and require explicit independent all-scale and genuine-context hybrid review. The ordinary-only predecessor is not a current hybrid approval. Registration requires the exact `retained-original-details-v2-painted0255` provenance declaration and exact selected cells. Existing default artwork holds are preserved; this option does not rescue invalid framing, provider refusals or broad geometry/material failures.
Retained pixels are Original details, not generated HD. Original palette cycling, water/fire and blinking labels remain live Source indices; ScaleNx may also contain transparent subpixels shaped by Original neighbors. Coarse detail or palette effects can form visible seams and require explicit independent all-scale and genuine-context hybrid review. The ordinary-only predecessor is not a current hybrid approval. Registration accepts the existing `retained-original-details-v2-painted0255` declaration only for legacy lists of up to 64, or `retained-original-details-v3-max128-quarter-painted0255` with exact selected cells. Longer lists always require v3 and actual catalog Source validation by the registrar. Metadata-only candidate preparation cannot prove the density bound. Lists up to 64 preserve existing per-frame, indexed and rejected-fallback cache identities; longer lists use new indexed and rejected-fallback namespaces. Protected terrain joins follow the conditioning rule above for either list size. The combined 32 MiB Source-reference budget is unchanged. Retained cells never count as generated detail, and the area rule does not prove novelty in the unretained cells. A whole-Source fallback cannot gain artistic HD approval through this option; legacy acceptance is preserved without relabeling its pixels. Material, all-scale and genuine-context gates stay in force (the separately bounded unused-catalog scope still explicitly declares no genuine runtime context). Existing default artwork holds are preserved; this option does not rescue invalid framing, provider refusals or broad geometry/material failures.
Opt-in metadata participates in both derived and indexed cache identities. Frames without these policies retain their previous cache keys and pixels. Retained-detail opt-ins have a distinct versioned frame, rejected-fallback and prepared-terrain key; absent/empty selections keep the previous identities. The candidate and original-art audit tools validate the same metadata restrictions as the Rust loader.
@@ -63,12 +64,24 @@ RAPTOR_TEST_DATA=/absolute/path/to/data \
That check validates every supplied frame and renders it through `HdAssets::sprite` at scales 1–4 with the original palette. It checks dimensions, visible output and each cache ceiling. Visual review, animation alignment and terrain seams remain separate acceptance checks.
## Retained detail validation — 17 October 2026
## Earlier retained detail validation — 17 October 2026
The optional detail policy passed 70 unique Rust regressions in the private source
validation and 31 Python checks against the applied shared files. Both included
Linux and Windows executables were rebuilt. All 5,339 current HD frames load with
Linux and Windows executables were rebuilt. All 5,339 HD frames supplied at that checkpoint load with
zero rejects. Actual 1080p software-SDL menu, Sector 1 Wave 7 and Sector 3 Wave 9
captures match their pre-change images byte for byte (6,220,800 pixels compared).
These default-mode checks do not approve a new hybrid asset. Explicit retained
Original selectors still require their own whole-frame and scene review.
## Atlas and terrain-join update — 17 October 2026
The rational atlas sampler, protected join inputs and bounded 65–128-cell
retention policy pass 90 Rust tests and 78 Python tests; five existing Rust
integration checks remain ignored. Both bundled executables were rebuilt.
The installed Linux build validates all 5,384 supplied frames with zero rejects,
all 45 mods with one explicit override, and the 6,449-entry original catalog.
Fresh 1080p menu, Sector 1 Wave 7 and Sector 3 Wave 9 software-SDL captures
match the previous build exactly across 6,220,800 presented pixels. These are
bounded regression controls, not evidence for unreviewed artwork or every
possible terrain placement.
+7 -7
View File
@@ -3,7 +3,7 @@
"platform": "Linux x86_64",
"catalog_entries": 6449,
"approved_hd_frames": 5384,
"binary_sha256": "b0e5f424cb60301f34e5e0088a9d00ad10ae609cd70b4e72be560376d952ed83",
"binary_sha256": "499d450e94c1a3d62d94e4002afbc76fcc4fcfd33ec6b84ca99946f58de078d3",
"data_files": 9335,
"data_bytes": 8179315857,
"files": {
@@ -9344,11 +9344,11 @@
"data/ui/WINGAME_SWD": "304630a1223419d69723902ffd22c2c4eea0388491e37dfca663fcbe78e99aff"
},
"system_dependencies": [
"\tlinux-vdso.so.1 (0x00007f7cf6301000)",
"\tlibSDL2-2.0.so.0 => /home/quintine/Projects/RaptorReplica/RustRaptor/lib/libSDL2-2.0.so.0 (0x00007f7cf5400000)",
"\tlibgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f7cf62ad000)",
"\tlibm.so.6 => /usr/lib/libm.so.6 (0x00007f7cf6176000)",
"\tlibc.so.6 => /usr/lib/libc.so.6 (0x00007f7cf5000000)",
"\t/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f7cf6303000)"
"\tlinux-vdso.so.1 (0x00007fe0d25c8000)",
"\tlibSDL2-2.0.so.0 => /home/quintine/Projects/RaptorReplica/RustRaptor/lib/libSDL2-2.0.so.0 (0x00007fe0d1800000)",
"\tlibgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fe0d2574000)",
"\tlibm.so.6 => /usr/lib/libm.so.6 (0x00007fe0d1ac9000)",
"\tlibc.so.6 => /usr/lib/libc.so.6 (0x00007fe0d1400000)",
"\t/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fe0d25ca000)"
]
}
+1 -1
View File
@@ -37,7 +37,7 @@
]
},
"files": {
"bin/raptor-rust.exe": "28826f75f8952ac624f5058161e73d1a7c78a9ecc38f0e95635b327367ffba57",
"bin/raptor-rust.exe": "449ef2ac6d6a117092bdac276776798d238fa6eea8428668c31098e63a476bc7",
"bin/SDL2.dll": "53e8fa7e9ed43c30bd650c39396f0d9d2067d2fff0b926c9433b75202592e4aa",
"bin/SDL2-WINDOWS-LICENSE.txt": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"run.cmd": "b007382d9ed3560191856514a9ebd4e429801fc25428840e304bb297f3553269"
+328 -10
View File
@@ -46,6 +46,15 @@ pub struct Sequence {
pub frame_count: u32,
pub frames: Vec<Frame>,
}
/// Equal rational viewports in an unchanged atlas, not fitted pixel crops.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AtlasGrid {
pub columns: u32,
pub rows: u32,
pub column: u32,
pub row: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Frame {
@@ -60,6 +69,10 @@ pub struct Frame {
/// Optional pixel rectangle [x, y, width, height] in an atlas PNG.
#[serde(default)]
pub crop: Option<[u32; 4]>,
/// Opt-in equal atlas cells; mutually exclusive with a pixel crop.
/// Absent values retain the original sampler, serialization and cache keys.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub atlas_grid: Option<AtlasGrid>,
/// Explicit, independently reviewed gameplay-only source250..255 alpha
/// coverage. Does not assign reserved labels to ordinary source cells.
#[serde(default, skip_serializing_if = "is_false")]
@@ -72,6 +85,8 @@ pub struct Frame {
/// Explicit retained Original detail, never generated HD. Gameplay
/// SourceClasses only, at all1–4 scales; painted Source indices0–255,
/// including Original cycling/fire/blink labels. TRANSPARENT is excluded.
/// Legacy lists<=64 retain their exact behavior. Lists65..128 must retain
/// no more than one quarter of the authentic painted Source cells.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub original_detail_cells: Vec<[u32; 2]>,
}
@@ -417,7 +432,8 @@ impl HdAssets {
.crop
.map(|[_, _, width, height]| (width, height))
.unwrap_or((info.width, info.height));
if !exact_aspect(width, height, frame.logical_width, frame.logical_height) {
if frame.atlas_grid.is_none()
&& !exact_aspect(width, height, frame.logical_width, frame.logical_height) {
// Preserve exact and rounded-opaque identities. Newly eligible
// transparent rounded frames have their own alpha-validation namespace.
if requires_alpha {
@@ -439,6 +455,14 @@ impl HdAssets {
digest.update(value.to_le_bytes());
}
}
if let Some(grid) = frame.atlas_grid {
digest.update(b"raptor-hd-rational-atlas-grid-lanczos3-f32-v1");
for value in [grid.columns, grid.rows, grid.column, grid.row] {
digest.update(value.to_le_bytes());
}
digest.update(info.width.to_le_bytes());
digest.update(info.height.to_le_bytes());
}
// Preserve exact legacy disk/indexed cache keys when both policies are
// absent/default. Every opt-in has a distinct, versioned namespace.
if frame.preserve_reserved_coverage || !frame.native_nearest_cues.is_empty() {
@@ -452,7 +476,7 @@ impl HdAssets {
}
}
if !frame.original_detail_cells.is_empty() {
digest.update(b"raptor-retained-original-details-v2-painted0255");
digest.update(original_detail_policy_tag(frame.original_detail_cells.len(), false));
digest.update((frame.original_detail_cells.len() as u32).to_le_bytes());
for cell in &frame.original_detail_cells {
for coordinate in cell { digest.update(coordinate.to_le_bytes()); }
@@ -492,7 +516,7 @@ impl HdAssets {
return Ok((key, rgba));
}
let mut rgba = if let Some(atlas) = self.atlases.get(&info.digest) {
cropped_frame(atlas, frame.crop)
selected_frame(atlas, &frame, target_width, target_height, requires_alpha)?
} else {
let bytes = bounded_read(&path, MAX_PNG_BYTES)?;
ensure!(
@@ -512,7 +536,7 @@ impl HdAssets {
"HD PNG changed while loading"
);
self.png_decodes += 1;
let rgba = cropped_frame(&atlas, frame.crop);
let rgba = selected_frame(&atlas, &frame, target_width, target_height, requires_alpha)?;
let bytes = atlas.as_raw().len();
self.atlases.insert(info.digest, atlas, bytes);
rgba
@@ -784,7 +808,8 @@ impl HdAssets {
self.sprite_with_policy(id, scale, palette, PalettePolicy::Static)
}
/// Baseline source-class HD plus trusted retention metadata, for terrain.
/// Retained Original samples must be applied only AFTER all neighbor joins.
/// Condition shared join inputs with joined Original samples, then restore
/// exact retained footprints after all neighbor joins.
pub fn terrain_sprite(&mut self, id: u32, scale: usize, palette: &[[u8; 3]; 256])
-> Result<Option<(Sprite, Option<OriginalDetailCells>)>>
{
@@ -807,7 +832,7 @@ impl HdAssets {
if frame.original_detail_cells.is_empty() { return None; }
if let Ok(Some((key, _))) = self.rgba_frame(id) { return Some(key); }
let mut digest = Sha256::new();
digest.update(b"retained-original-rejected-fallback-v2-painted0255");
digest.update(original_detail_policy_tag(frame.original_detail_cells.len(), true));
digest.update(serde_json::to_vec(&frame).ok()?);
Some(digest.finalize().into())
}
@@ -956,6 +981,137 @@ fn cropped_frame(atlas: &RgbaImage, crop: Option<[u32; 4]>) -> RgbaImage {
atlas.clone()
}
}
fn validate_grid_metadata(frame: &Frame) -> Result<()> {
if let Some(grid) = frame.atlas_grid {
ensure!(frame.crop.is_none(), "atlas_grid and crop are mutually exclusive");
ensure!(grid.columns > 0 && grid.rows > 0, "atlas grid axes must be nonzero");
ensure!(grid.columns as u64 * grid.rows as u64 <= MAX_FRAMES as u64,
"atlas grid exceeds cell limit");
ensure!(grid.column < grid.columns && grid.row < grid.rows,
"atlas grid cell is outside its declared axes");
}
Ok(())
}
#[derive(Clone, Copy)]
struct GridViewport {
left: f64,
top: f64,
right: f64,
bottom: f64,
// Bounding raw pixels with positive area inside this viewport. Fractional
// boundary pixels remain shared; no strips are dropped or pixels rewritten.
pixels: [u32; 4],
resident: [u32; 2],
}
fn grid_viewport(frame: &Frame, width: u32, height: u32) -> Result<GridViewport> {
validate_grid_metadata(frame)?;
let grid = frame.atlas_grid.context("missing atlas grid")?;
ensure!(width > 0 && height > 0 && frame.logical_width > 0 && frame.logical_height > 0,
"invalid atlas or logical dimensions");
// All eligibility arithmetic is exact. Only the resampling kernel converts
// these rational bounds to floating point; there is no integer crop fitting.
ensure!(width as u128 * frame.logical_height as u128 * grid.rows as u128
== height as u128 * frame.logical_width as u128 * grid.columns as u128,
"rational atlas cell aspect differs from original canvas");
let x_den = grid.columns as u64 * frame.logical_width as u64;
let y_den = grid.rows as u64 * frame.logical_height as u64;
ensure!(width as u64 >= 2 * x_den && height as u64 >= 2 * y_den,
"rational atlas cell needs at least twice the logical resolution");
let factor = (width as u64 / x_den).min(height as u64 / y_den).min(4) as u32;
let x0 = width as u64 * grid.column as u64;
let x1 = width as u64 * (grid.column as u64 + 1);
let y0 = height as u64 * grid.row as u64;
let y1 = height as u64 * (grid.row as u64 + 1);
Ok(GridViewport {
left: x0 as f64 / grid.columns as f64,
right: x1 as f64 / grid.columns as f64,
top: y0 as f64 / grid.rows as f64,
bottom: y1 as f64 / grid.rows as f64,
pixels: [(x0 / grid.columns as u64) as u32,
(y0 / grid.rows as u64) as u32,
x1.div_ceil(grid.columns as u64) as u32,
y1.div_ceil(grid.rows as u64) as u32],
resident: [frame.logical_width * factor, frame.logical_height * factor],
})
}
fn grid_lanczos(value: f64) -> f64 {
if value == 0.0 { return 1.0; }
if value.abs() >= 3.0 { return 0.0; }
let value = value * std::f64::consts::PI;
(value.sin() / value) * ((value / 3.0).sin() / (value / 3.0))
}
fn grid_axis_weights(start: f64, end: f64, output: u32) -> Result<Vec<Vec<(u32, f32)>>> {
let scale = ((end - start) / output as f64).max(1.0);
(0..output).map(|destination| {
let center = start + (destination as f64 + 0.5) * (end - start) / output as f64;
let lower = (center - 3.0 * scale).floor().max(start.floor()) as u32;
let upper = (center + 3.0 * scale).ceil().min(end.ceil()) as u32;
let mut weights: Vec<(u32, f64)> = (lower..upper).filter_map(|index| {
let overlap = ((index as f64 + 1.0).min(end) - (index as f64).max(start)).max(0.0);
let weight = grid_lanczos((index as f64 + 0.5 - center) / scale) * overlap;
(weight != 0.0).then_some((index, weight))
}).collect();
let total: f64 = weights.iter().map(|(_, value)| *value).sum();
// Positive-size validated viewports and center-supported Lanczos always
// have a nonzero sum; keep this deterministic precondition explicit.
ensure!(total.is_finite() && total.abs() > 1e-12, "invalid rational atlas filter weights");
Ok(weights.drain(..).map(|(index, weight)| (index, (weight / total) as f32)).collect())
}).collect()
}
fn selected_frame(atlas: &RgbaImage, frame: &Frame, width: u32, height: u32,
requires_alpha: bool) -> Result<RgbaImage> {
if frame.atlas_grid.is_none() { return Ok(cropped_frame(atlas, frame.crop)); }
let viewport = grid_viewport(frame, atlas.width(), atlas.height())?;
ensure!(viewport.resident == [width, height], "rational viewport resident mismatch");
let [left, top, right, bottom] = viewport.pixels;
if viewport.left.fract() == 0.0 && viewport.top.fract() == 0.0
&& viewport.right.fract() == 0.0 && viewport.bottom.fract() == 0.0 {
// Integral cells keep the existing crop/resize implementation. In
// particular a 1x1 opt-in never allocates a new floating-point canvas.
return Ok(cropped_frame(atlas, Some([left, top, right-left, bottom-top])));
}
let mut transparent = false;
let mut visible = false;
for y in top..bottom {
for x in left..right {
let alpha = atlas.get_pixel(x, y)[3];
transparent |= alpha == 0;
visible |= alpha >= 128;
}
}
ensure!(!requires_alpha || transparent, "HD frame needs a transparent background matching its original");
ensure!(visible, "HD frame has no visible pixels");
let x_weights = grid_axis_weights(viewport.left, viewport.right, width)?;
let y_weights = grid_axis_weights(viewport.top, viewport.bottom, height)?;
// A separable in-memory sampler; no export/padding/alpha repair. Only this
// cell's raw pixel areas participate, including fractional boundary areas.
let rows = (bottom - top) as usize;
ensure!(width as usize * rows <= MAX_PNG_PIXELS as usize,
"rational atlas temporary canvas exceeds limit");
let mut horizontal = vec![[0.0f32; 4]; width as usize * rows];
for y in top..bottom {
for (x, weights) in x_weights.iter().enumerate() {
let sample = &mut horizontal[(y - top) as usize * width as usize + x];
for &(source_x, weight) in weights {
let pixel = atlas.get_pixel(source_x, y);
for channel in 0..4 { sample[channel] += pixel[channel] as f32 * weight; }
}
}
}
let mut output = RgbaImage::new(width, height);
for (y, weights) in y_weights.iter().enumerate() {
for x in 0..width as usize {
let mut sample = [0.0f32; 4];
for &(source_y, weight) in weights {
let pixel = horizontal[(source_y - top) as usize * width as usize + x];
for channel in 0..4 { sample[channel] += pixel[channel] * weight; }
}
output.put_pixel(x as u32, y as u32,
image::Rgba(sample.map(|value| value.clamp(0.0, 255.0).round() as u8)));
}
}
Ok(output)
}
fn image_limits() -> image::Limits {
let mut limits = image::Limits::default();
limits.max_image_width = Some(4096);
@@ -984,6 +1140,10 @@ fn integer_rounded_aspect(
}
// Dimension eligibility is independent of alpha; rgba_frame checks source-aware alpha.
fn frame_dimensions(frame: &Frame, png: &PngInfo, _requires_alpha: bool) -> Result<(u32, u32)> {
if frame.atlas_grid.is_some() {
let viewport = grid_viewport(frame, png.width, png.height)?;
return Ok((viewport.resident[0], viewport.resident[1]));
}
let (width, height) = if let Some([x, y, width, height]) = frame.crop {
ensure!(
width > 0
@@ -1034,6 +1194,7 @@ fn validate_rgba(rgba: &RgbaImage, requires_alpha: bool) -> Result<()> {
Ok(())
}
fn validate_frame(frame: &Frame, original: &Sprite, source: &[u8]) -> Result<()> {
validate_grid_metadata(frame)?;
ensure!(
frame.logical_width as usize == original.width
&& frame.logical_height as usize == original.height,
@@ -1063,7 +1224,8 @@ fn validate_frame(frame: &Frame, original: &Sprite, source: &[u8]) -> Result<()>
"native nearest cue must be an ordinary original source cell"
);
}
ensure!(frame.original_detail_cells.len() <= 64, "Original detail list exceeds64 cells");
ensure!(original_detail_bound(&frame.original_detail_cells, original),
"Original details need<=64 legacy cells or65..128 cells within one quarter of painted Source");
ensure!(frame.original_detail_cells.windows(2).all(|w| w[0] < w[1]), "Original details sorted and unique");
for &[x, y] in &frame.original_detail_cells {
ensure!(x < frame.logical_width && y < frame.logical_height, "Original detail outside logical canvas");
@@ -1082,6 +1244,22 @@ fn validate_frame(frame: &Frame, original: &Sprite, source: &[u8]) -> Result<()>
);
Ok(())
}
// Count decoded painted Source indices, not canvas area or generated PNG alpha.
// Legacy lists keep all prior validation/sampling/cache behavior. The caller
// still validates coordinates, order, uniqueness, painted membership and cues.
fn original_detail_bound(cells: &[[u32; 2]], original: &Sprite) -> bool {
let count = cells.len();
count <= 64 || (count <= 128
&& count * 4 <= original.pixels.iter().filter(|&&p| p <= 255).count())
}
fn original_detail_policy_tag(count: usize, fallback: bool) -> &'static [u8] {
match (count <= 64, fallback) {
(true, false) => b"raptor-retained-original-details-v2-painted0255",
(true, true) => b"retained-original-rejected-fallback-v2-painted0255",
(false, false) => b"raptor-retained-original-details-v3-max128-quarter-painted0255",
(false, true) => b"retained-original-rejected-fallback-v3-max128-quarter-painted0255",
}
}
fn source_reference_budget(labels: usize, details: usize, new_labels: usize, new_details: usize) -> bool {
labels.checked_add(details).and_then(|n| n.checked_add(new_labels))
.and_then(|n| n.checked_add(new_details)).is_some_and(|n| n <= MAX_SOURCE_LABEL_BYTES)
@@ -1137,7 +1315,7 @@ mod tests {
logical_height: 2,
origin: [0, 0],
source_sha256: format!("{:x}", Sha256::digest(source)),
crop: None,
crop: None, atlas_grid: None,
preserve_reserved_coverage: false,
native_nearest_cues: Vec::new(),
original_detail_cells: Vec::new(),
@@ -2283,7 +2461,7 @@ mod policy_proposal_tests {
logical_height: 2,
origin: [0, 0],
source_sha256: "00".into(),
crop: None,
crop: None, atlas_grid: None,
preserve_reserved_coverage: false,
native_nearest_cues: Vec::new(),
original_detail_cells: Vec::new(),
@@ -2610,7 +2788,7 @@ mod retained_original_detail_tests {
fn source() -> Sprite { Sprite { width: 2, height: 2, pixels: vec![19,20,18,18] } }
fn frame() -> Frame { Frame { frame: 0, file: "raw.png".into(), logical_width: 2,
logical_height: 2, origin: [0,0], source_sha256: format!("{:x}",Sha256::digest(b"source")),
crop: None, preserve_reserved_coverage: false, native_nearest_cues: Vec::new(),
crop: None, atlas_grid: None, preserve_reserved_coverage: false, native_nearest_cues: Vec::new(),
original_detail_cells: Vec::new() } }
#[test]
fn typed_serialization_defaults_and_source_constraints() {
@@ -2815,3 +2993,143 @@ mod retained_original_detail_tests {
assert_ne!(HdAssets::frame_key(&selected,&info,false),ordinary_only_v1_frame_key(&selected,&info,false));
}
}
#[cfg(test)]
mod rational_atlas_grid_tests {
use super::*;
fn frame() -> Frame {
Frame { frame: 0, file: "synthetic.png".into(), logical_width: 1,
logical_height: 1, origin: [0, 0], source_sha256: "00".repeat(32),
crop: None, atlas_grid: Some(AtlasGrid { columns: 2, rows: 2, column: 0, row: 0 }),
preserve_reserved_coverage: false, native_nearest_cues: Vec::new(),
original_detail_cells: Vec::new() }
}
fn info() -> PngInfo {
PngInfo { size: 1, modified: None, digest: [1; 32], width: 17, height: 17 }
}
#[test]
fn rational_cells_retain_exact_fractional_bounds_and_last_pixel() {
let mut f = frame(); f.logical_width=32; f.logical_height=32;
f.atlas_grid=Some(AtlasGrid { columns:4,rows:2,column:3,row:1 });
let view=grid_viewport(&f,1774,887).unwrap();
assert_eq!((view.left,view.top,view.right,view.bottom),(1330.5,443.5,1774.0,887.0));
assert_eq!(view.pixels,[1330,443,1774,887]);
assert_eq!(view.resident,[128,128]);
let weights=grid_axis_weights(view.left,view.right,128).unwrap();
assert!(weights.iter().flatten().all(|(x,_)|*x>=1330&&*x<1774));
}
#[test]
fn invalid_axes_indices_products_aspects_and_crop_are_rejected() {
for grid in [
AtlasGrid{columns:0,rows:2,column:0,row:0},
AtlasGrid{columns:2,rows:0,column:0,row:0},
AtlasGrid{columns:129,rows:129,column:0,row:0},
AtlasGrid{columns:2,rows:2,column:2,row:0},
AtlasGrid{columns:2,rows:2,column:0,row:2},
] { let mut f=frame(); f.atlas_grid=Some(grid);assert!(grid_viewport(&f,5,5).is_err()); }
let mut f=frame();f.crop=Some([0,0,2,2]);assert!(grid_viewport(&f,5,5).is_err());
assert!(grid_viewport(&frame(),5,6).is_err());
assert!(grid_viewport(&frame(),3,3).is_err());
}
#[test]
fn absent_grid_adds_no_serialized_field_and_null_stays_absent() {
let mut f=frame();f.atlas_grid=None;
let serialized=serde_json::to_value(&f).unwrap();
assert!(serialized.get("atlas_grid").is_none());
let mut explicit=serialized.clone();explicit["atlas_grid"]=serde_json::Value::Null;
let decoded:Frame=serde_json::from_value(explicit).unwrap();
assert_eq!(serde_json::to_value(decoded).unwrap(),serialized);
}
#[test]
fn grid_json_rejects_unknown_boolean_negative_and_float_fields() {
for value in [
r#"{"columns":2,"rows":2,"column":0,"row":0,"extra":1}"#,
r#"{"columns":true,"rows":2,"column":0,"row":0}"#,
r#"{"columns":2,"rows":2,"column":-1,"row":0}"#,
r#"{"columns":2,"rows":2,"column":0.0,"row":0}"#,
] { assert!(serde_json::from_str::<AtlasGrid>(value).is_err()); }
}
#[test]
fn grid_cache_identity_binds_layout_cell_source_png_and_kernel_namespace() {
let mut f=frame();let mut metadata=info();
let mut keys=std::collections::BTreeSet::new();
for row in 0..2 {for column in 0..2 {
f.atlas_grid=Some(AtlasGrid{columns:2,rows:2,column,row});
assert!(keys.insert(HdAssets::frame_key(&f,&metadata,false)));
}}
f.atlas_grid=Some(AtlasGrid{columns:4,rows:4,column:0,row:0});
assert!(keys.insert(HdAssets::frame_key(&f,&metadata,false)));
f.atlas_grid=None;assert!(keys.insert(HdAssets::frame_key(&f,&metadata,false)));
f.atlas_grid=Some(AtlasGrid{columns:2,rows:2,column:0,row:0});
metadata.digest=[2;32];assert!(keys.insert(HdAssets::frame_key(&f,&metadata,false)));
f.source_sha256="11".repeat(32);assert!(keys.insert(HdAssets::frame_key(&f,&metadata,false)));
}
#[test]
fn constant_fractional_cell_is_constant_without_neighbor_invention() {
let f=frame();let raw=RgbaImage::from_pixel(5,5,image::Rgba([40,80,120,255]));
let old=raw.as_raw().clone();
let selected=selected_frame(&raw,&f,2,2,false).unwrap();
assert!(selected.pixels().all(|p|p.0==[40,80,120,255]));
assert_eq!(*raw.as_raw(),old);
}
#[test]
fn fractional_boundary_pixel_is_shared_but_wholly_neighboring_pixels_are_not() {
let raw=RgbaImage::from_fn(5,5,|x,_|image::Rgba(
if x<2 {[255,0,0,255]} else if x==2 {[0,255,0,255]} else {[0,0,255,255]}));
let left=selected_frame(&raw,&frame(),2,2,false).unwrap();
assert!(left.pixels().all(|p|p[2]==0));
let mut f=frame();f.atlas_grid.as_mut().unwrap().column=1;
let right=selected_frame(&raw,&f,2,2,false).unwrap();
assert!(right.pixels().all(|p|p[0]==0));
let a=grid_axis_weights(0.0,2.5,2).unwrap();let b=grid_axis_weights(2.5,5.0,2).unwrap();
assert!(a.iter().flatten().any(|(x,w)|*x==2&&*w!=0.0));
assert!(b.iter().flatten().any(|(x,w)|*x==2&&*w!=0.0));
}
#[test]
fn integral_selection_uses_original_crop_without_an_extra_sampler() {
let f=frame();let raw=RgbaImage::from_fn(8,8,|x,y|image::Rgba([x as u8,y as u8,0,255]));
assert_eq!(selected_frame(&raw,&f,4,4,false).unwrap(),
imageops::crop_imm(&raw,0,0,4,4).to_image());
}
#[test]
fn raw_alpha_validation_is_before_fractional_filtering() {
let opaque=RgbaImage::from_pixel(5,5,image::Rgba([1,2,3,255]));
assert!(selected_frame(&opaque,&frame(),2,2,true).is_err());
let hidden=RgbaImage::new(5,5);
assert!(selected_frame(&hidden,&frame(),2,2,true).is_err());
}
}
#[cfg(test)]
mod extended_retained_original_bound_tests {
use super::*;
fn source(painted: usize, canvas: usize) -> Sprite {
let mut pixels = vec![TRANSPARENT; canvas];
for (i, p) in pixels.iter_mut().take(painted).enumerate() { *p = (i % 256) as u16; }
Sprite { width: canvas, height: 1, pixels }
}
#[test]
fn exact_quarter_edges_and_hard_maximum() {
for (n, painted, expected) in [(64, 0, true), (65, 260, true),
(65, 259, false), (73, 625, true), (128, 512, true),
(128, 511, false), (129, 4096, false)] {
assert_eq!(original_detail_bound(&vec![[0,0]; n], &source(painted,4096)), expected);
}
}
#[test]
fn transparent_canvas_never_inflates_fraction() {
assert!(!original_detail_bound(&vec![[0,0];65], &source(259,4096)));
assert!(original_detail_bound(&vec![[0,0];65], &source(260,4096)));
}
#[test]
fn legacy_tags_remain_exact_and_extended_namespaces_are_new() {
for n in [0,1,64] {
assert_eq!(original_detail_policy_tag(n,false), b"raptor-retained-original-details-v2-painted0255");
assert_eq!(original_detail_policy_tag(n,true), b"retained-original-rejected-fallback-v2-painted0255");
}
assert_ne!(original_detail_policy_tag(65,false),original_detail_policy_tag(64,false));
assert_ne!(original_detail_policy_tag(65,true),original_detail_policy_tag(64,true));
assert_eq!(original_detail_policy_tag(65,false),original_detail_policy_tag(128,false));
}
}
+234 -11
View File
@@ -127,8 +127,8 @@ impl TerrainHd {
) -> Composition {
self.compose_retaining_original(context, replacements, &[], scale, palette)
}
/// Original detail is explicit metadata and is patched only into the FINAL
/// layer. Pristine HD edge inputs and all unprotected samples stay unchanged.
/// Original detail is explicit metadata. Condition the shared join inputs
/// before edges/corners, then restore exact Original footprints in the final layer.
pub fn compose_retaining_original(&mut self, context: &Sprite,
replacements: &[Replacement], retained: &[RetainedOriginalPlacement],
scale: usize, palette: &[[u8; 3]; 256]) -> Composition {
@@ -170,8 +170,8 @@ impl TerrainHd {
return Composition { image: context.clone(), results: vec![OverlayResult::InvalidOriginalDetails; replacements.len()] };
}
self.prepare_palette(palette);
// Keep a pristine joined HD context. Every edge reads this same layer,
// so the result cannot depend on the overlay order or previous blending.
// Build one joined detailed input. Every edge reads this same layer,
// so the result cannot depend on overlay order or previous blending.
let mut detailed = context.clone();
for (index, tile) in replacements.iter().enumerate() {
if accepted.get(&(tile.position, tile.logical_size)) != Some(&index) {
@@ -213,6 +213,9 @@ impl TerrainHd {
}
}
}
// Protected generated pixels must not tint any neighboring join.
// The map is already validated/clipped; empty retention is a no-op.
for pixel in protected.keys() { detailed.pixels[*pixel] = context.pixels[*pixel]; }
image.pixels.copy_from_slice(&detailed.pixels);
for (index, tile) in replacements.iter().enumerate() {
if accepted.get(&(tile.position, tile.logical_size)) != Some(&index) {
@@ -293,8 +296,8 @@ impl TerrainHd {
}
}
}
// AFTER all tile edges/corners: copy authentic joined Original context.
// These samples were never read by shared_target/tile_edges.
// AFTER all tile edges/corners, reassert the exact Original footprint.
// Joining may blend these positions even though its inputs were conditioned.
for pixel in protected.keys() { image.pixels[*pixel] = context.pixels[*pixel]; }
Composition { image, results }
}
@@ -399,7 +402,7 @@ impl TerrainHd {
} else {
tile.image.width
};
// Pair pristine detail at each mirrored distance across the join.
// Pair conditioned detail at each mirrored distance across the join.
// Repeating only the outer edge's sample throughout this band
// stretches fine grain into ribs even when the source art joins.
(0..length * band)
@@ -658,17 +661,19 @@ mod retained_original_join_tests {
}
fn palette()->[[u8;3];256] {std::array::from_fn(|i|[i as u8;3])}
#[test]
fn edge_protection_is_after_all_joins_no_neighbor_leak_at_any_scale() {
fn protected_edges_condition_join_inputs_then_restore_exact_original_all_scales() {
for scale in 1..=4 {
let context=Sprite {width:64*scale,height:32*scale,pixels:vec![17;64*32*scale*scale]};
let tiles=vec![Replacement{image:Sprite{width:32*scale,height:32*scale,pixels:vec![60;32*32*scale*scale]},position:[0,0],logical_size:[32,32]},
Replacement{image:Sprite{width:32*scale,height:32*scale,pixels:vec![80;32*32*scale*scale]},position:[32,0],logical_size:[32,32]}];
let mut t=TerrainHd::default();let base=t.compose(&context,&tiles,scale,&palette()).image;
let reference=super::protected_join_input_regression_tests::reference(&context,&tiles,&[detail(0,vec![[31,16]])],scale,&palette());
let result=t.compose_retaining_original(&context,&tiles,&[detail(0,vec![[31,16]])],scale,&palette());
assert!(result.results.iter().all(|r|*r==OverlayResult::Applied));
for y in 0..context.height {for x in 0..context.width {
let i=y*context.width+x;let protected=x/scale==31&&y/scale==16;
assert_eq!(result.image.pixels[i],if protected{context.pixels[i]}else{base.pixels[i]});
assert_eq!(result.image.pixels[i],reference.pixels[i]);
if protected { assert_eq!(result.image.pixels[i],context.pixels[i]); }
}}
let empty=t.compose_retaining_original(&context,&tiles,&[],scale,&palette()).image;
assert_eq!(base,empty);
@@ -693,7 +698,7 @@ mod retained_original_join_tests {
let context=crate::render::upscale(&logical,scale).unwrap();
let hd=vec![Replacement{image:Sprite{width:32*scale,height:32*scale,pixels:vec![60;32*32*scale*scale]},position:[0,0],logical_size:[32,32]},Replacement{image:Sprite{width:32*scale,height:32*scale,pixels:vec![80;32*32*scale*scale]},position:[32,0],logical_size:[32,32]}];
let mut compositor=TerrainHd::default();
let baseline=compositor.compose(&context,&hd,scale,&palette()).image;
let reference=super::protected_join_input_regression_tests::reference(&context,&hd,&[detail(0,vec![[31,15],[31,16]])],scale,&palette());
let hybrid=compositor.compose_retaining_original(&context,&hd,&[detail(0,vec![[31,15],[31,16]])],scale,&palette()).image;
let mut phase=palette();phase[252]=[255,0,0];phase[253]=[0,255,0];
let mut phase2=phase;phase2[252]=[0,0,0];phase2[253]=[255,255,255];
@@ -701,7 +706,8 @@ mod retained_original_join_tests {
let rgba2=crate::render::Canvas{image:hybrid.clone(),scale}.rgba(&phase2,false);
for y in 0..context.height {for x in 0..context.width {
let i=y*context.width+x;let protected=x/scale==31&&(y/scale==15||y/scale==16);
assert_eq!(hybrid.pixels[i],if protected{context.pixels[i]}else{baseline.pixels[i]});
assert_eq!(hybrid.pixels[i],reference.pixels[i]);
if protected { assert_eq!(hybrid.pixels[i],context.pixels[i]); }
if protected&&(hybrid.pixels[i]==252||hybrid.pixels[i]==253){assert_ne!(&rgba1[i*4..i*4+3],&rgba2[i*4..i*4+3]);}
}}
let mixed=&hd[..1];let base_mixed=compositor.compose(&context,mixed,scale,&palette()).image;
@@ -712,3 +718,220 @@ mod retained_original_join_tests {
}
}
}
#[cfg(test)]
mod protected_join_input_regression_tests {
use super::*;
fn palette() -> [[u8; 3]; 256] {
let mut p = std::array::from_fn(|i| [i as u8; 3]);
p[150] = [225, 180, 0];
p[151] = [0, 0, 255];
p
}
fn tile(position: [i32; 2], value: u16, scale: usize) -> Replacement {
Replacement { position, logical_size: [32, 32], image: Sprite {
width: 32 * scale, height: 32 * scale, pixels: vec![value; 1024 * scale * scale],
}}
}
fn detail(index: usize, cells: Vec<[u32; 2]>) -> RetainedOriginalPlacement {
RetainedOriginalPlacement { replacement_index: index,
details: crate::hd::OriginalDetailCells::for_test(cells, [32, 32]) }
}
fn cloned(tiles: &[Replacement]) -> Vec<Replacement> {
tiles.iter().map(|t| Replacement { position: t.position,
logical_size: t.logical_size, image: t.image.clone() }).collect()
}
fn paint_cells(tile: &mut Replacement, cells: &[[u32; 2]], value: u16, scale: usize) {
for &[x, y] in cells { for dy in 0..scale { for dx in 0..scale {
let i = (y as usize * scale + dy) * tile.image.width + x as usize * scale + dx;
tile.image.pixels[i] = value;
}}}
}
// Explicit synthetic counterfactual: condition only the listed input samples,
// use the unchanged no-policy compositor, then restore the listed footprints.
// This does not call compose_retaining_original to derive its own expected output.
pub(super) fn reference(context: &Sprite, tiles: &[Replacement],
retained: &[RetainedOriginalPlacement], scale: usize, p: &[[u8; 3]; 256]) -> Sprite {
let mut conditioned = cloned(tiles);
let mut positions = Vec::new();
for retention in retained {
let t = &mut conditioned[retention.replacement_index];
for &[x, y] in retention.details.cells() { for dy in 0..scale { for dx in 0..scale {
let xx = (t.position[0] as i64 + x as i64) * scale as i64 + dx as i64;
let yy = (t.position[1] as i64 + y as i64) * scale as i64 + dy as i64;
if xx < 0 || yy < 0 || xx >= context.width as i64 || yy >= context.height as i64 { continue; }
let dst = yy as usize * context.width + xx as usize;
let src = (y as usize * scale + dy) * t.image.width + x as usize * scale + dx;
t.image.pixels[src] = context.pixels[dst];
positions.push(dst);
}}}
}
let mut image = TerrainHd::default().compose(context, &conditioned, scale, p).image;
for i in positions { image.pixels[i] = context.pixels[i]; }
image
}
// Unprotected synthetic counterfactual: empty retention intentionally leaves
// the generated border in the join inputs. This is not an old-library call.
fn unprotected_composition(context: &Sprite, tiles: &[Replacement], scale: usize,
p: &[[u8; 3]; 256]) -> Sprite {
TerrainHd::default().compose(context, tiles, scale, p).image
}
#[test]
fn protected_wrong_color_cannot_tint_any_cardinal_neighbor_all_scales_and_orders() {
for scale in 1..=4 { for side in 0..4 {
let (first, second, width, height, cells): (_, _, _, _, Vec<[u32; 2]>) = match side {
0 => ([0, 0], [32, 0], 64, 32, (0..32).map(|y| [31, y]).collect()),
1 => ([32, 0], [0, 0], 64, 32, (0..32).map(|y| [0, y]).collect()),
2 => ([0, 0], [0, 32], 32, 64, (0..32).map(|x| [x, 31]).collect()),
_ => ([0, 32], [0, 0], 32, 64, (0..32).map(|x| [x, 0]).collect()),
};
let context = Sprite { width: width * scale, height: height * scale,
pixels: vec![17; width * height * scale * scale] };
let mut gold = vec![tile(first, 60, scale), tile(second, 80, scale)];
paint_cells(&mut gold[0], &cells, 150, scale);
let mut blue = cloned(&gold); paint_cells(&mut blue[0], &cells, 151, scale);
let unprotected_gold = unprotected_composition(&context, &gold, scale, &palette());
let unprotected_blue = unprotected_composition(&context, &blue, scale, &palette());
let neighbor_positions: Vec<_> = (0..32*scale).flat_map(|y| (0..32*scale).map(move |x|
(second[1] as usize * scale + y) * context.width + second[0] as usize * scale + x)).collect();
assert!(neighbor_positions.iter().any(|&i| unprotected_gold.pixels[i] != unprotected_blue.pixels[i]),
"fixture must reproduce unprotected raw influence on neighbor, scale={scale} side={side}");
let expected = reference(&context, &gold, &[detail(0, cells.clone())], scale, &palette());
for reverse in [false, true] {
let mut a = cloned(&gold); let mut b = cloned(&blue);
if reverse { a.reverse(); b.reverse(); }
let owner = usize::from(reverse);
let result_a = TerrainHd::default().compose_retaining_original(&context, &a,
&[detail(owner, cells.clone())], scale, &palette());
let result_b = TerrainHd::default().compose_retaining_original(&context, &b,
&[detail(owner, cells.clone())], scale, &palette());
assert!(result_a.results.iter().all(|r| *r == OverlayResult::Applied));
assert_eq!(result_a.image, expected);
assert_eq!(result_b.image, expected);
// All remote pixels stay exact: no protected input can reach beyond
// the existing two-logical-pixel mirrored join band.
for y in 0..context.height { for x in 0..context.width {
let axis = if side < 2 { x } else { y };
if axis.abs_diff(32 * scale) > 2 * scale {
let i = y * context.width + x;
assert_eq!(result_a.image.pixels[i], unprotected_gold.pixels[i]);
}
}}
}
}}
}
#[test]
fn protected_corner_is_conditioned_for_all_twenty_four_orders_at_every_scale() {
for scale in 1..=4 {
let context = Sprite { width:64*scale, height:64*scale, pixels:vec![17;4096*scale*scale] };
let mut original = vec![tile([0,0],60,scale), tile([32,0],80,scale),
tile([0,32],100,scale), tile([32,32],120,scale)];
paint_cells(&mut original[0], &[[31,31]], 150, scale);
let expected = reference(&context,&original,&[detail(0,vec![[31,31]])],scale,&palette());
for a in 0..4 { for b in 0..4 { for c in 0..4 { for d in 0..4 {
let order=[a,b,c,d];
if order.iter().copied().collect::<std::collections::BTreeSet<_>>().len()!=4 {continue;}
let owner=order.iter().position(|&i|i==0).unwrap();
let mut tiles:Vec<_>=order.iter().map(|&i|cloned(&original[i..i+1]).remove(0)).collect();
let gold=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(owner,vec![[31,31]])],scale,&palette());
paint_cells(&mut tiles[owner],&[[31,31]],151,scale);
let blue=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(owner,vec![[31,31]])],scale,&palette());
assert_eq!(gold.image,expected); assert_eq!(blue.image,expected);
}}}}
}
}
#[test]
fn absent_empty_details_match_unprotected_route_for_classes_and_orders() {
let mut logical=Sprite{width:64,height:32,pixels:vec![17;2048]};
for y in 0..32 {for x in 0..64 {
logical.pixels[y*64+x]=match (x/8+y/8)%6 {0=>17,1=>243,2=>247,3=>252,4=>255,_=>TRANSPARENT};
}}
for scale in 1..=4 {
let context=crate::render::upscale(&logical,scale).unwrap();
let mut tiles=vec![tile([0,0],60,scale),tile([32,0],80,scale)];
for reverse in [false,true] {
if reverse {tiles.reverse();}
let unprotected=unprotected_composition(&context,&tiles,scale,&palette());
let mut compositor=TerrainHd::default();
assert_eq!(compositor.compose(&context,&tiles,scale,&palette()).image,unprotected);
assert_eq!(compositor.compose_retaining_original(&context,&tiles,&[],scale,&palette()).image,unprotected);
assert_eq!(compositor.compose_retaining_original(&context,&tiles,
&[detail(0,Vec::new())],scale,&palette()).image,unprotected);
}
}
}
#[test]
fn protected_source_classes_remain_exact_and_original_neighbor_fallback_is_untouched() {
let mut logical=Sprite{width:64,height:32,pixels:vec![17;2048]};
let cells=vec![[31,12],[31,13],[31,14],[31,15],[31,16]];
for (cell,value) in cells.iter().zip([252,253,243,247,19]) {
logical.pixels[cell[1] as usize*64+31]=value;
}
logical.pixels[11*64+32]=TRANSPARENT;
logical.pixels[14*64+32]=244; logical.pixels[15*64+32]=249;
for scale in 1..=4 {
let context=crate::render::upscale(&logical,scale).unwrap();
let mut tiles=vec![tile([0,0],60,scale),tile([32,0],80,scale)];
paint_cells(&mut tiles[0],&cells,150,scale);
let expected=reference(&context,&tiles,&[detail(0,cells.clone())],scale,&palette());
let result=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(0,cells.clone())],scale,&palette());
assert_eq!(result.image,expected);
for (&source,&output) in context.pixels.iter().zip(&result.image.pixels) {
assert!(match source {0..=239=>output<240,240..=244=>(240..=244).contains(&output),
245..=249=>(245..=249).contains(&output),250..=255=>output==source,_=>output==TRANSPARENT});
}
let only=&tiles[..1];
let fallback=TerrainHd::default().compose_retaining_original(&context,only,
&[detail(0,cells.clone())],scale,&palette());
for y in 0..context.height {for x in 32*scale..context.width {
let i=y*context.width+x;assert_eq!(fallback.image.pixels[i],context.pixels[i]);
}}
tiles[1].image.pixels[0]=TRANSPARENT;
let rejected=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(0,cells.clone()),detail(1,vec![[0,0]])],scale,&palette());
assert_eq!(rejected.results[1],OverlayResult::NonOpaque);
assert_eq!(rejected.image,fallback.image);
}
}
#[test]
fn clipped_protected_footprints_never_escape_visible_join() {
for scale in 1..=4 {
let context=Sprite{width:48*scale,height:32*scale,pixels:vec![17;48*32*scale*scale]};
let mut tiles=vec![tile([-16,0],60,scale),tile([16,0],80,scale)];
let cells=vec![[0,16],[31,16]];paint_cells(&mut tiles[0],&cells,150,scale);
let expected=reference(&context,&tiles,&[detail(0,cells.clone())],scale,&palette());
let result=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(0,cells.clone())],scale,&palette());
assert_eq!(result.image,expected);
paint_cells(&mut tiles[0],&cells,151,scale);
let changed=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(0,cells)],scale,&palette());
assert_eq!(changed.image,result.image);
}
}
#[test]
fn invalid_protection_fails_before_any_join_at_all_scales() {
for scale in 1..=4 {
let context=Sprite{width:64*scale,height:32*scale,pixels:vec![17;2048*scale*scale]};
let tiles=vec![tile([0,0],150,scale),tile([1,0],151,scale)];
for bad in [vec![detail(2,vec![[0,0]])],vec![detail(0,vec![[32,0]])],
vec![detail(0,vec![[1,0]]),detail(1,vec![[0,0]])],
vec![RetainedOriginalPlacement{replacement_index:0,
details:crate::hd::OriginalDetailCells::for_test(vec![[0,0]],[31,32])}]] {
let result=TerrainHd::default().compose_retaining_original(&context,&tiles,&bad,scale,&palette());
assert_eq!(result.image,context);
assert!(result.results.iter().all(|r|*r==OverlayResult::InvalidOriginalDetails));
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
"""Synthetic-only scalar model of the proposed opt-in kernel; no game/art IO."""
import math
import struct
def f32(value):
return struct.unpack('<f', struct.pack('<f', value))[0]
def lanczos(value):
if value == 0:
return 1.0
if abs(value) >= 3:
return 0.0
value *= math.pi
return math.sin(value) / value * (math.sin(value / 3) / (value / 3))
def axis_weights(start, end, output):
start, end = float(start), float(end)
scale = max((end - start) / output, 1.0)
rows = []
for destination in range(output):
center = start + (destination + 0.5) * (end - start) / output
lower = max(math.floor(center - 3 * scale), math.floor(start))
upper = min(math.ceil(center + 3 * scale), math.ceil(end))
weights = []
for index in range(lower, upper):
overlap = max(0.0, min(index + 1, end) - max(index, start))
weight = lanczos((index + 0.5 - center) / scale) * overlap
if weight:
weights.append((index, weight))
total = sum(value for _, value in weights)
if not math.isfinite(total) or abs(total) <= 1e-12:
raise ValueError('invalid rational atlas filter weights')
rows.append([(index, f32(value / total)) for index, value in weights])
return rows
def sample_fractional(pixels, geometry):
left, top, right, bottom = geometry['bounds']
width, height = geometry['resident']
_, first_y, _, last_y = geometry['pixels']
x_weights = axis_weights(left, right, width)
y_weights = axis_weights(top, bottom, height)
horizontal = []
for source_y in range(first_y, last_y):
row = []
for weights in x_weights:
sample = [0.0] * 4
for source_x, weight in weights:
for channel in range(4):
sample[channel] = f32(sample[channel] + f32(pixels[source_y][source_x][channel] * weight))
row.append(sample)
horizontal.append(row)
result = []
for weights in y_weights:
row = []
for x in range(width):
sample = [0.0] * 4
for source_y, weight in weights:
for channel in range(4):
sample[channel] = f32(sample[channel] + f32(horizontal[source_y - first_y][x][channel] * weight))
row.append(tuple(math.floor(min(255, max(0, value)) + 0.5) for value in sample))
result.append(row)
return result
+177
View File
@@ -0,0 +1,177 @@
import copy
from fractions import Fraction
import hashlib
import importlib.util
import json
from pathlib import Path
import struct
import sys
import tempfile
import unittest
from PIL import Image
PROJECT = Path(__file__).resolve().parents[1]
Q = Path(__file__).resolve().parent
sys.path.insert(0, str(PROJECT / 'tools'))
import hd_frame_policy as policy
import hd_art_audit as audit
import hd_candidate_review as candidate
import hd_register_reviewed as registration
from grid_sampler_reference import axis_weights, sample_fractional
def frame(columns=2, rows=2, column=0, row=0, size=(1, 1)):
return dict(frame=0, file='synthetic.png', logical_width=size[0], logical_height=size[1],
origin=[0, 0], source_sha256='0' * 64,
atlas_grid=dict(columns=columns, rows=rows, column=column, row=row))
class Geometry(unittest.TestCase):
def test_exact_fractional_extents(self):
item = frame(4, 2, 3, 1, (32, 32))
result = policy.atlas_grid_geometry(item, 1774, 887)
self.assertEqual(result['bounds'], (Fraction(2661, 2), Fraction(887, 2), Fraction(1774), Fraction(887)))
self.assertEqual(result['pixels'], (1330, 443, 1774, 887))
self.assertEqual(result['resident'], (128, 128))
def test_no_strip_gaps_or_omissions(self):
for count in range(1, 17):
extent = count * 5 + 1
spans = [policy.atlas_grid_geometry(frame(count, count, cell, 0), extent, extent)['bounds'] for cell in range(count)]
self.assertEqual(spans[0][0], 0)
self.assertEqual(spans[-1][2], extent)
self.assertTrue(all(left[2] == right[0] for left, right in zip(spans, spans[1:])))
self.assertTrue(all(span[2] - span[0] == Fraction(extent, count) for span in spans))
def test_last_cell_bounds_never_exceed_atlas(self):
for cols, rows, width, height in [(2, 2, 5, 5), (4, 2, 1774, 887), (7, 7, 31, 31), (1, 1, 9, 9)]:
g = policy.atlas_grid_geometry(frame(cols, rows, cols-1, rows-1), width, height)
self.assertEqual(g['pixels'][2:], (width, height))
for output in [1, 2, 4, 16]:
for weights in axis_weights(g['bounds'][0], g['bounds'][2], output):
self.assertTrue(all(g['pixels'][0] <= index < width for index, _ in weights))
def test_resolution_is_common_bounded_factor(self):
self.assertEqual(policy.atlas_grid_geometry(frame(), 5, 5)['resident'], (2, 2))
self.assertEqual(policy.atlas_grid_geometry(frame(), 7, 7)['resident'], (3, 3))
self.assertEqual(policy.atlas_grid_geometry(frame(), 13, 13)['resident'], (4, 4))
def test_non_square_logical_aspect(self):
self.assertEqual(policy.atlas_grid_geometry(frame(2, 2, size=(2, 1)), 10, 5)['resident'], (4, 2))
def test_odd_grid_size_does_not_mutate_frame(self):
item = frame(4, 2, 1, 0, (32, 32)); old = copy.deepcopy(item)
policy.atlas_grid_geometry(item, 1774, 887)
self.assertEqual(item, old)
def test_grid_and_crop_are_mutually_exclusive(self):
item = frame(); item['crop'] = [0, 0, 2, 2]
with self.assertRaises(ValueError): policy.validate_frame_policy(item)
def test_absent_grid_is_noop_and_adds_no_default_key(self):
item = frame(); item.pop('atlas_grid'); old = copy.deepcopy(item)
policy.validate_frame_policy(item)
self.assertIsNone(policy.atlas_grid_geometry(item, 5, 4))
self.assertEqual(item, old)
self.assertNotIn('atlas_grid', item)
def test_null_grid_is_noop_without_adding_or_removing_keys(self):
item = frame(); item['atlas_grid'] = None; old = copy.deepcopy(item)
self.assertIsNone(policy.validate_atlas_grid(item)); self.assertEqual(item, old)
def test_invalid_typed_grid_fields(self):
for field, value in [('columns', 0), ('rows', 0), ('columns', -1), ('rows', 2**32),
('column', True), ('row', 0.0), ('columns', '2'), ('column', 2), ('row', 2)]:
with self.subTest(field=field, value=value):
item = frame(); item['atlas_grid'][field] = value
with self.assertRaises(ValueError): policy.validate_frame_policy(item)
def test_grid_cell_budget_and_unknown_fields(self):
for grid in [{}, [], 7, dict(columns=129, rows=129, column=0, row=0),
dict(columns=2, rows=2, column=0, row=0, extra=1)]:
item = frame(); item['atlas_grid'] = grid
with self.assertRaises(ValueError): policy.validate_frame_policy(item)
def test_bad_aspect_resolution_and_dimensions(self):
for size in [(5, 6), (3, 3), (0, 5), (5, 0), (-1, 5), (True, 5)]:
with self.subTest(size=size):
with self.assertRaises(ValueError): policy.atlas_grid_geometry(frame(), *size)
class Sampler(unittest.TestCase):
def test_constant_rgba_preserved(self):
g = policy.atlas_grid_geometry(frame(), 5, 5)
pixels = [[(40, 80, 120, 255)] * 5 for _ in range(5)]
self.assertEqual(sample_fractional(pixels, g), [[(40, 80, 120, 255)] * 2 for _ in range(2)])
def test_half_boundary_pixel_participates_on_both_sides(self):
for start, end in [(Fraction(0), Fraction(5, 2)), (Fraction(5, 2), Fraction(5))]:
weights = axis_weights(start, end, 2)
self.assertTrue(any(index == 2 and weight != 0 for row in weights for index, weight in row))
def test_no_neighbor_pixel_bleed(self):
pixels = [[(255, 0, 0, 255), (255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255), (0, 0, 255, 255)] for _ in range(5)]
left = sample_fractional(pixels, policy.atlas_grid_geometry(frame(), 5, 5))
right = sample_fractional(pixels, policy.atlas_grid_geometry(frame(column=1), 5, 5))
self.assertTrue(all(pixel[2] == 0 for row in left for pixel in row))
self.assertTrue(all(pixel[0] == 0 for row in right for pixel in row))
def test_alpha_is_sampled_as_raw_channel(self):
pixels = [[(0, 0, 0, 1)] * 5 for _ in range(5)]
self.assertTrue(all(pixel[3] == 1 for row in sample_fractional(pixels, policy.atlas_grid_geometry(frame(), 5, 5)) for pixel in row))
def test_weight_normalization(self):
for extent, output in [(Fraction(5, 2), 2), (Fraction(887, 2), 128), (Fraction(31, 7), 4)]:
for weights in axis_weights(0, extent, output):
self.assertAlmostEqual(sum(value for _, value in weights), 1, places=6)
class Consumers(unittest.TestCase):
def test_candidate_retains_grid_and_source_identity(self):
item = frame(); proposal = dict(sequences=[dict(name='Synthetic', base_asset=1, frame_count=1, frames=[item])])
rows = candidate.proposal_frames(proposal)
self.assertEqual(rows['00001']['atlas_grid'], item['atlas_grid'])
changed = copy.deepcopy(rows); changed['00001']['atlas_grid']['column'] = 1
self.assertNotEqual(candidate.binding_digest(rows), candidate.binding_digest(changed))
self.assertEqual(candidate.binding_digest(rows), candidate.binding_digest(candidate.proposal_frames(proposal)))
def test_registration_typed_identity_cannot_ignore_grid_changes(self):
original = frame()
changed = copy.deepcopy(original); changed['atlas_grid']['row'] = 1
self.assertFalse(registration.same_json(original, changed))
changed = copy.deepcopy(original); changed['atlas_grid']['row'] = False
self.assertFalse(registration.same_json(original, changed))
self.assertTrue(registration.same_json(original, copy.deepcopy(original)))
def test_grid_policy_does_not_silently_drop_unknown_metadata(self):
original = frame(); original['atlas_grid']['automatic_fit'] = True
with self.assertRaises(ValueError): policy.validate_frame_policy(original)
original = frame(); original['crop'] = [0, 0, 2, 2]
with self.assertRaises(ValueError): candidate.proposal_frames(dict(sequences=[
dict(name='Synthetic', base_asset=1, frame_count=1, frames=[original])]))
def test_candidate_targets_must_bind_same_grid(self):
item = frame(); target = copy.deepcopy(item); target['asset'] = '00001'; target['atlas_grid']['column'] = 1
proposal = dict(targets=[target], sequences=[dict(name='Synthetic', base_asset=1, frame_count=1, frames=[item])])
with self.assertRaises(ValueError): candidate.proposal_frames(proposal)
def test_audit_rejects_bad_grid_and_accepts_fractional_without_modifying_png(self):
payload = struct.pack('<5i', 1, 0, 0, 1, 1) + b'\x01'
picture = Image.new('RGBA', (1, 1), (1, 1, 1, 255))
with tempfile.TemporaryDirectory(prefix='synthetic-', dir=Q) as directory:
root = Path(directory); path = root / 'synthetic.png'
Image.new('RGBA', (5, 5), (30, 50, 70, 255)).save(path)
previous = path.read_bytes(); item = frame(); item['source_sha256'] = hashlib.sha256(payload).hexdigest()
self.assertEqual(audit.validate_hd_frame(root, item, payload, picture), 16)
self.assertEqual(path.read_bytes(), previous)
item['crop'] = [0, 0, 2, 2]
with self.assertRaises(ValueError): audit.validate_hd_frame(root, item, payload, picture)
def test_audit_transparent_original_needs_raw_transparency(self):
payload = struct.pack('<5i', 1, 0, 0, 1, 1) + b'\x01'
picture = Image.new('RGBA', (1, 1), (0, 0, 0, 0))
with tempfile.TemporaryDirectory(prefix='synthetic-', dir=Q) as directory:
root = Path(directory); path = root / 'synthetic.png'
Image.new('RGBA', (5, 5), (30, 50, 70, 255)).save(path)
item = frame(); item['source_sha256'] = hashlib.sha256(payload).hexdigest()
with self.assertRaises(ValueError): audit.validate_hd_frame(root, item, payload, picture)
if __name__ == '__main__':
unittest.main(verbosity=2)
+140
View File
@@ -0,0 +1,140 @@
"""Private generic bounds; synthetic Source/PNG fixtures, no real art or engine."""
import ast, copy, hashlib, importlib.util, json, pathlib, struct, sys, tempfile, unittest
from unittest import mock
ROOT=pathlib.Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/'tools'));sys.path.insert(0,str(ROOT/'tests'))
import hd_frame_policy as policy
import hd_art_audit as audit
import hd_register_reviewed as registrar
import hd_candidate_review as candidate
import test_hd_retained_original_policy as old
from PIL import Image
def raw_source(painted, width=32, height=32):
data=struct.pack('<5i',0,0,0,width,height)
for y in range(height):
n=max(0,min(width,painted-y*width))
if n: data+=struct.pack('<4i',0,y,y*width,n)+bytes((y*width+x)%256 for x in range(n))
return data+struct.pack('<4i',0,0,-1,0)
def frame(count, painted=625):
raw=raw_source(painted)
cells=sorted([[i%32,i//32] for i in range(painted)])[:count]
return dict(frame=0,file='synthetic.png',logical_width=32,logical_height=32,origin=[0,0],source_sha256=hashlib.sha256(raw).hexdigest(),original_detail_cells=cells)
class ExtendedPolicy(unittest.TestCase):
def test_exact_quarter_and_maximum_boundaries(self):
for n,painted,ok in [(64,64,True),(65,260,True),(65,259,False),(73,625,True),(128,512,True),(128,511,False),(129,600,False)]:
with self.subTest(n=n,painted=painted):
if ok: policy.validate_frame_policy(frame(n,painted),raw_source(painted))
else:
with self.assertRaises(ValueError): policy.validate_frame_policy(frame(n,painted),raw_source(painted))
def test_legacy_full_small_source_still_valid_without_new_density_rule(self):
f=old.frame();f['original_detail_cells']=[[0,0],[0,1],[1,0],[1,1]]
policy.validate_frame_policy(f,old.payload())
def test_transparent_canvas_area_is_never_denominator(self):
f=frame(65,259)
self.assertEqual(f['logical_width']*f['logical_height'],1024)
with self.assertRaisesRegex(ValueError,'quarter'): policy.validate_frame_policy(f,raw_source(259))
def test_schema_preparation_is_not_final_density_approval(self):
f=frame(65,259);policy.validate_frame_policy(f)
rows=candidate.proposal_frames({'sequences':[dict(base_asset=1,frame_count=1,frames=[f])]})
self.assertEqual(len(rows['00001']['original_detail_cells']),65)
with self.assertRaises(ValueError):policy.validate_frame_policy(f,raw_source(259))
def test_sorted_unique_inbounds_typed_and_painted_guards_remain(self):
mutations=[lambda c:c[::-1],lambda c:c[:64]+[c[0]],lambda c:c[:64]+[[32,0]],lambda c:c[:64]+[[True,0]],lambda c:c[:64]+[[31,31]]]
for mutate in mutations:
f=frame(65);f['original_detail_cells']=mutate(f['original_detail_cells'])
with self.subTest(cells=f['original_detail_cells'][-1]),self.assertRaises(ValueError):policy.validate_frame_policy(f,raw_source(625))
def test_extended_reserved_painted_domain_and_nearest_cue_overlap(self):
f=frame(128,512);policy.validate_frame_policy(f,raw_source(512))
#256 is transparency, while raw labels0 and240..255 remain painted.
self.assertEqual(sum(0<=p<=255 for p in policy._indices(raw_source(512))[2]),512)
f['native_nearest_cues']=[f['original_detail_cells'][0]]
with self.assertRaises(ValueError):policy.validate_frame_policy(f,raw_source(512))
def test_legacy_v2_cannot_authorize_extended_and_v3_can_keep_legacy(self):
for n in [1,64]:
self.assertTrue(policy.retained_review_version_ok(policy.LEGACY_RETAINED_POLICY,{'00001':[[0,0]]*n}))
self.assertTrue(policy.retained_review_version_ok(policy.EXTENDED_RETAINED_POLICY,{'00001':[[0,0]]*n}))
self.assertFalse(policy.retained_review_version_ok(policy.LEGACY_RETAINED_POLICY,{'00001':[[0,0]]*65}))
self.assertTrue(policy.retained_review_version_ok(policy.EXTENDED_RETAINED_POLICY,{'00001':[[0,0]]*65}))
for bad in [None,True,'retained-original-details-v1',[]]:self.assertFalse(policy.retained_review_version_ok(bad,{'00001':[[0,0]]*65}))
def test_audit_rejects_density_before_png_access(self):
raw=raw_source(259);f=frame(65,259)
with mock.patch.object(audit.Image,'open',side_effect=AssertionError('PNG read forbidden')):
with self.assertRaisesRegex(ValueError,'quarter'):audit.validate_hd_frame(pathlib.Path('/absent'),f,raw,Image.new('RGBA',(32,32)))
def test_real_synthetic_audit_accepts128_of512_without_raw_changes(self):
with tempfile.TemporaryDirectory() as temp:
q=pathlib.Path(temp);raw=raw_source(512);src=q/'Source';src.write_bytes(raw)
picture=audit.decode(src,[(i,i,i) for i in range(256)])
image=Image.new('RGBA',(64,64),(40,50,60,255));image.putpixel((63,63),(0,0,0,0));image.save(q/'synthetic.png')
before=(q/'synthetic.png').read_bytes()
self.assertEqual(audit.validate_hd_frame(q,frame(128,512),raw,picture),64*64*4)
self.assertEqual(before,(q/'synthetic.png').read_bytes())
def test_malformed_source_and_canvas_still_rejected(self):
f=frame(65)
for raw in [b'',struct.pack('<5i',1,0,0,32,32),raw_source(625,25,25)]:
with self.subTest(length=len(raw)),self.assertRaises(ValueError):policy.validate_frame_policy(f,raw)
class ExtendedRegistrar(old.ActualRegistrarProvenance):
# Inherited legacy tests exercise actual approved_job unmodified call surface.
def extended(self, painted=625, count=73, layout='project'):
self.f=frame(count,painted);self.f['file']='raw.png';raw=raw_source(painted)
self.candidate['sequences'][0]['frames']=[self.f];self.rows=candidate.proposal_frames(self.candidate);digest=candidate.binding_digest(self.rows)
self.review['frame_bindings_sha256']=digest
decl=self.review['retained_original_detail_review'];decl.update(cells_by_asset={'00001':self.f['original_detail_cells']},frame_bindings_sha256=digest,policy_version=policy.EXTENDED_RETAINED_POLICY)
self.write_json(self.job_dir/'candidate.json',self.candidate)
self.write_json(self.job_dir/'job.json',dict(job_id='synthetic-complete1',targets=[dict(asset='00001',source_sha256=self.f['source_sha256'],logical_canvas=[32,32])]))
qa=json.loads((self.root/'qa.json').read_text());qa['frame_bindings_sha256']=digest;qa['frames'][0].update(source_sha256=self.f['source_sha256'],logical_canvas=[32,32]);self.write_json(self.root/'qa.json',qa)
data=(self.project/'data') if layout=='project' else self.project.parent/'data'
(data/'sprites').mkdir(parents=True,exist_ok=True);(data/'sprites/fixture.pic').write_bytes(raw);(data/'INDEX').write_text(f'00001 {len(raw)} sprites/fixture.pic\n');return data
def test_extended_actual_approved_job_both_catalog_layouts(self):
data=self.extended();self.assertEqual(len(self.approve()[2]['00001']['original_detail_cells']),73)
#Identical authenticated payload under the other product layout, no larger raw copies.
(data/'INDEX').unlink();self.extended(layout='parent');self.assertEqual(len(self.approve()[2]['00001']['original_detail_cells']),73)
def test_extended_old_version_rejected(self):
self.extended();d=copy.deepcopy(self.review['retained_original_detail_review']);d['policy_version']=policy.LEGACY_RETAINED_POLICY
with self.assertRaisesRegex(ValueError,'provenance'):self.approve(d)
def test_extended_actual_source_density_beats_forged_claim(self):
self.extended(259,65);self.review['retained_original_detail_review']['claimed_painted_Source_cell_count']=1024
with self.assertRaisesRegex(ValueError,'quarter'):self.approve()
def test_extended_actual_source_hash_required(self):
data=self.extended();(data/'sprites/fixture.pic').write_bytes(raw_source(624))
with self.assertRaisesRegex(ValueError,'authentic catalog Source'):self.approve()
def test_extended_missing_source_rejected(self):
data=self.extended();(data/'INDEX').unlink()
with self.assertRaisesRegex(ValueError,'authentic catalog Source'):self.approve()
def test_extended_source_path_escape_rejected(self):
data=self.extended();(data/'INDEX').write_text('00001 4 ../outside.pic\n')
with self.assertRaisesRegex(ValueError,'escapes catalog'):self.approve()
def test_extended_genuine_context_and_retained_not_generated_still_required(self):
self.extended()
for key in ['hybrid_allscale_and_genuine_context_independent_review','retained_original_not_generated_HD']:
d=copy.deepcopy(self.review['retained_original_detail_review']);d[key]=False
with self.subTest(key=key),self.assertRaisesRegex(ValueError,'provenance'):self.approve(d)
def test_density_helper_legacy_path_needs_no_catalog_io(self):
with mock.patch.object(pathlib.Path,'read_text',side_effect=AssertionError('legacy catalog read')):
registrar.validate_extended_retained_sources(pathlib.Path('/absent'),{'00001':old.selected()})
class RustSourceCompatibility(unittest.TestCase):
def test_legacy_hd_function_bodies_and_normalized_key_tags_remain_exact(self):
# Exact pre-bounds atlas baseline (36b3502e), formerly read from the
# private proposal's sibling original/src/hd.rs. Inline fingerprints
# preserve those exact guards in an ordinary standalone checkout.
expected = {'source_reference_budget': '1e544a7639fd123a6e813fbb34f1ded100a1db192973faeb7ce1b96ad54339a0', 'retain_original_detail_samples': '5b1516291d05de782f4d81242f38686be1dca24d00ab0ee4858e3b92729a73b4', 'sprite_with_policy': '7def63d10f179c30ad71c02b3670ca457a7652a976b4c97c1a9098388655fed5', 'terrain_sprite': 'b5d713c0ed8e5327d96c1dae464b2aca4b5e8c9450f85c53928bdde5c99b6e99', 'frame_key': '3605c9fc321571dbc777036ff500f8c436ebb1e717343c056e363c4b634ea1a1', 'original_detail_cache_identity': 'bb9baa84ba832e34dea5274866ddbde52b35f48634cff0ff80533a4dc6d81ffc'}
new=(ROOT/'src/hd.rs').read_text()
def function(s,name):
start=s.index('fn '+name+'(');left=s.index('{',start);depth=1;end=left+1
while depth:depth+=(s[end]=='{')-(s[end]=='}');end+=1
return s[start:end]
for name in ['source_reference_budget','retain_original_detail_samples','sprite_with_policy','terrain_sprite']:
self.assertEqual(expected[name],hashlib.sha256(function(new,name).encode()).hexdigest(),name)
for name,old_bytes,call in [('frame_key','b"raptor-retained-original-details-v2-painted0255"','original_detail_policy_tag(frame.original_detail_cells.len(), false)'),('original_detail_cache_identity','b"retained-original-rejected-fallback-v2-painted0255"','original_detail_policy_tag(frame.original_detail_cells.len(), true)')]:
self.assertEqual(expected[name],hashlib.sha256(function(new,name).replace(call,old_bytes).encode()).hexdigest(),name)
def test_audit_still_calls_actual_payload_validator(self):
tree=ast.parse((ROOT/'tools/hd_art_audit.py').read_text())
node=next(n for n in tree.body if isinstance(n,ast.FunctionDef) and n.name=='validate_hd_frame')
self.assertIsInstance(node.body[1],ast.Expr)
self.assertEqual(ast.unparse(node.body[1]),'validate_frame_policy(frame, payload)')
if __name__=='__main__':unittest.main(verbosity=2)
+1 -1
View File
@@ -89,7 +89,7 @@ class PaintedPolicy(unittest.TestCase):
def test_official_audit_and_schema_share_actual_validator(self):
tree = ast.parse((TOOLS / "hd_art_audit.py").read_text())
self.assertTrue(any(isinstance(n, ast.ImportFrom) and n.module == "hd_frame_policy"
and {a.name for a in n.names} == {"FRAME_KEYS", "validate_frame_policy"} for n in tree.body))
and {"FRAME_KEYS", "validate_frame_policy"} <= {a.name for a in n.names} for n in tree.body))
self.assertIn("original_detail_cells", policy.FRAME_KEYS)
self.assertIn("carried master74 frame has an unreviewed optional policy", (TOOLS / "hd_register_reviewed.py").read_text())
+23 -2
View File
@@ -12,7 +12,7 @@ import struct
from pathlib import Path
from PIL import Image
from hd_art_source import decode
from hd_frame_policy import FRAME_KEYS, validate_frame_policy
from hd_frame_policy import FRAME_KEYS, validate_frame_policy, atlas_grid_geometry
CATEGORIES = ["Player banking", "Bosses", "Enemies and ground objects", "Player and enemy projectiles", "Explosions and effects", "Equipment and pickups", "Terrain", "Menus, portraits, HUD and other pictures"]
@@ -32,7 +32,12 @@ def integer_rounded_aspect(width, height, logical_width, logical_height):
)
def validate_hd_frame(root, frame, payload, picture):
"""Mirror loader checks; artistic approval is deliberately a separate step."""
"""Mirror loader checks, including actual-Source extended retention density.
Schema-only preparation is not enough for65..128 cells; the shared policy
validator receives the decoded authentic payload here before any PNG read.
Artistic approval remains separate.
"""
validate_frame_policy(frame, payload)
if frame["source_sha256"] != hashlib.sha256(payload).hexdigest():
raise ValueError("original source hash changed")
@@ -56,6 +61,22 @@ def validate_hd_frame(root, frame, payload, picture):
image = source.convert("RGBA")
png_size = image.size
source_alpha_min, source_alpha_max = picture.getchannel("A").getextrema()
geometry = atlas_grid_geometry(frame, image.width, image.height)
if geometry is not None:
# Every raw pixel with positive area inside the rational viewport is
# checked. This read-only view is not a fitted crop or replacement PNG.
left, top, right, bottom = geometry["pixels"]
alpha_min, alpha_max = image.getchannel("A").crop((left, top, right, bottom)).getextrema()
if source_alpha_min == 0 and alpha_min != 0:
raise ValueError("transparent original needs a transparent HD background")
if alpha_max < 128:
raise ValueError("artwork has no visible pixels")
width, height = geometry["resident"]
# Mirror the new fractional sampler's explicit working-buffer bound.
integral = all(value.denominator == 1 for value in geometry["bounds"])
if not integral and width * (bottom - top) > 4_194_304:
raise ValueError("rational atlas temporary canvas exceeds limit")
return width * height * 4
if frame.get("crop") is not None:
x, y, width, height = frame["crop"]
if min(x, y) < 0 or min(width, height) <= 0 or x + width > image.width or y + height > image.height:
+58 -3
View File
@@ -1,8 +1,57 @@
"""Strict optional HD presentation metadata; source fidelity requires peer review."""
import struct
from fractions import Fraction
FRAME_KEYS = {"frame", "file", "logical_width", "logical_height", "origin",
"source_sha256", "crop", "preserve_reserved_coverage", "native_nearest_cues", "original_detail_cells"}
"source_sha256", "crop", "atlas_grid", "preserve_reserved_coverage", "native_nearest_cues", "original_detail_cells"}
def validate_atlas_grid(frame):
grid = frame.get("atlas_grid")
if grid is None:
return None
if type(grid) is not dict or set(grid) != {"columns", "rows", "column", "row"}:
raise ValueError("atlas_grid needs exactly columns/rows/column/row")
if any(type(value) is not int or not 0 <= value < 2**32 for value in grid.values()):
raise ValueError("atlas_grid fields must be u32 integers")
if frame.get("crop") is not None:
raise ValueError("atlas_grid and crop are mutually exclusive")
if grid["columns"] == 0 or grid["rows"] == 0 or grid["columns"] * grid["rows"] > 16384:
raise ValueError("atlas_grid axes or cell count invalid")
if grid["column"] >= grid["columns"] or grid["row"] >= grid["rows"]:
raise ValueError("atlas_grid cell is outside its declared axes")
return grid
def atlas_grid_geometry(frame, width, height):
"""Exact equal bounds and a bounded, uniform resident2x–4x canvas."""
grid = validate_atlas_grid(frame)
if grid is None:
return None
if any(type(value) is not int or value <= 0 for value in [width, height]):
raise ValueError("invalid atlas dimensions")
lw, lh = frame["logical_width"], frame["logical_height"]
if any(type(value) is not int or not 0 < value < 2**32 for value in [lw, lh]):
raise ValueError("invalid logical dimensions")
cols, rows = grid["columns"], grid["rows"]
if width * lh * rows != height * lw * cols:
raise ValueError("rational atlas cell aspect differs from original canvas")
if width < 2 * cols * lw or height < 2 * rows * lh:
raise ValueError("rational atlas cell needs at least twice the logical resolution")
factor = min(width // (cols * lw), height // (rows * lh), 4)
left, right = Fraction(width * grid["column"], cols), Fraction(width * (grid["column"] + 1), cols)
top, bottom = Fraction(height * grid["row"], rows), Fraction(height * (grid["row"] + 1), rows)
floor = lambda value: value.numerator // value.denominator
ceil = lambda value: -(-value.numerator // value.denominator)
return dict(bounds=(left, top, right, bottom),
pixels=(floor(left), floor(top), ceil(right), ceil(bottom)),
resident=(lw * factor, lh * factor))
LEGACY_RETAINED_POLICY = "retained-original-details-v2-painted0255"
EXTENDED_RETAINED_POLICY = "retained-original-details-v3-max128-quarter-painted0255"
def retained_review_version_ok(version, retained_details):
"""The old declaration cannot authorize a new longer list; old bytes stay valid."""
return version == EXTENDED_RETAINED_POLICY or (version == LEGACY_RETAINED_POLICY
and all(len(cells) <= 64 for cells in retained_details.values()))
def _indices(payload):
if len(payload) < 20:
@@ -40,6 +89,7 @@ def _indices(payload):
def validate_frame_policy(frame, source_payload=None):
if set(frame) - FRAME_KEYS:
raise ValueError("unknown HD frame fields")
validate_atlas_grid(frame)
if type(frame.get("preserve_reserved_coverage", False)) is not bool:
raise ValueError("preserve_reserved_coverage must be a boolean")
cues = frame.get("native_nearest_cues", [])
@@ -68,8 +118,8 @@ def validate_frame_policy(frame, source_payload=None):
raise ValueError("native nearest cue must be an ordinary original source cell")
details = frame.get("original_detail_cells", [])
if type(details) is not list or len(details) > 64:
raise ValueError("original_detail_cells must contain at most64 Source cells")
if type(details) is not list or len(details) > 128:
raise ValueError("original_detail_cells must contain at most128 Source cells")
previous = None
for cell in details:
if type(cell) is not list or len(cell) != 2 or any(type(n) is not int or not 0 <= n < 2**32 for n in cell):
@@ -86,3 +136,8 @@ def validate_frame_policy(frame, source_payload=None):
raise ValueError("Original detail canvas differs from Source")
if any(not 0 <= indices[y * width + x] < 256 for x, y in details):
raise ValueError("Original details select painted Source indices0–255 only")
# Metadata-only preparation can check structure, not claim Source density.
# Runtime, audit and extended registrar approval supply actual payloads.
if len(details) > 64 and len(details) * 4 > sum(0 <= p <= 255 for p in indices):
raise ValueError("65..128 Original details may retain at most one quarter of painted Source")
+38 -1
View File
@@ -17,6 +17,7 @@ from pathlib import Path
from hd_candidate_review import binding_digest, proposal_frames
from hd_art_audit import checked_palette, validate_hd_frame
from hd_art_source import decode
from hd_frame_policy import validate_frame_policy, retained_review_version_ok
def asset_list(value, label):
@@ -454,6 +455,41 @@ def approved_catalog_only_retained_evidence(root, project, declaration, rows, qa
raise ValueError("catalog-only actual all-scale evidence differs")
def validate_extended_retained_sources(project, rows):
"""Longer lists require authentic catalog bytes, not claimed QA pixel counts.
Only the two product data layouts are eligible. ID+SHA select the authentic
payload; a catalog-only trace is still separately bound to its exact root.
No generated PNG or recorded density can supply the denominator.
"""
for asset, frame in rows.items():
if len(frame.get("original_detail_cells", [])) <= 64:
continue
authenticated = False
for data in (project / "data", project.parent / "data"):
index = data / "INDEX"
if not index.is_file():
continue
matches = [parts for line in index.read_text().splitlines()
if len(parts := line.split()) == 3 and int(parts[0], 16) == int(asset, 16)]
if len(matches) != 1:
continue
relative = Path(matches[0][2])
if relative.is_absolute() or ".." in relative.parts:
raise ValueError("extended Original Source path escapes catalog")
path = (data / relative).resolve()
if not path.is_relative_to(data.resolve()):
raise ValueError("extended Original Source path escapes catalog")
payload = path.read_bytes()
if hashlib.sha256(payload).hexdigest() != frame["source_sha256"]:
continue
validate_frame_policy(frame, payload)
authenticated = True
break
if not authenticated:
raise ValueError("extended Original details lack authentic catalog Source bytes")
def approved_job(root, identity, project):
directory = contained(root, Path("jobs") / identity)
if directory.parent != (root / "jobs").resolve():
@@ -492,7 +528,7 @@ def approved_job(root, identity, project):
and declaration.get("catalog_only_independent_whole_material_and_allscale_review") is True)
if (not same_json(declaration.get("cells_by_asset"), retained_details)
or declaration.get("retained_original_not_generated_HD") is not True
or declaration.get("policy_version") != "retained-original-details-v2-painted0255"
or not retained_review_version_ok(declaration.get("policy_version"), retained_details)
or not same_json(declaration.get("painted_Source_palette_domain"), [0, 255])
or declaration.get("functional_palette_effects_are_retained_Original") is not True
or not qualified_scope
@@ -550,6 +586,7 @@ def approved_job(root, identity, project):
approved_standalone_evidence(root, selection, review, rows, qa)
elif retained_details and catalog_only:
approved_catalog_only_retained_evidence(root, project, declaration, rows, qa)
validate_extended_retained_sources(project, rows)
return candidate, review, rows