Add Iron Coast II Black Tide eight-mission campaign

This commit is contained in:
raptorreplica committed 2026-10-11 20:08:01 +11:00
1 parent a08b932beb
commit 71cb7cdba3
4492 files changed
+164577 -45

No files matched your search

@@ -0,0 +1,901 @@
{
"difficulty": 2,
"fixture_health": 100000,
"rate": "70/3 Hz",
"rows": [
{
"damage": [
1344
],
"group_dps": 44.8,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1344
],
"group_dps": 44.8,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1344,
1024,
704,
0
],
"group_dps": 102.4,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1344,
0,
0,
0,
0,
0
],
"group_dps": 44.8,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
2038
],
"group_dps": 67.93333333333334,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
2038
],
"group_dps": 67.93333333333334,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
2040,
1024,
704,
0
],
"group_dps": 125.6,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
2038,
0,
0,
0,
0,
0
],
"group_dps": 67.93333333333334,
"item": "iron_coast_railburst:equipment",
"price": 685000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1080
],
"group_dps": 36.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1080,
0,
0,
0
],
"group_dps": 36.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1080,
540,
300,
300,
0,
0
],
"group_dps": 74.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
694
],
"group_dps": 23.133333333333333,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1774
],
"group_dps": 59.13333333333333,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1776,
0,
0,
0
],
"group_dps": 59.2,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1774,
540,
300,
300,
0,
0
],
"group_dps": 97.13333333333334,
"item": "iron_coast_depth_charge:equipment",
"price": 525000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
800
],
"group_dps": 26.666666666666668,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
800
],
"group_dps": 26.666666666666668,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
800,
800,
800,
800
],
"group_dps": 106.66666666666667,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
800,
800,
0,
0,
0,
800
],
"group_dps": 80.0,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1494
],
"group_dps": 49.8,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1494
],
"group_dps": 49.8,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1496,
800,
800,
800
],
"group_dps": 129.86666666666667,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1494,
800,
0,
0,
0,
800
],
"group_dps": 103.13333333333334,
"item": "ion_lance:equipment",
"price": 425000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1248
],
"group_dps": 41.6,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1248
],
"group_dps": 41.6,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1248,
780,
468,
312
],
"group_dps": 93.6,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1248,
468,
312,
0,
0,
780
],
"group_dps": 93.6,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
1942
],
"group_dps": 64.73333333333333,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1942
],
"group_dps": 64.73333333333333,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1944,
780,
468,
312
],
"group_dps": 116.8,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
1942,
468,
312,
0,
0,
780
],
"group_dps": 116.73333333333333,
"item": "lightning_cannon:equipment",
"price": 825000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": true
},
{
"damage": [
694
],
"group_dps": 23.133333333333333,
"item": "base:forward_guns",
"price": 12000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
694
],
"group_dps": 23.133333333333333,
"item": "base:forward_guns",
"price": 12000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "base:forward_guns",
"price": 12000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "base:forward_guns",
"price": 12000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
696,
0,
0,
0
],
"group_dps": 23.2,
"item": "base:forward_guns",
"price": 12000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
694,
0,
0,
0,
0,
0
],
"group_dps": 23.133333333333333,
"item": "base:forward_guns",
"price": 12000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
2400
],
"group_dps": 80.0,
"item": "base:death_ray",
"price": 950000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
2400
],
"group_dps": 80.0,
"item": "base:death_ray",
"price": 950000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "base:death_ray",
"price": 950000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "base:death_ray",
"price": 950000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
2400,
0,
0,
0
],
"group_dps": 80.0,
"item": "base:death_ray",
"price": 950000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
2400,
0,
0,
0,
0,
0
],
"group_dps": 80.0,
"item": "base:death_ray",
"price": 950000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
8000
],
"group_dps": 266.6666666666667,
"item": "base:forward_laser",
"price": 1750000,
"scene": "aligned_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
8000
],
"group_dps": 266.6666666666667,
"item": "base:forward_laser",
"price": 1750000,
"scene": "aligned_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "base:forward_laser",
"price": 1750000,
"scene": "off_axis_air",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
0
],
"group_dps": 0.0,
"item": "base:forward_laser",
"price": 1750000,
"scene": "off_axis_ground",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
8000,
0,
0,
0
],
"group_dps": 266.6666666666667,
"item": "base:forward_laser",
"price": 1750000,
"scene": "aligned_column",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
},
{
"damage": [
8000,
0,
0,
0,
0,
0
],
"group_dps": 266.6666666666667,
"item": "base:forward_laser",
"price": 1750000,
"scene": "ground_cluster",
"seconds": 30,
"ticks": 700,
"with_forward_guns": false
}
],
"target_size": [
32,
32
],
"package": "iron_coast_railburst",
"final_price": 685000,
"resale_price": 342500,
"pricing_reason": "44.8 single air/ground DPS and102.4 finite three-target aligned-column DPS, zero off-axis damage. Higher single-target throughput than425,000 Ion Lance26.67, but tighter initial acquisition and finite penetration compared with825,000 Lightning Cannon41.6 single/93.6 chain and wider acquisition. Below950,000 Death Ray80 single and1,750,000 Forward Laser266.67. Ion Lance can exceed this finite penetration against four or more perfectly stacked targets; this is a different aiming/cadence choice, not an upgrade over every role.",
"observation": "Actual World::step700ticks; deterministic stationary32x32 health100000 fixtures, ground scroll canceled after normal collision pass. Native original and new weapons unmodified. Forward-gun combination separately observed. Does not measure campaign difficulty or moving-target accuracy.",
"source_input_hashes": {
"mod.toml": "e22f6dd8e8aaeb7fe1549cdddc2be28fcda71a16f9285c975701e77a009faf84",
"content.json": "de70d59f723cce78ca4b59a3ecce704ce877517d91d83a06526daed7f42d4484",
"scripts/weapon.rhai": "209ae40d88b03bda4802c1970852d791f84b48986990d70c0e3dbcf502ba5b9b"
}
}
@@ -0,0 +1,145 @@
//! Read-only public engine asset/audio review for both independent Stage 2 mods.
//! Writes captures only to the caller's evidence directory, never to game data.
use image::{RgbaImage, imageops};
use raptor_replica::{
assets::{Catalog, Sprite, TRANSPARENT},
audio::Mixer,
content::{ContentId, Registry, art_sprite_variant},
modding::{self, ActiveMods},
render::{Canvas, nearest},
sound::{SoundBank, encode_capture},
};
use serde_json::json;
use std::{env, fs, path::Path};
fn normalized(sprite: &Sprite, w: usize, h: usize) -> Sprite {
let mut out = Sprite::blank(w, h);
for y in 0..h {
for x in 0..w {
out.pixels[y * w + x] =
sprite.pixels[(y * sprite.height / h) * sprite.width + x * sprite.width / w];
}
}
out
}
fn main() {
let args: Vec<_> = env::args().collect();
assert_eq!(args.len(), 4, "usage: native_review DATA MODS EVIDENCE");
let catalog = Catalog::open(&args[1]).unwrap();
let base = Registry::base(&catalog).unwrap();
let ids = ["iron_coast_railburst", "iron_coast_depth_charge"].map(String::from);
let discovery = modding::discover(Path::new(&args[2]));
let active = ActiveMods::build(&base, &discovery, &ids, Some(&catalog)).unwrap();
let out = Path::new(&args[3]);
fs::create_dir_all(out).unwrap();
let palette = catalog.palette().unwrap();
let mut rows = Vec::new();
let mut gallery = RgbaImage::new(1024, 1024);
let mut row = 0;
for owner in &ids {
for kind in ["equipment", "projectile"] {
let id: ContentId = format!(
"{owner}:{}",
if kind == "equipment" {
"equipment"
} else {
"projectile"
}
)
.as_str()
.into();
let art = active.registry.art(&id).unwrap();
for hd in [false, true] {
let source =
art_sprite_variant(&catalog, &active.registry, owner, art, 0, hd).unwrap();
assert!(
source
.pixels
.iter()
.filter(|c| **c != TRANSPARENT)
.all(|c| *c < 240)
);
assert!(
source.pixels[0] == TRANSPARENT
&& source.pixels[source.width - 1] == TRANSPARENT
);
let mut counts = Vec::new();
for scale in 1..=4 {
// The same canvas sizing and nearest resampling used by Host::art.
let display = if source.width == art.logical_width as usize
&& source.height == art.logical_height as usize
{
nearest(&source, scale)
} else {
normalized(
&source,
art.logical_width as usize * scale,
art.logical_height as usize * scale,
)
};
counts.push(json!({"scale":scale,"width":display.width,"height":display.height,"painted":display.pixels.iter().filter(|c|**c!=TRANSPARENT).count()}));
let mut canvas = Canvas::new(scale);
canvas.clear(12);
canvas.rect(160, 0, 160, 200, 93);
for (x, y) in [(70, 90), (230, 90)] {
canvas.blit(&display, x * scale as i32, y * scale as i32);
}
let rgba = RgbaImage::from_raw(
canvas.image.width as u32,
canvas.image.height as u32,
canvas.rgba(&palette, false),
)
.unwrap();
rgba.save(out.join(format!(
"{owner}-{kind}-{}-{scale}x.png",
if hd { "hd" } else { "sd" }
)))
.unwrap();
}
let display = normalized(
&source,
art.logical_width as usize * 8,
art.logical_height as usize * 8,
);
let mut cell = Canvas::new(1);
cell.clear(12);
cell.rect(160, 0, 160, 200, 93);
cell.blit(&display, 20, 20);
cell.blit(&display, 180, 20);
let rgba = RgbaImage::from_raw(320, 200, cell.rgba(&palette, false)).unwrap();
imageops::overlay(
&mut gallery,
&rgba,
if hd { 512 } else { 0 },
(row * 256) as i64,
);
rows.push(json!({"owner":owner,"kind":kind,"hd":hd,"physical_canvas":[source.width,source.height],"logical_canvas":[art.logical_width,art.logical_height],"frame":0,"frames":art.frames,"ordinary_palette_only":true,"clear_corner_alpha":true,"all_scales":counts}));
}
row += 1;
}
}
gallery.save(out.join("indexed-sd-hd-gallery.png")).unwrap();
let instruments = catalog.named("GENMIDI_OP2").unwrap();
let mut audio = Vec::new();
for owner in &ids {
for kind in ["fire", "impact"] {
let id: ContentId = format!("{owner}:{kind}").as_str().into();
let mut bank = SoundBank::default();
let sample = bank.mod_sample(&active.registry, &id).unwrap();
assert!(!sample.data.is_empty());
let mut mixer = Mixer::new(44100, &instruments).unwrap();
mixer.start_capture(1).unwrap();
bank.queue_mod(&active.registry, &mut mixer, &id).unwrap();
let mut samples = vec![0; 44100 * 2];
mixer.mix(&mut samples);
assert!(samples.iter().any(|x| *x != 0));
let file = format!("{owner}-{kind}-mixed.wav");
fs::write(
out.join(&file),
encode_capture(&mixer.take_capture().unwrap()).unwrap(),
)
.unwrap();
audio.push(json!({"id":id,"decoder":"SoundBank::mod_sample","mixer":"Mixer::sample through SoundBank::queue_mod","source_rate":sample.rate,"decoded_samples":sample.data.len(),"mixed_rate":44100,"mixed_channels":2,"nonzero_output_samples":samples.iter().filter(|x|**x!=0).count(),"peak":samples.iter().map(|x|(*x as i32).abs()).max().unwrap(),"capture":file}));
}
}
fs::write(out.join("native-review.json"),serde_json::to_vec_pretty(&json!({"scope":"Public PNG decoder, same Host art normalization, indexed Canvas blitter and actual SoundBank/Mixer. Synthetic backgrounds; no live desktop/gameplay claim.","assets":rows,"audio":audio})).unwrap()).unwrap();
}
@@ -0,0 +1,283 @@
{
"package": "iron_coast_railburst",
"portable_tests": {
"file": "tests/iron_coast_stage2_weapons.rs",
"passed": 9,
"balance_integration_passed": 1,
"cases": [
"independent activation and coexistence",
"shop half-price resale and duplicate rejection",
"rail aligned contact finite penetration",
"depth contact-only ground radius/falloff/cap",
"unselected/idle/misses without explosions",
"serialized state/RNG continuation and invalid-command rollback",
"explicit cooldown/reusable inventory",
"lethal primary contact continuation",
"full-slot inventory preservation",
"disable/re-enable saved equipment, exact mod pins and script state preserved without altering save"
]
},
"native_decoder_blitter_audio": {
"assets": [
{
"all_scales": [
{
"height": 16,
"painted": 94,
"scale": 1,
"width": 16
},
{
"height": 32,
"painted": 376,
"scale": 2,
"width": 32
},
{
"height": 48,
"painted": 846,
"scale": 3,
"width": 48
},
{
"height": 64,
"painted": 1504,
"scale": 4,
"width": 64
}
],
"clear_corner_alpha": true,
"frame": 0,
"frames": 1,
"hd": false,
"kind": "equipment",
"logical_canvas": [
16,
16
],
"ordinary_palette_only": true,
"owner": "iron_coast_railburst",
"physical_canvas": [
16,
16
]
},
{
"all_scales": [
{
"height": 16,
"painted": 92,
"scale": 1,
"width": 16
},
{
"height": 32,
"painted": 361,
"scale": 2,
"width": 32
},
{
"height": 48,
"painted": 809,
"scale": 3,
"width": 48
},
{
"height": 64,
"painted": 1434,
"scale": 4,
"width": 64
}
],
"clear_corner_alpha": true,
"frame": 0,
"frames": 1,
"hd": true,
"kind": "equipment",
"logical_canvas": [
16,
16
],
"ordinary_palette_only": true,
"owner": "iron_coast_railburst",
"physical_canvas": [
64,
64
]
},
{
"all_scales": [
{
"height": 20,
"painted": 26,
"scale": 1,
"width": 4
},
{
"height": 40,
"painted": 104,
"scale": 2,
"width": 8
},
{
"height": 60,
"painted": 234,
"scale": 3,
"width": 12
},
{
"height": 80,
"painted": 416,
"scale": 4,
"width": 16
}
],
"clear_corner_alpha": true,
"frame": 0,
"frames": 1,
"hd": false,
"kind": "projectile",
"logical_canvas": [
4,
20
],
"ordinary_palette_only": true,
"owner": "iron_coast_railburst",
"physical_canvas": [
4,
20
]
},
{
"all_scales": [
{
"height": 20,
"painted": 21,
"scale": 1,
"width": 4
},
{
"height": 40,
"painted": 87,
"scale": 2,
"width": 8
},
{
"height": 60,
"painted": 200,
"scale": 3,
"width": 12
},
{
"height": 80,
"painted": 352,
"scale": 4,
"width": 16
}
],
"clear_corner_alpha": true,
"frame": 0,
"frames": 1,
"hd": true,
"kind": "projectile",
"logical_canvas": [
4,
20
],
"ordinary_palette_only": true,
"owner": "iron_coast_railburst",
"physical_canvas": [
16,
80
]
}
],
"audio": [
{
"capture": "iron_coast_railburst-fire-mixed.wav",
"decoded_samples": 3528,
"decoder": "SoundBank::mod_sample",
"id": "iron_coast_railburst:fire",
"mixed_channels": 2,
"mixed_rate": 44100,
"mixer": "Mixer::sample through SoundBank::queue_mod",
"nonzero_output_samples": 6320,
"peak": 3200,
"source_rate": 22050
},
{
"capture": "iron_coast_railburst-impact-mixed.wav",
"decoded_samples": 5071,
"decoder": "SoundBank::mod_sample",
"id": "iron_coast_railburst:impact",
"mixed_channels": 2,
"mixed_rate": 44100,
"mixer": "Mixer::sample through SoundBank::queue_mod",
"nonzero_output_samples": 11168,
"peak": 2432,
"source_rate": 22050
}
],
"scope": "Public PNG decoder, same Host art normalization, indexed Canvas blitter and actual SoundBank/Mixer. Synthetic backgrounds; no live desktop/gameplay claim."
},
"visual_inspection": "Inspected original style references separately, all four raw masters and actual indexed SD/HD gallery at logical and4x canvases. Readable compact silhouettes, neutral metal with cyan rail/orange charge roles, transparent borders. SD colors intentionally simplified by reduction; no reserved palette effects. Captures use synthetic palette backgrounds, not a live mission or actual shop pane.",
"limitations": [
"Private desktop Host/shop screenshots are delegated to root integration review.",
"Native point collision may skip exceptionally tiny targets for the speed16 rail shot.",
"Terrain cells receive native primary damage but are not available as scripted penetration/splash snapshots."
],
"evidence_gallery_sha256": "c1b5d6ad1d286eb17fab1067143222a6071dff5e70a35d904757275767ec5a26",
"reproduction_byte_equality": {
"art/equipment-sd.png": true,
"art/equipment-hd.png": true,
"art/projectile-sd.png": true,
"art/projectile-hd.png": true,
"audio/fire.wav": true,
"audio/impact.wav": true
},
"native_build_bindings": {
"libraries": {
"raptor_replica": {
"path": "/home/quintine/Projects/RaptorReplica/RustRaptor/target/debug/libraptor_replica.rlib",
"sha256": "4fd737d3c6c808fd5de1ecfc1f9a41b0ef37e82dc1a186f005b8dc9c2360ccd5"
},
"image": {
"path": "/home/quintine/Projects/RaptorReplica/RustRaptor/target/debug/deps/libimage-25e356663b4aa6b0.rlib",
"sha256": "ea709066ea4c8c625efe3bf7f3741d7a68abfb7ef4b304837f1f0da20e5e2d58"
},
"serde_json": {
"path": "/home/quintine/Projects/RaptorReplica/RustRaptor/target/debug/deps/libserde_json-1d17875658175ae0.rlib",
"sha256": "e463d4b704418cc5a168d73818662f7a801a77d96e8ef3d6a121e90375e4532f"
}
},
"review_source_sha256": "32c4e836b7a01d159251427b2203dcf0d40f43bebef1def2f13a571e845d8ca3",
"validation_cases": [
{
"enabled": [
"iron_coast_railburst"
],
"stdout": "Validated 1 mods; 0 override(s)",
"stderr": "",
"exit_code": 0
},
{
"enabled": [
"iron_coast_depth_charge"
],
"stdout": "Validated 1 mods; 0 override(s)",
"stderr": "",
"exit_code": 0
},
{
"enabled": [
"iron_coast_railburst",
"iron_coast_depth_charge"
],
"stdout": "Validated 2 mods; 0 override(s)",
"stderr": "",
"exit_code": 0
}
],
"scope": "Frozen package copies; public asset, indexed Canvas and audio APIs; no live desktop or pilot mutations."
}
}
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Reproduce isolated Stage 2 package validation and public indexed/audio review."""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--data", type=Path)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
root = Path(__file__).resolve().parents[3]
data = (args.data or root / "data").resolve()
output = args.output.resolve()
output.mkdir(parents=True, exist_ok=True)
packages = ["iron_coast_railburst", "iron_coast_depth_charge"]
subprocess.run(["cargo", "build", "--locked", "--no-default-features", "--lib", "--bin", "raptor-rust"], cwd=root, check=True)
deps = root / "target/debug/deps"
suffix = ".exe" if os.name == "nt" else ""
executable = output / ("native_review" + suffix)
command = ["rustc", "--edition=2024", str(Path(__file__).with_name("native_review.rs")), "-L", f"dependency={deps}", "-o", str(executable)]
libraries = {}
for name in ["raptor_replica", "image", "serde_json"]:
library = root / "target/debug/libraptor_replica.rlib" if name == "raptor_replica" else max(deps.glob(f"lib{name}-*.rlib"), key=lambda path: path.stat().st_mtime_ns)
command += ["--extern", f"{name}={library}"]
libraries[name] = {"path": str(library), "sha256": hashlib.sha256(library.read_bytes()).hexdigest()}
subprocess.run(command, cwd=root, check=True)
cases = []
with tempfile.TemporaryDirectory(prefix="iron-coast-stage2-review-") as folder:
frozen = Path(folder)
for package in packages:
shutil.copytree(root / "mods" / package, frozen / package)
for selected in [[packages[0]], [packages[1]], packages]:
result = subprocess.run([str(root / ("target/debug/raptor-rust" + suffix)), "--data", str(data), "--mods", str(frozen), "--validate-mods", ",".join(selected)], cwd=root, check=True, capture_output=True, text=True)
print(result.stdout, end="")
cases.append({"enabled": selected, "stdout": result.stdout.strip(), "stderr": result.stderr.strip(), "exit_code": result.returncode})
subprocess.run([str(executable), str(data), str(frozen), str(output)], cwd=root, check=True)
(output / "build-bindings.json").write_text(json.dumps({"libraries": libraries, "review_source_sha256": hashlib.sha256(Path(__file__).with_name("native_review.rs").read_bytes()).hexdigest(), "validation_cases": cases, "scope": "Frozen package copies; public asset, indexed Canvas and audio APIs; no live desktop or pilot mutations."}, indent=2) + "\n")
if __name__ == "__main__":
main()