Preserve explicit original HD details and rebuild Linux and Windows runtimes

This commit is contained in:
raptorreplica committed 2026-10-17 16:31:08 +11:00
1 parent 1bc0a9ceff
commit 9015a6c955
13 files changed
+739 -33

No files matched your search

BIN
View File
Binary file not shown.
Binary file not shown.
+17 -3
View File
@@ -10,12 +10,16 @@ A complete PNG for either an opaque or transparent original may use dimensions p
## Optional frame presentation policies
Both policies below default to disabled. They require explicit frame metadata and source/art review; loader validation alone does not approve a remaster.
All policies below default to disabled. They require explicit frame metadata and source/art review; loader validation alone does not approve a remaster.
- `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.
Opt-in metadata participates in both derived and indexed cache identities. Frames without these policies retain their previous cache keys and pixels. The candidate and original-art audit tools validate the same metadata restrictions as the Rust loader.
- `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.
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.
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.
`HdAssets::load` performs full eager validation using the same bounded caches. `validate_all()` can also validate a lazily registered pack; its report distinguishes registered, validated and rejected frames. Full validation can therefore be used in a CLI or audit without raising runtime memory limits. It validates supplied artwork and does not infer coverage for absent originals.
@@ -31,7 +35,7 @@ Three least-recently-used caches each have a 32 MiB ceiling:
Resident cached pixels total at most 96 MiB, irrespective of catalog coverage. Decoder input, resampling work buffers and returned owned sprites are additional bounded working allocations; callers should retain only currently needed sprites. Eviction removes cached pixels, not frame registrations or validated coverage. Adjacent crops reuse the decoded atlas; an atlas is not cloned in full merely to extract a small crop. Repeated draws copy only the final indexed canvas. Palette changes cannot reuse stale indexed colors.
Source palette labels have a separate hard 32 MiB metadata ceiling. Frames using only ordinary colors need no label map. Frames containing animated labels retain one byte per original logical pixel; transparent and ordinary source locations use the ordinary class. Quantization restricts ordinary HD pixels to indices 0–239, water to 240–244 and fire to 245–249. Original glow/flash labels 250–255 remain exact. Newly visible HD pixels outside the original silhouette use ordinary colors. This prevents a bright static highlight from acquiring fire, water or blinking behavior. Cache color keys include the source class; equal RGB values in different classes cannot share an incorrect palette result.
Source palette labels and full Original references for opted-in retained details share one checked 32 MiB ceiling. Frames using only ordinary colors need no label map. A nonempty retained-detail selection keeps one bounded full Original indexed reference; absent/empty selections keep none. Frames containing animated labels retain one byte per original logical pixel; transparent and ordinary source locations use the ordinary class. Quantization restricts ordinary HD pixels to indices 0–239, water to 240–244 and fire to 245–249. Original glow/flash labels 250–255 remain exact. Newly visible HD pixels outside the original silhouette use ordinary colors. This prevents a bright static highlight from acquiring fire, water or blinking behavior. Cache color keys include the source class; equal RGB values in different classes cannot share an incorrect palette result.
Standalone opening cards use `sprite_static_palette` with all 256 entries of their own unfaded palette. Its indexed cache policy is distinct from gameplay source classes; both share the same checked PNG and RGBA caches. Cinematic fades apply during presentation. See [hd-opening-cards.md](hd-opening-cards.md) for the route and fallback checks.
@@ -58,3 +62,13 @@ 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
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
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.
+17 -7
View File
@@ -5,8 +5,8 @@ The self-contained RustRaptor folder includes **5,339 original HD pictures /
data files / 8,150,528,285 bytes** match the runtime manifest. This update adds
the complete four-frame turret `3001a–3001d` and tank `30078–3007b`, plus terrain
`10525`, `1052a`, `20402` and `206a0`. Separate source, material, scene and native
metadata reviews passed. All previous data, artwork, engine source and executables
remain unchanged. Actual bundled HD and mod validation is recorded with this release.
metadata reviews passed. That artwork update preserves previous data and artwork.
It is published as `1bc0a9ce`; the subsequent runtime rebuild is described below.
The requested 97-picture batch is published as `a45abc58`. The previous 5,327-frame
revision, `afbf119`, passed [native Windows CI](https://github.com/Quintine/RustRaptor/actions/runs/38518853865):
@@ -26,11 +26,21 @@ transitions, with no full-route survival claim. Fully clipped cases retain their
explicit limits. Source shadows, animation order and original collision dimensions
remain unchanged.
The optional retained-Original-detail implementation passed independent source
review, 70 unique private Rust tests and 31 Python checks. Shared engine cutover,
Linux/Windows rebuilds, sampler continuity and genuine hybrid terrain scene reviews
are next. Original painted samples retained by this policy will be distinguished
from newly generated HD detail. Existing artwork holds remain in force.
The optional retained-Original-detail implementation is now applied and rebuilt
in the included Linux and Windows executables. Its exact source passed independent
review and 70 unique private Rust tests; all 31 Python policy/rounding tests also
pass against the shared files. The new Linux executable validates all 5,339 HD
frames with zero rejects. Menu, S1 Wave 7 and S3 Wave 9 software-SDL captures at
1080p are byte-identical to the pre-change references, with 6,220,800 pixels compared.
Windows compilation, PE imports and complete shared-data hashes pass; native
Windows execution remains a separate post-push CI check.
No approved picture has enabled the new detail policy yet. Four sampler continuity
cases and the genuine 10511 hybrid terrain scene checks are in progress. Original
painted samples retained by this policy will be distinguished from generated HD
detail. Existing artwork holds remain in force. Current rebuild receipts and the
actual Source18/library epoch are in
`.reference/hd-root-painted0255-shared-cutover-20261017/`.
Root owns assembly, README, this checkpoint and Git publication; coordinator owns
the four HD coverage documents. Current receipts are in
+7 -7
View File
@@ -3,7 +3,7 @@
"platform": "Linux x86_64",
"catalog_entries": 6449,
"approved_hd_frames": 5339,
"binary_sha256": "43aef5c389866edd5ccad381d27952f26b329a88e537c8e26a88988d6fdd4fcd",
"binary_sha256": "ff6a6f08586a7950ee6bfcaba14da89409df63fbbd584d4255efc2b2ecff836f",
"data_files": 9323,
"data_bytes": 8150528285,
"files": {
@@ -9332,11 +9332,11 @@
"data/ui/WINGAME_SWD": "304630a1223419d69723902ffd22c2c4eea0388491e37dfca663fcbe78e99aff"
},
"system_dependencies": [
"\tlinux-vdso.so.1 (0x00007fe66f101000)",
"\tlibSDL2-2.0.so.0 => /home/quintine/.local/lib/libSDL2-2.0.so.0 (0x00007fe66e400000)",
"\tlibgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fe66e7d3000)",
"\tlibm.so.6 => /usr/lib/libm.so.6 (0x00007fe66e69c000)",
"\tlibc.so.6 => /usr/lib/libc.so.6 (0x00007fe66e000000)",
"\t/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fe66f103000)"
"\tlinux-vdso.so.1 (0x00007f8e2614b000)",
"\tlibSDL2-2.0.so.0 => /home/quintine/Projects/RaptorReplica/RustRaptor/lib/libSDL2-2.0.so.0 (0x00007f8e25400000)",
"\tlibgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f8e260f7000)",
"\tlibm.so.6 => /usr/lib/libm.so.6 (0x00007f8e256c9000)",
"\tlibc.so.6 => /usr/lib/libc.so.6 (0x00007f8e25000000)",
"\t/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f8e2614d000)"
]
}
+1 -1
View File
@@ -37,7 +37,7 @@
]
},
"files": {
"bin/raptor-rust.exe": "546f18a1e0f5df3d601058ffbd63e74190505878fa677a75c310ef1ff5c9dd51",
"bin/raptor-rust.exe": "427140b2b8b773979db46d9647728835af529082bee8503e6fa4eaaae37d4bee",
"bin/SDL2.dll": "53e8fa7e9ed43c30bd650c39396f0d9d2067d2fff0b926c9433b75202592e4aa",
"bin/SDL2-WINDOWS-LICENSE.txt": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
"run.cmd": "b007382d9ed3560191856514a9ebd4e429801fc25428840e304bb297f3553269"
+20 -5
View File
@@ -3078,7 +3078,7 @@ impl Host {
visible_tiles.push((art.clone(), x, y));
}
}
let key = serde_json::to_vec(&(
let mut key = serde_json::to_vec(&(
&world.mission,
row_origin,
canvas.scale,
@@ -3087,6 +3087,16 @@ impl Host {
&self.active.pins,
&visible_tiles,
))?;
if self.config.hd_mode {
let ids: std::collections::BTreeSet<_> = visible_tiles.iter()
.filter_map(|(art, _, _)| art.asset).collect();
let details: Vec<_> = ids.into_iter().filter_map(|id|
self.hd.original_detail_cache_identity(id).map(|identity| (id, identity))).collect();
if !details.is_empty() {
key.extend_from_slice(b"terrain-retained-original-details-v2-painted0255");
key.extend_from_slice(&serde_json::to_vec(&details)?);
}
}
let shift = if world.tick > 0 { alpha - 1.0 } else { 0.0 };
let translated_y = ((-34.0 + phase as f32 + shift) * canvas.scale as f32).round() as i32;
if let Some(bg) = self.terrain_hd.cached_layer(&key) {
@@ -3146,6 +3156,7 @@ impl Host {
let mut bg = upscale(&terrain.image, canvas.scale)?;
if self.config.hd_mode {
let mut replacements = Vec::new();
let mut retained_original = Vec::new();
let mut labels = Vec::new();
for (art, x, y, detailed_original) in prepared_tiles {
let replacement =
@@ -3158,15 +3169,19 @@ impl Host {
),
};
self.art(&mut tile, &art, &owner, 0, 0.0, 0.0)?;
Some(tile.image)
Some((tile.image, None))
} else if let Some(id) = art.asset {
self.hd.sprite(id, canvas.scale, &self.palette)?
self.hd.terrain_sprite(id, canvas.scale, &self.palette)?
} else {
None
};
let Some(replacement) = replacement else {
let Some((replacement, details)) = replacement else {
continue;
};
if let Some(details) = details {
retained_original.push(crate::terrain_hd::RetainedOriginalPlacement {
replacement_index: replacements.len(), details });
}
labels.push(art.asset.map(|id| format!("{id:05x}")).unwrap_or_else(|| {
art.hd_png.clone().or(art.png.clone()).unwrap_or_default()
}));
@@ -3179,7 +3194,7 @@ impl Host {
if !replacements.is_empty() {
let composition =
self.terrain_hd
.compose(&bg, &replacements, canvas.scale, &self.palette);
.compose_retaining_original(&bg, &replacements, &retained_original, canvas.scale, &self.palette);
bg = composition.image;
for (label, result) in labels.into_iter().zip(composition.results) {
if result != OverlayResult::Applied {
+325 -8
View File
@@ -69,6 +69,25 @@ pub struct Frame {
/// Other cells/scales and static palettes retain their existing sampler.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub native_nearest_cues: Vec<[u32; 2]>,
/// 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.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub original_detail_cells: Vec<[u32; 2]>,
}
/// Validated Source-only footprint metadata. Fields cannot be forged by callers.
#[derive(Clone)]
pub struct OriginalDetailCells {
cells: Vec<[u32; 2]>,
logical_size: [usize; 2],
}
impl OriginalDetailCells {
pub fn cells(&self) -> &[[u32; 2]] { &self.cells }
pub fn logical_size(&self) -> [usize; 2] { self.logical_size }
#[cfg(test)]
pub(crate) fn for_test(cells: Vec<[u32; 2]>, logical_size: [usize; 2]) -> Self {
Self { cells, logical_size }
}
}
fn is_false(value: &bool) -> bool {
!*value
@@ -79,6 +98,8 @@ struct Replacement {
/// None means every source location is ordinary. Special locations retain
/// their original cycling group or exact glow/flash label.
source_labels: Option<Vec<u8>>,
/// Only opt-in frames retain full indexed Source for Original ScaleNx.
original_detail_source: Option<Sprite>,
validated: bool,
rejected: bool,
}
@@ -168,6 +189,11 @@ pub struct CacheStats {
enum PalettePolicy {
SourceClasses,
Static,
// Baseline HD for terrain edges; same sampler/classes, no Original patch.
SourceClassesBaseline,
}
impl PalettePolicy {
fn source_classes(self) -> bool { self != Self::Static }
}
pub struct HdAssets {
root: PathBuf,
@@ -184,6 +210,7 @@ pub struct HdAssets {
disk_limit: u64,
metadata_rejected: usize,
source_label_bytes: usize,
original_detail_source_bytes: usize,
pub diagnostics: Vec<String>,
}
impl Default for HdAssets {
@@ -203,6 +230,7 @@ impl Default for HdAssets {
disk_limit: DISK_CACHE_BYTES,
metadata_rejected: 0,
source_label_bytes: 0,
original_detail_source_bytes: 0,
diagnostics: Vec::new(),
}
}
@@ -305,15 +333,19 @@ impl HdAssets {
None
};
let label_bytes = source_labels.as_ref().map_or(0, Vec::len);
ensure!(
self.source_label_bytes + label_bytes <= MAX_SOURCE_LABEL_BYTES,
"HD source palette labels exceed 32 MiB"
);
let detail_bytes = if frame.original_detail_cells.is_empty() { 0 }
else { original.pixels.len() * std::mem::size_of::<u16>() };
ensure!(source_reference_budget(self.source_label_bytes,
self.original_detail_source_bytes, label_bytes, detail_bytes),
"HD retained Source references exceed combined32 MiB");
self.source_label_bytes += label_bytes;
self.original_detail_source_bytes += detail_bytes;
Ok(Replacement {
frame: frame.clone(),
requires_alpha: original.pixels.contains(&TRANSPARENT),
source_labels,
original_detail_source: if frame.original_detail_cells.is_empty() { None }
else { Some(original.clone()) },
validated: false,
rejected: false,
})
@@ -419,6 +451,13 @@ impl HdAssets {
}
}
}
if !frame.original_detail_cells.is_empty() {
digest.update(b"raptor-retained-original-details-v2-painted0255");
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()); }
}
}
digest.finalize().into()
}
fn rgba_frame(&mut self, id: u32) -> Result<Option<([u8; 32], RgbaImage)>> {
@@ -744,6 +783,34 @@ impl HdAssets {
) -> Result<Option<Sprite>> {
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.
pub fn terrain_sprite(&mut self, id: u32, scale: usize, palette: &[[u8; 3]; 256])
-> Result<Option<(Sprite, Option<OriginalDetailCells>)>>
{
let has_details = self.replacements.get(&id)
.is_some_and(|r| !r.frame.original_detail_cells.is_empty());
let policy = if has_details { PalettePolicy::SourceClassesBaseline }
else { PalettePolicy::SourceClasses };
let Some(image) = self.sprite_with_policy(id, scale, palette, policy)? else { return Ok(None) };
let details = self.replacements.get(&id).filter(|r| !r.frame.original_detail_cells.is_empty())
.map(|r| OriginalDetailCells { cells: r.frame.original_detail_cells.clone(),
logical_size: [r.frame.logical_width as usize, r.frame.logical_height as usize] });
Ok(Some((image, details)))
}
pub fn original_detail_source_bytes(&self) -> usize { self.original_detail_source_bytes }
/// None for all default frames, without opening/decoding their PNGs.
/// Opt-ins bind actual resident raw identity; failed eligibility cannot reuse
/// the formerly valid hybrid prepared layer.
pub fn original_detail_cache_identity(&mut self, id: u32) -> Option<[u8; 32]> {
let frame = self.replacements.get(&id)?.frame.clone();
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(serde_json::to_vec(&frame).ok()?);
Some(digest.finalize().into())
}
fn sprite_with_policy(
&mut self,
id: u32,
@@ -782,7 +849,7 @@ impl HdAssets {
for (index, (destination, pixel)) in
sprite.pixels.iter_mut().zip(resized.pixels()).enumerate()
{
let pixel = if policy == PalettePolicy::SourceClasses
let pixel = if policy.source_classes()
&& scale == 1
&& !frame.native_nearest_cues.is_empty()
&& frame
@@ -802,7 +869,7 @@ impl HdAssets {
};
let logical =
(index / width / scale) * frame.logical_width as usize + (index % width / scale);
let source_label = if policy == PalettePolicy::SourceClasses {
let source_label = if policy.source_classes() {
replacement
.source_labels
.as_ref()
@@ -814,7 +881,7 @@ impl HdAssets {
// Require meaningful existing alpha support in the bounded
// resident logical cell; missing/mostly empty artwork remains
// transparent. This guard is not source-fidelity approval.
let supported = policy == PalettePolicy::SourceClasses
let supported = policy.source_classes()
&& frame.preserve_reserved_coverage
&& (250..=255).contains(&source_label)
&& reserved_cell_supported(&rgba, frame, logical);
@@ -826,10 +893,16 @@ impl HdAssets {
*destination = *colors
.entry((rgb, source_label))
.or_insert_with(|| match policy {
PalettePolicy::SourceClasses => closest(palette, rgb, source_label),
PalettePolicy::SourceClasses | PalettePolicy::SourceClassesBaseline => closest(palette, rgb, source_label),
PalettePolicy::Static => closest_in_range(palette, rgb, 0..256),
});
}
if policy == PalettePolicy::SourceClasses && !frame.original_detail_cells.is_empty() {
let original = replacement.original_detail_source.as_ref()
.context("validated retained Original reference missing")?;
let control = crate::render::upscale(original, scale)?;
retain_original_detail_samples(&mut sprite, &control, &frame.original_detail_cells, scale)?;
}
self.scaled
.insert(scaled_key, sprite.clone(), sprite.pixels.len() * 2);
Ok(Some(sprite))
@@ -990,6 +1063,15 @@ 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!(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");
ensure!(original.pixels[y as usize * original.width + x as usize] < TRANSPARENT,
"retained Original detail must be a painted Source index0–255");
ensure!(frame.native_nearest_cues.binary_search(&[x, y]).is_err(),
"Original detail and PNG-nearest policies may not overlap");
}
ensure!(
frame.origin == [0, 0],
"HD format 1 preserves the original top-left anchor [0, 0]"
@@ -1000,6 +1082,24 @@ fn validate_frame(frame: &Frame, original: &Sprite, source: &[u8]) -> Result<()>
);
Ok(())
}
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)
}
fn retain_original_detail_samples(image: &mut Sprite, original_control: &Sprite,
cells: &[[u32; 2]], scale: usize) -> Result<()> {
ensure!((1..=4).contains(&scale) && image.width == original_control.width
&& image.height == original_control.height && image.pixels.len() == original_control.pixels.len(),
"retained Original control extent");
for &[x, y] in cells {
for dy in 0..scale {
let start = (y as usize * scale + dy) * image.width + x as usize * scale;
ensure!(start + scale <= image.pixels.len(), "retained Original footprint extent");
image.pixels[start..start+scale].copy_from_slice(&original_control.pixels[start..start+scale]);
}
}
Ok(())
}
fn hex_digest(key: &[u8; 32]) -> String {
key.iter().map(|byte| format!("{byte:02x}")).collect()
}
@@ -1040,6 +1140,7 @@ mod tests {
crop: None,
preserve_reserved_coverage: false,
native_nearest_cues: Vec::new(),
original_detail_cells: Vec::new(),
}
}
#[test]
@@ -1077,6 +1178,7 @@ mod tests {
frame: frame(source),
requires_alpha: true,
source_labels: None,
original_detail_source: None,
validated: false,
rejected: false,
},
@@ -2184,6 +2286,7 @@ mod policy_proposal_tests {
crop: None,
preserve_reserved_coverage: false,
native_nearest_cues: Vec::new(),
original_detail_cells: Vec::new(),
}
}
fn assets(label: u8, mode: bool, rgba: RgbaImage) -> (tempfile::TempDir, HdAssets) {
@@ -2201,6 +2304,7 @@ mod policy_proposal_tests {
frame: f,
requires_alpha: true,
source_labels: Some(vec![label, 0, 0, 0]),
original_detail_source: None,
validated: false,
rejected: false,
},
@@ -2455,6 +2559,7 @@ mod policy_proposal_tests {
frame: f,
requires_alpha: false,
source_labels: None,
original_detail_source: None,
validated: false,
rejected: false,
},
@@ -2498,3 +2603,215 @@ mod policy_proposal_tests {
);
}
}
#[cfg(test)]
mod retained_original_detail_tests {
use super::*;
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(),
original_detail_cells: Vec::new() } }
#[test]
fn typed_serialization_defaults_and_source_constraints() {
let f=frame();let j=serde_json::to_value(&f).unwrap();
assert!(j.get("original_detail_cells").is_none());
let round:Frame=serde_json::from_value(j.clone()).unwrap();assert!(round.original_detail_cells.is_empty());
for value in [serde_json::json!([[true,0]]),serde_json::json!([[0.0,0]]),serde_json::json!([[-1,0]])] {
let mut bad=j.clone();bad["original_detail_cells"]=value;
assert!(serde_json::from_value::<Frame>(bad).is_err());
}
for cells in [vec![[0,0],[0,0]],vec![[1,0],[0,0]],vec![[2,0]],vec![[0,2]]] {
let mut bad=f.clone();bad.original_detail_cells=cells;
assert!(validate_frame(&bad,&source(),b"source").is_err());
}
let mut selected=f.clone();selected.original_detail_cells=vec![[0,0]];
assert!(validate_frame(&selected,&source(),b"source").is_ok());
for label in [0,19,239,240,243,245,249,250,252,253,255] {
let mut original=source();original.pixels[0]=label;
assert!(validate_frame(&selected,&original,b"source").is_ok());
}
for label in [TRANSPARENT,257,u16::MAX] {
let mut original=source();original.pixels[0]=label;
assert!(validate_frame(&selected,&original,b"source").is_err());
}
selected.native_nearest_cues=vec![[0,0]];
assert!(validate_frame(&selected,&source(),b"source").is_err());
}
#[test]
fn footprint_all_scales_and_outside_exact() {
let original=Sprite {width:3,height:3,pixels:vec![0,243,0,243,19,0,0,0,0]};
for scale in 1..=4 {
let control=crate::render::upscale(&original,scale).unwrap();
let mut image=Sprite {width:3*scale,height:3*scale,pixels:vec![80;9*scale*scale]};
retain_original_detail_samples(&mut image,&control,&[[1,1]],scale).unwrap();
for y in 0..image.height {for x in 0..image.width {
let i=y*image.width+x;
assert_eq!(image.pixels[i],if x/scale==1&&y/scale==1{control.pixels[i]}else{80});
}}
}
}
#[test]
fn empty_keys_unchanged_and_optins_distinct() {
let f=frame();let info=PngInfo{size:1,modified:None,digest:[1;32],width:8,height:8};
let old=HdAssets::frame_key(&f,&info,false);
let mut explicit_empty=f.clone();explicit_empty.original_detail_cells=Vec::new();
assert_eq!(old,HdAssets::frame_key(&explicit_empty,&info,false));
let mut a=f.clone();a.original_detail_cells=vec![[0,0]];
let mut b=a.clone();b.original_detail_cells=vec![[1,0]];
assert_ne!(old,HdAssets::frame_key(&a,&info,false));
assert_ne!(HdAssets::frame_key(&a,&info,false),HdAssets::frame_key(&b,&info,false));
}
#[test]
fn source_reference_memory_is_combined_bounded_and_overflow_safe() {
assert!(source_reference_budget(MAX_SOURCE_LABEL_BYTES,0,0,0));
assert!(source_reference_budget(0,MAX_SOURCE_LABEL_BYTES,0,0));
assert!(!source_reference_budget(MAX_SOURCE_LABEL_BYTES,0,0,1));
assert!(!source_reference_budget(0,MAX_SOURCE_LABEL_BYTES,1,0));
assert!(!source_reference_budget(usize::MAX,0,1,0));
}
#[test]
fn real_sampler_retains_source_only_for_gameplay_not_baseline_or_static() {
let dir=tempfile::tempdir().unwrap();
image::RgbaImage::from_pixel(8,8,image::Rgba([255,0,0,255])).save(dir.path().join("raw.png")).unwrap();
let mut h=HdAssets {root:dir.path().into(),..HdAssets::default()};
let mut f=frame();f.original_detail_cells=vec![[0,0]];
h.replacements.insert(1,Replacement {frame:f,requires_alpha:false,source_labels:None,
original_detail_source:Some(source()),validated:false,rejected:false});
let mut palette=[[0;3];256];palette[80]=[255,0,0];
for scale in 1..=4 {
let hybrid=h.sprite(1,scale,&palette).unwrap().unwrap();
let (baseline,details)=h.terrain_sprite(1,scale,&palette).unwrap().unwrap();
let static_card=h.sprite_static_palette(1,scale,&palette).unwrap().unwrap();
assert_eq!(details.unwrap().cells(),&[[0,0]]);
assert!(baseline.pixels.iter().all(|p|*p==80));assert_eq!(baseline,static_card);
for y in 0..2*scale {for x in 0..2*scale {
assert_eq!(hybrid.pixels[y*2*scale+x],if x<scale&&y<scale{19}else{80});
}}
}
}
#[test]
fn functional_palette_indices_retained_at_all_scales_and_Static_PNG_unaffected() {
let dir=tempfile::tempdir().unwrap();
let raw=image::RgbaImage::from_fn(8,8,|x,y|if x<4&&y>=4 {image::Rgba([255,0,0,255])}else{image::Rgba([0,0,0,0])});
raw.save(dir.path().join("raw.png")).unwrap();
let raw_before=std::fs::read(dir.path().join("raw.png")).unwrap();
let original=Sprite{width:2,height:2,pixels:vec![252,253,80,TRANSPARENT]};
let mut h=HdAssets{root:dir.path().into(),..HdAssets::default()};let mut f=frame();f.original_detail_cells=vec![[0,0],[1,0]];
h.replacements.insert(1,Replacement{frame:f,requires_alpha:true,source_labels:Some(vec![252,253,0,0]),original_detail_source:Some(original.clone()),validated:false,rejected:false});
let mut a=[[0;3];256];a[80]=[255,0,0];a[252]=[255,255,0];a[253]=[0,255,0];
let mut b=a;b[252]=[0,0,0];b[253]=[255,255,255];
for scale in 1..=4 {
let control=crate::render::upscale(&original,scale).unwrap();
let baseline=h.terrain_sprite(1,scale,&a).unwrap().unwrap().0;
let hybrid_a=h.sprite(1,scale,&a).unwrap().unwrap();let hybrid_b=h.sprite(1,scale,&b).unwrap().unwrap();
let static_a=h.sprite_static_palette(1,scale,&a).unwrap().unwrap();
let mut no_details=h.replacements.get(&1).unwrap().frame.clone();no_details.original_detail_cells.clear();
let saved=h.replacements.get(&1).unwrap().frame.clone();h.replacements.get_mut(&1).unwrap().frame=no_details;
let static_default=h.sprite_static_palette(1,scale,&a).unwrap().unwrap();h.replacements.get_mut(&1).unwrap().frame=saved;
assert_eq!(static_a,static_default,"retention must never alter Static sampling");
assert_eq!(hybrid_a,hybrid_b,"functional indices survive changing palette values");
let rendered_a=crate::render::Canvas{image:hybrid_a.clone(),scale}.rgba(&a,false);
let rendered_b=crate::render::Canvas{image:hybrid_b.clone(),scale}.rgba(&b,false);
for y in 0..2*scale {for x in 0..2*scale {
let i=y*2*scale+x;let expected=if y<scale{control.pixels[i]}else{baseline.pixels[i]};
assert_eq!(hybrid_a.pixels[i],expected);
if expected==252||expected==253 {
assert_eq!(&rendered_a[4*i..4*i+3],&a[expected as usize]);
assert_eq!(&rendered_b[4*i..4*i+3],&b[expected as usize]);
assert_ne!(&rendered_a[4*i..4*i+3],&rendered_b[4*i..4*i+3]);
}
}}
}
assert_eq!(std::fs::read(dir.path().join("raw.png")).unwrap(),raw_before,"no PNG/alpha edit");
}
#[test]
fn reserved_logical_cell_can_truthfully_retain_transparent_OriginalNx_subpixels() {
let original=Sprite{width:3,height:3,pixels:vec![TRANSPARENT,TRANSPARENT,80,TRANSPARENT,252,80,80,80,80]};
let mut had_mixed=false;
for scale in 1..=4 {
let control=crate::render::upscale(&original,scale).unwrap();
let mut image=Sprite{width:3*scale,height:3*scale,pixels:vec![80;9*scale*scale]};
retain_original_detail_samples(&mut image,&control,&[[1,1]],scale).unwrap();
for y in 0..image.height {for x in 0..image.width {
let i=y*image.width+x;let selected=x/scale==1&&y/scale==1;
assert_eq!(image.pixels[i],if selected{control.pixels[i]}else{80});
had_mixed|=selected&&control.pixels[i]==TRANSPARENT;
}}
}
assert!(had_mixed,"real ScaleNx diagonal control includes transparent subpixel");
}
#[test]
fn retained_reserved_metadata_never_rescues_invalid_PNG_eligibility() {
for invisible in [false,true] {
let dir=tempfile::tempdir().unwrap();
let (w,hgt)=if invisible{(8,8)}else{(8,5)};
let raw=image::RgbaImage::from_fn(w,hgt,|x,y|if invisible||(x==0&&y==0){image::Rgba([0,0,0,0])}else{image::Rgba([255,0,0,255])});
raw.save(dir.path().join("raw.png")).unwrap();
let mut h=HdAssets{root:dir.path().into(),..HdAssets::default()};let mut f=frame();f.original_detail_cells=vec![[0,0]];
let original=Sprite{width:2,height:2,pixels:vec![252,80,80,TRANSPARENT]};
h.replacements.insert(1,Replacement{frame:f,requires_alpha:true,source_labels:Some(vec![252,0,0,0]),original_detail_source:Some(original),validated:false,rejected:false});
assert!(h.sprite(1,1,&[[0;3];256]).unwrap().is_none());
assert!(h.terrain_sprite(1,1,&[[0;3];256]).unwrap().is_none());
assert!(!h.diagnostics.is_empty());
}
}
fn ordinary_only_v1_frame_key(frame: &Frame, info: &PngInfo, requires_alpha: bool) -> [u8; 32] {
let mut digest = Sha256::new();
digest.update(KERNEL_VERSION);
let (width, height) = frame
.crop
.map(|[_, _, width, height]| (width, height))
.unwrap_or((info.width, info.height));
if !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 {
digest.update(b"full-frame-transparent-rounding-v1");
} else {
digest.update(b"full-frame-opaque-rounding-v1");
}
}
digest.update(info.digest);
digest.update(frame.source_sha256.as_bytes());
digest.update(frame.logical_width.to_le_bytes());
digest.update(frame.logical_height.to_le_bytes());
for value in frame.origin {
digest.update(value.to_le_bytes());
}
digest.update([u8::from(requires_alpha), u8::from(frame.crop.is_some())]);
if let Some(crop) = frame.crop {
for value in crop {
digest.update(value.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() {
digest.update(b"raptor-hd-frame-presentation-policy-v1");
digest.update([u8::from(frame.preserve_reserved_coverage)]);
digest.update((frame.native_nearest_cues.len() as u32).to_le_bytes());
for cue in &frame.native_nearest_cues {
for coordinate in cue {
digest.update(coordinate.to_le_bytes());
}
}
}
if !frame.original_detail_cells.is_empty() {
digest.update(b"raptor-retained-original-details-v1");
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()); }
}
}
digest.finalize().into()
}
#[test]
fn painted_domain_nonempty_keys_are_versioned_while_defaults_are_identical() {
let f=frame();let info=PngInfo{size:1,modified:None,digest:[1;32],width:8,height:8};
assert_eq!(HdAssets::frame_key(&f,&info,false),ordinary_only_v1_frame_key(&f,&info,false));
let mut selected=f;selected.original_detail_cells=vec![[0,0]];
assert_ne!(HdAssets::frame_key(&selected,&info,false),ordinary_only_v1_frame_key(&selected,&info,false));
}
}
+103
View File
@@ -58,6 +58,7 @@ pub enum OverlayResult {
Applied,
InvalidCanvas,
NonOpaque,
InvalidOriginalDetails,
}
pub struct Replacement {
@@ -66,6 +67,10 @@ pub struct Replacement {
pub position: [i32; 2],
pub logical_size: [usize; 2],
}
pub struct RetainedOriginalPlacement {
pub replacement_index: usize,
pub details: crate::hd::OriginalDetailCells,
}
pub struct Composition {
pub image: Sprite,
pub results: Vec<OverlayResult>,
@@ -120,6 +125,13 @@ impl TerrainHd {
scale: usize,
palette: &[[u8; 3]; 256],
) -> 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.
pub fn compose_retaining_original(&mut self, context: &Sprite,
replacements: &[Replacement], retained: &[RetainedOriginalPlacement],
scale: usize, palette: &[[u8; 3]; 256]) -> Composition {
let mut image = context.clone();
let mut results = Vec::with_capacity(replacements.len());
let mut accepted = BTreeMap::new();
@@ -133,6 +145,30 @@ impl TerrainHd {
if accepted.is_empty() {
return Composition { image, results };
}
// Validate/project all accepted protected footprints before composition.
// Conflicting ownership fails the entire opt-in context to Original;
// default/no-retention composition remains exactly the old route.
let mut protected = BTreeMap::new();
let valid_details = retained.iter().all(|retention| {
let Some(tile) = replacements.get(retention.replacement_index) else { return false };
if accepted.get(&(tile.position, tile.logical_size)) != Some(&retention.replacement_index) { return true; }
if retention.details.logical_size() != tile.logical_size { return false; }
for &[x, y] in retention.details.cells() {
if x as usize >= tile.logical_size[0] || y as usize >= tile.logical_size[1] { return false; }
for dy in 0..scale { for dx in 0..scale {
let xx = (tile.position[0] as i64 + x as i64) * scale as i64 + dx as i64;
let yy = (tile.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 pixel = yy as usize * context.width + xx as usize;
if protected.insert(pixel, retention.replacement_index)
.is_some_and(|owner| owner != retention.replacement_index) { return false; }
}}
}
true
});
if !valid_details {
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.
@@ -257,6 +293,9 @@ impl TerrainHd {
}
}
}
// AFTER all tile edges/corners: copy authentic joined Original context.
// These samples were never read by shared_target/tile_edges.
for pixel in protected.keys() { image.pixels[*pixel] = context.pixels[*pixel]; }
Composition { image, results }
}
@@ -609,3 +648,67 @@ fn edge_weight(distance: usize, band: usize) -> usize {
let denominator = band * band * band;
(distance * distance * (3 * band - 2 * distance) * (LEVELS - 1) + denominator / 2) / denominator
}
#[cfg(test)]
mod retained_original_join_tests {
use super::*;
fn detail(index:usize,cells:Vec<[u32;2]>) -> RetainedOriginalPlacement {
RetainedOriginalPlacement {replacement_index:index,
details:crate::hd::OriginalDetailCells::for_test(cells,[32,32])}
}
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() {
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 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]});
}}
let empty=t.compose_retaining_original(&context,&tiles,&[],scale,&palette()).image;
assert_eq!(base,empty);
}
}
#[test]
fn duplicate_protected_owners_fail_to_original_not_last_write() {
let context=Sprite{width:64,height:32,pixels:vec![17;64*32]};
let tiles=vec![Replacement{image:Sprite{width:32,height:32,pixels:vec![60;1024]},position:[0,0],logical_size:[32,32]},
Replacement{image:Sprite{width:32,height:32,pixels:vec![80;1024]},position:[1,0],logical_size:[32,32]}];
let result=TerrainHd::default().compose_retaining_original(&context,&tiles,
&[detail(0,vec![[1,0]]),detail(1,vec![[0,0]])],1,&palette());
assert_eq!(result.image,context);assert!(result.results.iter().all(|r|*r==OverlayResult::InvalidOriginalDetails));
}
#[test]
fn reserved_detail_after_joins_preserves_functional_labels_and_never_leaks_to_neighbors() {
let mut logical=Sprite{width:64,height:32,pixels:vec![17;64*32]};
logical.pixels[15*64+31]=252;logical.pixels[16*64+31]=253;
logical.pixels[15*64+32]=243;logical.pixels[16*64+32]=240;
for scale in 1..=4 {
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 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];
let rgba1=crate::render::Canvas{image:hybrid.clone(),scale}.rgba(&phase,false);
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]});
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;
let retained_mixed=compositor.compose_retaining_original(&context,mixed,&[detail(0,vec![[31,15],[31,16]])],scale,&palette()).image;
for y in 0..context.height {for x in 32*scale..context.width {
let i=y*context.width+x;assert_eq!(retained_mixed.pixels[i],base_mixed.pixels[i],"foreign Original fallback unchanged");
}}
}
}
}
+20 -1
View File
@@ -42,8 +42,27 @@ class PythonRoundingMirror(unittest.TestCase):
raw=Image.new('RGBA',(161,101),(0,0,0,255));raw.putpixel((160,100),(0,0,0,alpha))
raw.save(self.root/'fixture.png')
with self.assertRaisesRegex(ValueError,'fully opaque'):self.check()
def test_transparent_original_cannot_use_rounding(self):
def test_transparent_original_rounding_keeps_the_alpha_background_guard(self):
self.picture.putpixel((0,0),(0,0,0,0));self.artwork((17,11))
# Full-frame integer rounding is eligible for alpha Source; an opaque
# PNG still fails the unchanged authentic transparent-background guard.
with self.assertRaisesRegex(ValueError,'transparent HD background'):self.check()
raw=Image.open(self.root/'fixture.png').convert('RGBA')
raw.putpixel((0,0),(0,0,0,0));raw.save(self.root/'fixture.png')
self.assertEqual(self.check(),17*11*4)
def test_transparent_rounding_rejects_bad_aspect_partial_crop_and_half_tie(self):
self.picture.putpixel((0,0),(0,0,0,0))
def alpha_art(size):
raw=Image.new('RGBA',size,(80,100,120,255))
raw.putpixel((0,0),(0,0,0,0));raw.save(self.root/'fixture.png')
alpha_art((21,11))
with self.assertRaisesRegex(ValueError,'aspect differs'):self.check()
alpha_art((18,12));self.frame['crop']=[0,0,17,11]
with self.assertRaisesRegex(ValueError,'aspect differs'):self.check()
self.picture=Image.new('RGBA',(2,2),(80,100,120,255))
self.picture.putpixel((0,0),(0,0,0,0))
self.frame.update(logical_width=2,logical_height=2);self.frame.pop('crop')
alpha_art((5,6))
with self.assertRaisesRegex(ValueError,'aspect differs'):self.check()
def test_partial_crop_never_gets_exception(self):
self.artwork((18,12))
+196
View File
@@ -0,0 +1,196 @@
"""Pure Source/schema/registrar tests; no PNG body, compiler, renderer or writer."""
import ast
import copy
import hashlib
import importlib.util
import json
import pathlib
import struct
import subprocess
import sys
import tempfile
import unittest
from unittest import mock
TOOLS = pathlib.Path(__file__).resolve().parents[1] / "tools"
sys.path.insert(0, str(TOOLS))
import hd_frame_policy as policy
import hd_candidate_review as candidate
import hd_register_reviewed as registrar
from PIL import Image
VERSION = "retained-original-details-v2-painted0255"
def payload(values=(19, 20, 18, 18)):
return struct.pack("<5i", 1, 0, 0, 2, 2) + bytes(values)
def frame(raw=None):
return dict(frame=0, file="raw.png", logical_width=2, logical_height=2,
origin=[0, 0], source_sha256=hashlib.sha256(raw or payload()).hexdigest())
def selected(raw=None):
result = frame(raw)
result["original_detail_cells"] = [[0, 0]]
return result
class PaintedPolicy(unittest.TestCase):
def test_every_painted_Source_label_accepted(self):
for index in range(256):
raw = payload((index, 20, 18, 18))
with self.subTest(index=index):
policy.validate_frame_policy(selected(raw), raw)
def test_sparse_transparent_and_outofrange_Source_rejected(self):
sparse = (struct.pack("<5i", 0, 0, 0, 2, 2)
+ struct.pack("<4i", 1, 0, 1, 1) + bytes([19])
+ struct.pack("<4i", 0, 0, -1, 0))
with self.assertRaises(ValueError):
policy.validate_frame_policy(selected(sparse), sparse)
for invalid in (-1, 256, 257, 65535):
with self.subTest(index=invalid), mock.patch.object(policy, "_indices", return_value=(2, 2, [invalid, 20, 18, 18])):
with self.assertRaises(ValueError):
policy.validate_frame_policy(selected(), payload())
def test_old_coordinate_order_bound_capacity_types_unchanged(self):
for cells in (True, [[True, 0]], [[0.0, 0]], [[-1, 0]], [[2, 0]],
[[0, 2]], [[0, 0], [0, 0]], [[1, 0], [0, 0]], [[0, 0]] * 65):
f = frame(); f["original_detail_cells"] = cells
with self.subTest(cells=cells), self.assertRaises(ValueError):
policy.validate_frame_policy(f, payload())
f = selected(); f["native_nearest_cues"] = [[0, 0]]
with self.assertRaises(ValueError):
policy.validate_frame_policy(f, payload())
def test_detail_domain_never_expands_PNG_nearest_domain(self):
for index in range(240, 256):
raw = payload((index, 20, 18, 18))
policy.validate_frame_policy(selected(raw), raw)
f = frame(raw); f["native_nearest_cues"] = [[0, 0]]
with self.subTest(index=index), self.assertRaises(ValueError):
policy.validate_frame_policy(f, raw)
def test_absent_empty_defaults_and_Source_dimension_guard(self):
policy.validate_frame_policy(frame(), payload())
f = frame(); f["original_detail_cells"] = []
policy.validate_frame_policy(f, payload())
bad = selected(); bad["logical_width"] = 3
with self.assertRaises(ValueError):
policy.validate_frame_policy(bad, payload())
def test_candidate_digest_binds_cells_and_default_fields(self):
f = selected(payload((252, 253, 18, 18)))
c = dict(format_version=1, sequences=[dict(name="synthetic retained Original", base_asset=1, frame_count=1, frames=[f])])
rows = candidate.proposal_frames(c)
self.assertEqual(rows["00001"]["original_detail_cells"], [[0, 0]])
for changed in ([], [[1, 0]]):
other = copy.deepcopy(c); other["sequences"][0]["frames"][0]["original_detail_cells"] = changed
self.assertNotEqual(candidate.binding_digest(rows), candidate.binding_digest(candidate.proposal_frames(other)))
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))
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())
class ActualRegistrarProvenance(unittest.TestCase):
"""Run the real unchanged entry point on a synthetic file-only complete family.
The fixture is not an asset/QA approval. No standalone branch, Catalog decode,
PNG body, registrar main or actual writer is exercised.
"""
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="painted-original-pure-")
self.addCleanup(self.temp.cleanup)
self.directory = pathlib.Path(self.temp.name)
self.project = self.directory / "project"
(self.project / "src").mkdir(parents=True)
for name in ("assets.rs", "hd.rs"):
(self.project / "src" / name).write_bytes(b"synthetic Source pin")
(self.project / "target").mkdir()
self.library = self.project / "target/synthetic.rlib"
self.library.write_bytes(b"synthetic library identity, never executed")
self.root = self.directory / "data/hd"
self.job_dir = self.root / "jobs/synthetic-complete1"
self.job_dir.mkdir(parents=True)
self.f = selected(payload((252, 253, 18, 18)))
self.candidate = dict(format_version=1, job_id="synthetic-complete1", complete_family=True,
sequences=[dict(name="synthetic retained Original", base_asset=1, frame_count=1, frames=[self.f])])
self.rows = candidate.proposal_frames(self.candidate)
digest = candidate.binding_digest(self.rows)
self.review = dict(status="approved", frame_bindings_sha256=digest,
source_and_all_scale_visual_review="synthetic only; no asset approval",
independent_review="synthetic only; no actual reviewer",
runtime_qa="qa.json", retained_original_detail_review=dict(
cells_by_asset={"00001": [[0, 0]]}, retained_original_not_generated_HD=True,
policy_version=VERSION, painted_Source_palette_domain=[0, 255],
functional_palette_effects_are_retained_Original=True,
hybrid_allscale_and_genuine_context_independent_review=True,
frame_bindings_sha256=digest))
counts = [0] * 16; counts[12] = 1
qa = dict(frame_bindings_sha256=digest, diagnostics=[], source_newer_than_library=False,
any_game_source_newer_than_library=False, library="target/synthetic.rlib",
library_modified_ns=self.library.stat().st_mtime_ns,
library_sha256=hashlib.sha256(self.library.read_bytes()).hexdigest(),
frames=[dict(asset="00001", source_sha256=self.f["source_sha256"], logical_canvas=[2, 2],
source_mask=dict(palette_indices_240_to_255_counts=counts),
hd_masks=[dict(scale=s, mask=dict(palette_indices_240_to_255_counts=counts)) for s in range(1, 5)])],
artwork_sha256={"raw.png": hashlib.sha256(b"opaque synthetic non-image bytes; never decoded").hexdigest()})
(self.root / "raw.png").write_bytes(b"opaque synthetic non-image bytes; never decoded")
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=[2, 2])]))
self.write_json(self.root / "qa.json", qa)
@staticmethod
def write_json(path, value):
path.write_text(json.dumps(value, sort_keys=True) + "\n")
def approve(self, declaration=None):
review = copy.deepcopy(self.review)
if declaration is not None:
review["retained_original_detail_review"] = declaration
self.write_json(self.job_dir / "coordinator-review.json", review)
with mock.patch.object(registrar, "main", side_effect=AssertionError("main forbidden")), \
mock.patch.object(registrar, "atomic_json", side_effect=AssertionError("writer forbidden")), \
mock.patch.object(registrar, "decode", side_effect=AssertionError("Source decoder forbidden")), \
mock.patch.object(registrar, "validate_hd_frame", side_effect=AssertionError("art audit forbidden")), \
mock.patch.object(subprocess, "run", side_effect=AssertionError("process forbidden")), \
mock.patch.object(Image, "open", side_effect=AssertionError("PNG decoder forbidden")):
return registrar.approved_job(self.root, "synthetic-complete1", self.project)
def test_real_approved_job_accepts_exact_explicit_painted_provenance(self):
_, review, rows = self.approve()
self.assertEqual(rows, self.rows)
self.assertIs(review["retained_original_detail_review"]["functional_palette_effects_are_retained_Original"], True)
def test_real_approved_job_rejects_every_missing_provenance_field(self):
declaration = self.review["retained_original_detail_review"]
for key in declaration:
bad = copy.deepcopy(declaration); del bad[key]
with self.subTest(key=key), self.assertRaisesRegex(ValueError, "retained Original detail provenance"):
self.approve(bad)
def test_real_approved_job_rejects_wrong_version_domain_truth_and_cells(self):
mutations = dict(policy_version="retained-original-details-v1", painted_Source_palette_domain=[False, 255],
functional_palette_effects_are_retained_Original=1, retained_original_not_generated_HD=1,
hybrid_allscale_and_genuine_context_independent_review=1,
cells_by_asset={"00001": [[1, 0]]}, frame_bindings_sha256="0" * 64)
for key, value in mutations.items():
bad = copy.deepcopy(self.review["retained_original_detail_review"]); bad[key] = value
with self.subTest(key=key), self.assertRaisesRegex(ValueError, "retained Original detail provenance"):
self.approve(bad)
def test_real_approved_job_never_requires_provenance_for_default_frames(self):
self.candidate["sequences"][0]["frames"][0].pop("original_detail_cells")
rows = candidate.proposal_frames(self.candidate); digest = candidate.binding_digest(rows)
self.write_json(self.job_dir / "candidate.json", self.candidate)
self.review.pop("retained_original_detail_review")
self.review["frame_bindings_sha256"] = digest
qa = json.loads((self.root / "qa.json").read_text()); qa["frame_bindings_sha256"] = digest
self.write_json(self.root / "qa.json", qa)
_, review, actual = self.approve()
self.assertNotIn("retained_original_detail_review", review)
self.assertEqual(actual, rows)
if __name__ == "__main__":
unittest.main(verbosity=2)
+21 -1
View File
@@ -2,7 +2,7 @@
import struct
FRAME_KEYS = {"frame", "file", "logical_width", "logical_height", "origin",
"source_sha256", "crop", "preserve_reserved_coverage", "native_nearest_cues"}
"source_sha256", "crop", "preserve_reserved_coverage", "native_nearest_cues", "original_detail_cells"}
def _indices(payload):
if len(payload) < 20:
@@ -66,3 +66,23 @@ def validate_frame_policy(frame, source_payload=None):
raise ValueError("native nearest cues require an opaque original canvas")
if any(indices[y * width + x] >= 240 for x, y in cues):
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")
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):
raise ValueError("Original detail must be a pair of u32 coordinates")
key = tuple(cell)
if cell[0] >= width or cell[1] >= height or (previous is not None and key <= previous):
raise ValueError("Original details must be inbounds sorted unique cells")
if cell in cues:
raise ValueError("Original detail and PNG-nearest policies may not overlap")
previous = key
if source_payload is not None and details:
source_width, source_height, indices = _indices(source_payload)
if (source_width, source_height) != (width, height):
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")
+12
View File
@@ -344,6 +344,18 @@ def approved_job(root, identity, project):
raise ValueError(f"artistic approval belongs to another frame map: {identity}")
if not review.get("source_and_all_scale_visual_review") or not review.get("independent_review"):
raise ValueError(f"missing complete visual/independent review: {identity}")
retained_details = {asset: frame["original_detail_cells"] for asset, frame in rows.items()
if frame.get("original_detail_cells")}
if retained_details:
declaration = review.get("retained_original_detail_review", {})
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 same_json(declaration.get("painted_Source_palette_domain"), [0, 255])
or declaration.get("functional_palette_effects_are_retained_Original") is not True
or declaration.get("hybrid_allscale_and_genuine_context_independent_review") is not True
or declaration.get("frame_bindings_sha256") != digest):
raise ValueError("retained Original detail provenance and exact hybrid review are required")
qa_path = contained(root, review["runtime_qa"])
qa = json.loads(qa_path.read_text())
if qa.get("frame_bindings_sha256") != digest or qa.get("diagnostics"):