Add Iron Coast II Black Tide eight-mission campaign
This commit is contained in:
1 parent
a08b932beb
commit
71cb7cdba3
4492 files changed
+164577
-45
No files matched your search
@@ -0,0 +1,63 @@
|
||||
# Railburst Cannon 1.0.0
|
||||
|
||||
Enable **Railburst Cannon** in Mods, apply at the main menu, then buy it in the
|
||||
shop for **685,000 credits**. It is an independent API 1 mod without dependencies
|
||||
or overrides. Maximum one; resale returns 342,500 credits. It occupies a normal
|
||||
inventory slot and never replaces or deletes another upgrade.
|
||||
|
||||
This is a **selectable, reusable special weapon**. Select it in Inventory or with
|
||||
Alt/Shift during a mission, then hold Ctrl to fire. Space still drops an owned
|
||||
mega bomb. Merely owning or selecting the cannon does not auto-fire it. No
|
||||
ammunition is consumed; always-equipped forward guns can fire alongside it.
|
||||
|
||||
A native straight rail slug travels 16 logical pixels per simulation tick,
|
||||
dealing 42 damage on its first real air or ground collision. That contact
|
||||
penetrates at most two further living enemies ahead for 32 and 22 damage. Their
|
||||
centers must stay within 12 pixels of the original firing x coordinate and within
|
||||
160 pixels ahead of the first target's center. The nearest forward target wins;
|
||||
stable entity order breaks ties. The primary projectile retires normally, while
|
||||
the two additional contacts resolve through bounded `on_hit` damage commands.
|
||||
This is finite contact-triggered penetration rather than a persistent beam.
|
||||
|
||||
The cooldown is 22 ticks, about 0.94 seconds at the original 70/3 Hz simulation
|
||||
rate. Aim carefully: it does not home, acquire offscreen enemies, turn sideways
|
||||
or clear the screen. Native point collision can miss exceptionally tiny targets
|
||||
between its 16-pixel steps. Destructible terrain receives native primary damage;
|
||||
the API exposes enemy snapshots, so terrain does not trigger scripted penetration.
|
||||
Small air/ground impacts and impact audio occur only after an enemy contact;
|
||||
empty-space shots produce fire audio and the projectile, without muzzle blasts.
|
||||
|
||||
The SD equipment/projectile canvases are 16×16 and 4×20, with detailed 4× HD
|
||||
variants at 64×64 and 16×80. Toggle HD artwork in Options. Both modes share logical
|
||||
placement and gameplay dimensions; the single-frame master has no animation
|
||||
misalignment. [Artwork provenance](art-provenance.json) binds untouched built-in
|
||||
generated masters, prompts, output hashes and the preparation recipe. Generated
|
||||
masters, final sprites, original procedural WAVs and reproduction inputs belong
|
||||
to this package; decoded original reference art is kept outside it.
|
||||
|
||||
`python3 generate_audio.py` reproduces the firing crack and short metal impact.
|
||||
`prepare_art.rs` reproduces SD/HD fitting from the included source PNGs. The
|
||||
shared read-only [native review helper](validation/review.py) builds this project,
|
||||
validates both independent packages and writes indexed art/audio captures to an
|
||||
explicit evidence directory. It requires the included original catalog for the
|
||||
engine palette and instruments and never changes pilots or selected mods.
|
||||
|
||||
From the RustRaptor directory:
|
||||
|
||||
```sh
|
||||
cargo test --locked --no-default-features --test iron_coast_stage2_weapons
|
||||
cargo run --locked --no-default-features -- --data data --mods mods \
|
||||
--validate-mods iron_coast_railburst
|
||||
IRON_COAST_STAGE2_BALANCE_REPORT=/tmp/stage2-balance.json \
|
||||
cargo test --locked --no-default-features --test iron_coast_stage2_weapons \
|
||||
real_catalog_700_tick_balance_and_coexistence_report -- --ignored --nocapture
|
||||
python3 mods/iron_coast_railburst/validation/review.py --output /tmp/stage2-native
|
||||
```
|
||||
|
||||
See [validation/balance.json](validation/balance.json) for controlled 700-tick
|
||||
single-target, off-axis, aligned-column, mixed-cluster and forward-gun combination
|
||||
observations, and [validation/review.json](validation/review.json) for the tested
|
||||
scope. These stationary fixtures establish the price/role comparison, not a claim
|
||||
about every campaign encounter. Script counters and RNG checkpoints serialize
|
||||
normally with pilot state; rejected callback commands roll back the whole tick.
|
||||
Read the [API](../../docs/modding-api.md) for save compatibility and sandbox limits.
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"tool": "built-in image_gen through imagegen skill",
|
||||
"skill": "/home/quintine/.codex/skills/.system/imagegen/SKILL.md",
|
||||
"style_reference_catalog_ids": [
|
||||
"102b6",
|
||||
"101cb"
|
||||
],
|
||||
"style_reference_delivery": "Original catalog inspected separately; decoded originals are not included in this mod. The generated artwork is newly authored.",
|
||||
"source_records": [
|
||||
"art/generation-equipment-v1.json",
|
||||
"art/generation-projectile-v1.json"
|
||||
],
|
||||
"prompt_records": "prompts.json",
|
||||
"conversion_recipe": "prepare_art.rs",
|
||||
"conversion": "Fit alpha>=128 source bounds preserving aspect into one logical pixel margin; SD Triangle reduction and HD4x Lanczos3; binary final alpha128. Raw masters remain byte-identical to builtin outputs. Faint master glow outside the fitting bounds is not a separate in-game effect.",
|
||||
"canvases": {
|
||||
"equipment_sd": [
|
||||
16,
|
||||
16
|
||||
],
|
||||
"equipment_hd": [
|
||||
64,
|
||||
64
|
||||
],
|
||||
"projectile_sd": [
|
||||
4,
|
||||
20
|
||||
],
|
||||
"projectile_hd": [
|
||||
16,
|
||||
80
|
||||
]
|
||||
},
|
||||
"animation_frames": 1,
|
||||
"palette": "Engine maps ordinary PNG artwork to original palette0..239; no reserved palette effects requested.",
|
||||
"audio_recipe": "generate_audio.py",
|
||||
"audio_provenance": "audio/provenance.json",
|
||||
"outputs": [
|
||||
{
|
||||
"path": "art/equipment-generated-source-v1.png",
|
||||
"sha256": "2576062b00f8bc29078b84ff69db9cb578dfcdc08911ba4bdf90038b2c94c834"
|
||||
},
|
||||
{
|
||||
"path": "art/equipment-hd.png",
|
||||
"sha256": "afe40548427f4bc1c29a4261f4cdc1ed559d8b11e72a849cde561e9ca7429f6a"
|
||||
},
|
||||
{
|
||||
"path": "art/equipment-sd.png",
|
||||
"sha256": "c1dc5c80e9034ae742370d852c42fad5d333fc1e1e461e2a7ad23e9fe04045cf"
|
||||
},
|
||||
{
|
||||
"path": "art/projectile-generated-source-v1.png",
|
||||
"sha256": "46d958d55814c9c9fe81cd6d22847f82e1e81f8ed6ca2ff855d155c7e3281d87"
|
||||
},
|
||||
{
|
||||
"path": "art/projectile-hd.png",
|
||||
"sha256": "367f8720f69439c68552ada49f52e279ecae69a989747f16dde363c6eebb112e"
|
||||
},
|
||||
{
|
||||
"path": "art/projectile-sd.png",
|
||||
"sha256": "91958044ce398a11ed56dfe8073d09d1dd718552a481f69d349fd0490d5211cf"
|
||||
}
|
||||
],
|
||||
"recipe_hashes": {
|
||||
"prepare_art.rs": "f1ac3c61649ed9327f4169b3c0aaee3faad4cc0ee7d2c798818a2555bd9e112e",
|
||||
"generate_audio.py": "1813efe19cd4c0a0914b7e41d41a5966cfcf246fabd0d318b60a65e184d3da96"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 979 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 489 B |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"tool": "built-in image_gen",
|
||||
"builtin_original_output_path": "/home/quintine/.codex/generated_images/01a129a5-0efd-7c61-8556-922e09c8ea6c/exec-cdcc2180-8730-4d67-b4f3-7772fc9d997e.png",
|
||||
"local_raw_copy": "equipment-generated-source-v1.png",
|
||||
"raw_sha256": "2576062b00f8bc29078b84ff69db9cb578dfcdc08911ba4bdf90038b2c94c834",
|
||||
"copy_sha256": "2576062b00f8bc29078b84ff69db9cb578dfcdc08911ba4bdf90038b2c94c834",
|
||||
"exact_byte_equality": true,
|
||||
"prompt_file": "prompt-equipment-v1.txt",
|
||||
"prompt_sha256": "b4841b8ab5ea187f3158356a03d7b533f806acd4da8256cc6664e70ddeb292ad",
|
||||
"gate_lease_id": "d99da2b6-219d-44e0-babb-5ffcc0bc75d1",
|
||||
"gate_outcome": "success",
|
||||
"created_utc": "2026-10-11T08:17:17.156727+00:00"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"tool": "built-in image_gen",
|
||||
"builtin_original_output_path": "/home/quintine/.codex/generated_images/01a129a5-0efd-7c61-8556-922e09c8ea6c/exec-a4ce4dec-d02f-43b9-9fd0-e95b61fc25de.png",
|
||||
"local_raw_copy": "projectile-generated-source-v1.png",
|
||||
"raw_sha256": "46d958d55814c9c9fe81cd6d22847f82e1e81f8ed6ca2ff855d155c7e3281d87",
|
||||
"copy_sha256": "46d958d55814c9c9fe81cd6d22847f82e1e81f8ed6ca2ff855d155c7e3281d87",
|
||||
"exact_byte_equality": true,
|
||||
"prompt_file": "prompt-projectile-v1.txt",
|
||||
"prompt_sha256": "55c7d44e0de631fb7ea5f98d07ec3c1581d14e9c87e5d4622ac041a72f582dbf",
|
||||
"gate_lease_id": "213076b0-b1e0-4b92-a8d4-43f8ef8b1887",
|
||||
"gate_outcome": "success",
|
||||
"created_utc": "2026-10-11T08:18:46.812447+00:00"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 241 B |
@@ -0,0 +1 @@
|
||||
Use case: stylized-concept. Asset type: a single new HD equipment sprite for the shop/inventory of a Raptor-style military science-fiction vertical shooter. Generate a Railburst Cannon: compact armored gunmetal rectangular breech with a single short central accelerator muzzle pointing exactly UP, two restrained cyan induction rails along its sides, a small red service plate and bolted dark gray mounting base. Top-down orthographic overhead view, symmetrical readable silhouette with chunky industrial 1990s shooter forms, warm upper-left metal highlights and deep painted recesses. Fine machined detail for HD, clean hard silhouette readable at 16x16 logical pixels and64x64HD. Exactly one complete centered object on a genuinely transparent square canvas, with at least15percent transparent margin around the entire object. The silhouette should fill the central65percent of the canvas. No floor, black background, scene, ship, words, border, frame, cast shadow, large glow or extra objects. Muted gunmetal/steel, narrow cyan accents, tiny red service plate; no candy colors. Newly authored equipment, not a duplicate of any existing weapon.
|
||||
@@ -0,0 +1 @@
|
||||
Use case: stylized-concept. Asset type: one new transparent railgun projectile sprite for a Raptor-style military science-fiction vertical shooter. A single extremely narrow vertical ballistic rail slug traveling exactly UP: sharp pale steel pointed nose, short bright ivory core, a restrained slim cyan sheath, and a tapered blue-gray wake downward. It is a solid compact dart with a visible bright tip and a modest bounded trail, not a screen-length beam, not lightning, not an explosion. Top-down orthographic overhead view, perfectly straight and centered. Subject width:height about1:5; logical canvas4x20 pixels and16x80HD. Chunky crisp small-game silhouette with refined original metal/light detail forHD. One complete object on a genuinely transparent square canvas, generous blank side areas, at least15percent top/bottom transparent margin. No gun, spacecraft, text, border, background, shadow, smoke cloud, detached sparkles, branching, multiple objects or outside glow. Match restrained pale steel/ivory/cyan highlights of a classic1990s military shooter.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"tool": "original procedural Python synthesis",
|
||||
"recipe": "generate_audio.py",
|
||||
"source_recordings": [],
|
||||
"deterministic_seed": "0x71ac092d",
|
||||
"outputs": [
|
||||
{
|
||||
"file": "audio/fire.wav",
|
||||
"sha256": "fbc829573e92ec4a824bb4ba00a3dff53c866685c0ff1270335d896a6cabdf7c",
|
||||
"rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"frames": 3528,
|
||||
"duration_seconds": 0.16,
|
||||
"peak": 16253,
|
||||
"rms": 2346.20609017488,
|
||||
"dc_mean": 19.44331065759637,
|
||||
"clipped_samples": 0
|
||||
},
|
||||
{
|
||||
"file": "audio/impact.wav",
|
||||
"sha256": "4a44c8f561c230f519ed926d849e7e0bff1db2f11cf705c67c70d2eccb1c8221",
|
||||
"rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"frames": 5071,
|
||||
"duration_seconds": 0.22997732426303855,
|
||||
"peak": 15667,
|
||||
"rms": 2250.0482951936838,
|
||||
"dc_mean": 14.399132321041215,
|
||||
"clipped_samples": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"weapons": [
|
||||
{
|
||||
"id": "iron_coast_railburst:projectile",
|
||||
"name": "Railburst Cannon Projectile",
|
||||
"damage": 42,
|
||||
"speed": 16,
|
||||
"cooldown": 22,
|
||||
"behavior": "straight",
|
||||
"target": "all",
|
||||
"offsets": [
|
||||
[
|
||||
0,
|
||||
-4
|
||||
]
|
||||
],
|
||||
"art": {
|
||||
"png": "art/projectile-sd.png",
|
||||
"hd_png": "art/projectile-hd.png",
|
||||
"logical_width": 4,
|
||||
"logical_height": 20,
|
||||
"nearest": true
|
||||
},
|
||||
"script": "scripts/weapon.rhai"
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"id": "iron_coast_railburst:equipment",
|
||||
"name": "Railburst Cannon",
|
||||
"price": 685000,
|
||||
"max_count": 1,
|
||||
"initial_count": 1,
|
||||
"weapon": "iron_coast_railburst:projectile",
|
||||
"special": true,
|
||||
"consumable": false,
|
||||
"art": {
|
||||
"png": "art/equipment-sd.png",
|
||||
"hd_png": "art/equipment-hd.png",
|
||||
"logical_width": 16,
|
||||
"logical_height": 16,
|
||||
"nearest": true
|
||||
},
|
||||
"description": "Pierces up to 3 aligned targets. Selectable reusable special. Ctrl fires a fast rail slug at air or ground targets. A real hit penetrates up to two more enemies ahead within a narrow 24-pixel lane, with falling damage (42/32/22). Best against aligned armor; no ammunition consumed. Alt/Shift selects weapons; Space drops mega bombs."
|
||||
}
|
||||
],
|
||||
"sounds": [
|
||||
{
|
||||
"id": "iron_coast_railburst:fire",
|
||||
"name": "Railburst Cannon Fire",
|
||||
"file": "audio/fire.wav",
|
||||
"volume": 88,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"id": "iron_coast_railburst:impact",
|
||||
"name": "Railburst Cannon Impact",
|
||||
"file": "audio/impact.wav",
|
||||
"volume": 82,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reproduce original deterministic PCM sound effects; no external recordings."""
|
||||
import math, struct, wave, json, hashlib
|
||||
from pathlib import Path
|
||||
PACKAGE = 'iron_coast_railburst'
|
||||
RATE = 22050
|
||||
|
||||
def render(kind):
|
||||
rail = PACKAGE.endswith("railburst")
|
||||
duration = (0.16 if kind == "fire" else 0.23) if rail else (0.21 if kind == "fire" else 0.46)
|
||||
count = int(RATE * duration)
|
||||
seed = 0x71AC092D if rail else 0xDC042681
|
||||
phase = 0.0
|
||||
samples = []
|
||||
for n in range(count):
|
||||
t = n / RATE
|
||||
progress = n / count
|
||||
seed ^= (seed << 13) & 0xffffffff
|
||||
seed ^= seed >> 17
|
||||
seed ^= (seed << 5) & 0xffffffff
|
||||
seed &= 0xffffffff
|
||||
noise = seed / 2147483648.0 - 1.0
|
||||
if rail:
|
||||
frequency = (2100 * math.exp(-22*t) + 280) if kind == "fire" else (780 * math.exp(-14*t) + 95)
|
||||
tone = 0.30*math.sin(phase) + 0.18*math.sin(phase*1.993) + noise*(0.44 if kind=="fire" else 0.34)
|
||||
envelope = math.exp(-(32 if kind=="fire" else 19)*t)
|
||||
else:
|
||||
frequency = (175 * math.exp(-10*t) + 65) if kind=="fire" else (98*math.exp(-7*t)+42)
|
||||
tone = math.sin(phase)*0.53 + math.sin(phase*1.47)*0.10 + noise*(0.26 if kind=="fire" else 0.35)*math.exp(-6*t)
|
||||
envelope = math.exp(-(20 if kind=="fire" else 9)*t)
|
||||
phase += 2*math.pi*frequency/RATE
|
||||
attack = min(1.0, n/(RATE*0.002))
|
||||
tail = min(1.0, (count-n)/(RATE*0.015))
|
||||
samples.append(round(max(-1.0,min(1.0,tone*envelope*attack*tail))*22000))
|
||||
return samples
|
||||
|
||||
if __name__ == "__main__":
|
||||
root=Path(__file__).resolve().parent
|
||||
rows=[]
|
||||
for kind in ["fire","impact"]:
|
||||
samples=render(kind)
|
||||
path=root/"audio"/(kind+".wav")
|
||||
with wave.open(str(path),"wb") as output:
|
||||
output.setparams((1,2,RATE,0,"NONE","not compressed"))
|
||||
output.writeframes(struct.pack("<"+"h"*len(samples),*samples))
|
||||
rows.append({"file":str(path.relative_to(root)),"sha256":hashlib.sha256(path.read_bytes()).hexdigest(),"rate":RATE,"channels":1,"bits":16,"frames":len(samples),"duration_seconds":len(samples)/RATE,"peak":max(abs(x) for x in samples),"rms":math.sqrt(sum(x*x for x in samples)/len(samples)),"dc_mean":sum(samples)/len(samples),"clipped_samples":sum(abs(x)>=32767 for x in samples)})
|
||||
(root/"audio"/"provenance.json").write_text(json.dumps({"tool":"original procedural Python synthesis","recipe":"generate_audio.py","source_recordings":[],"deterministic_seed":hex(0x71AC092D if PACKAGE.endswith("railburst") else 0xDC042681),"outputs":rows},indent=2)+"\n")
|
||||
@@ -0,0 +1,7 @@
|
||||
id = "iron_coast_railburst"
|
||||
name = "Railburst Cannon"
|
||||
version = "1.0.0"
|
||||
api_version = 1
|
||||
description = "Independent Iron Coast II selectable upgrade with SD/HD artwork and procedural fire/impact audio."
|
||||
content = "content.json"
|
||||
scripts = ["scripts/weapon.rhai"]
|
||||
@@ -0,0 +1,76 @@
|
||||
const SHOT_W: u32 = 4;
|
||||
const SHOT_H: u32 = 20;
|
||||
// Fit untouched generated masters to shared logical canvases and four-times HD.
|
||||
// Compile with this project's pinned image crate; see README for reproduction.
|
||||
use image::{Rgba, RgbaImage, imageops};
|
||||
use std::{env, fs, path::Path};
|
||||
fn fit(source: &RgbaImage, w: u32, h: u32, scale: u32) -> RgbaImage {
|
||||
let mut bounds = (source.width(), source.height(), 0, 0);
|
||||
for (x, y, p) in source.enumerate_pixels() {
|
||||
if p[3] >= 128 {
|
||||
bounds.0 = bounds.0.min(x);
|
||||
bounds.1 = bounds.1.min(y);
|
||||
bounds.2 = bounds.2.max(x + 1);
|
||||
bounds.3 = bounds.3.max(y + 1);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
bounds.2 > bounds.0 && bounds.3 > bounds.1,
|
||||
"no meaningful source alpha"
|
||||
);
|
||||
let crop = imageops::crop_imm(
|
||||
source,
|
||||
bounds.0,
|
||||
bounds.1,
|
||||
bounds.2 - bounds.0,
|
||||
bounds.3 - bounds.1,
|
||||
)
|
||||
.to_image();
|
||||
// Exactly one logical pixel of clear margin, shared across SD and HD.
|
||||
let factor = ((w - 2) as f64 / crop.width() as f64).min((h - 2) as f64 / crop.height() as f64);
|
||||
let nw = (crop.width() as f64 * factor).round().max(1.) as u32;
|
||||
let nh = (crop.height() as f64 * factor).round().max(1.) as u32;
|
||||
let filter = if scale == 1 {
|
||||
imageops::FilterType::Triangle
|
||||
} else {
|
||||
imageops::FilterType::Lanczos3
|
||||
};
|
||||
let mut scaled = imageops::resize(&crop, nw * scale, nh * scale, filter);
|
||||
for p in scaled.pixels_mut() {
|
||||
if p[3] < 128 {
|
||||
*p = Rgba([0, 0, 0, 0]);
|
||||
} else {
|
||||
p[3] = 255;
|
||||
}
|
||||
}
|
||||
let mut out = RgbaImage::new(w * scale, h * scale);
|
||||
imageops::overlay(
|
||||
&mut out,
|
||||
&scaled,
|
||||
((w * scale - nw * scale) / 2) as i64,
|
||||
((h * scale - nh * scale) / 2) as i64,
|
||||
);
|
||||
out
|
||||
}
|
||||
fn main() {
|
||||
let args: Vec<_> = env::args().collect();
|
||||
assert_eq!(args.len(), 2, "usage: prepare_art PACKAGE_DIRECTORY");
|
||||
let root = Path::new(&args[1]);
|
||||
for (kind, w, h) in [("equipment", 16, 16), ("projectile", SHOT_W, SHOT_H)] {
|
||||
let source = image::open(root.join(format!("art/{kind}-generated-source-v1.png")))
|
||||
.unwrap()
|
||||
.into_rgba8();
|
||||
for (mode, scale) in [("sd", 1), ("hd", 4)] {
|
||||
let image = fit(&source, w, h, scale);
|
||||
let path = root.join(format!("art/{kind}-{mode}.png"));
|
||||
image.save(&path).unwrap();
|
||||
println!("{} {}x{}", path.display(), image.width(), image.height());
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
fs::metadata(root.join("art/equipment-hd.png"))
|
||||
.unwrap()
|
||||
.len()
|
||||
> 0
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"use_case": "stylized-concept",
|
||||
"tool": "built-in image_gen",
|
||||
"skill": "/home/quintine/.codex/skills/.system/imagegen/SKILL.md",
|
||||
"style_reference_ids": [
|
||||
"102b6",
|
||||
"101cb"
|
||||
],
|
||||
"reference_role": "Original catalog was inspected for classic palette, silhouette and scale; no decoded reference is included in this mod. New generation uses the prepared textual art direction.",
|
||||
"prompts": {
|
||||
"equipment": "Use case: stylized-concept. Asset type: a single new HD equipment sprite for the shop/inventory of a Raptor-style military science-fiction vertical shooter. Generate a Railburst Cannon: compact armored gunmetal rectangular breech with a single short central accelerator muzzle pointing exactly UP, two restrained cyan induction rails along its sides, a small red service plate and bolted dark gray mounting base. Top-down orthographic overhead view, symmetrical readable silhouette with chunky industrial 1990s shooter forms, warm upper-left metal highlights and deep painted recesses. Fine machined detail for HD, clean hard silhouette readable at 16x16 logical pixels and64x64HD. Exactly one complete centered object on a genuinely transparent square canvas, with at least15percent transparent margin around the entire object. The silhouette should fill the central65percent of the canvas. No floor, black background, scene, ship, words, border, frame, cast shadow, large glow or extra objects. Muted gunmetal/steel, narrow cyan accents, tiny red service plate; no candy colors. Newly authored equipment, not a duplicate of any existing weapon.",
|
||||
"projectile": "Use case: stylized-concept. Asset type: one new transparent railgun projectile sprite for a Raptor-style military science-fiction vertical shooter. A single extremely narrow vertical ballistic rail slug traveling exactly UP: sharp pale steel pointed nose, short bright ivory core, a restrained slim cyan sheath, and a tapered blue-gray wake downward. It is a solid compact dart with a visible bright tip and a modest bounded trail, not a screen-length beam, not lightning, not an explosion. Top-down orthographic overhead view, perfectly straight and centered. Subject width:height about1:5; logical canvas4x20 pixels and16x80HD. Chunky crisp small-game silhouette with refined original metal/light detail forHD. One complete object on a genuinely transparent square canvas, generous blank side areas, at least15percent top/bottom transparent margin. No gun, spacecraft, text, border, background, shadow, smoke cloud, detached sparkles, branching, multiple objects or outside glow. Match restrained pale steel/ivory/cyan highlights of a classic1990s military shooter."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Native straight projectile supplies the first collision and its 42 damage.
|
||||
// Penetration is contact-triggered, finite and locked to that projectile's x lane.
|
||||
fn on_fire(event, state, random) {
|
||||
if event.hostile { return #{commands: [], state: state}; }
|
||||
let shots = if state.contains("shots") { state.shots % 100000 + 1 } else { 1 };
|
||||
let hits = if state.contains("hits") { state.hits } else { 0 };
|
||||
#{commands: [#{op: "play_sound", sound: "iron_coast_railburst:fire"}],
|
||||
state: #{shots: shots, hits: hits}}
|
||||
}
|
||||
|
||||
fn on_hit(event, state, random) {
|
||||
let shots = if state.contains("shots") { state.shots } else { 0 };
|
||||
let hits = if state.contains("hits") { state.hits % 100000 + 1 } else { 1 };
|
||||
let origin_y = event.y;
|
||||
let ground = false;
|
||||
for enemy in event.entities {
|
||||
if enemy.entity == event.entity {
|
||||
origin_y = enemy.y + enemy.height / 2;
|
||||
ground = enemy.ground;
|
||||
}
|
||||
}
|
||||
let kind = if ground { "small_ground" } else { "small_air" };
|
||||
let commands = [#{op: "play_sound", sound: "iron_coast_railburst:impact"},
|
||||
#{op: "effect", kind: kind, x: event.x, y: event.y}];
|
||||
let struck = [event.entity];
|
||||
let previous_y = origin_y;
|
||||
let damages = [32, 22];
|
||||
for hop in 0..2 {
|
||||
let chosen = #{};
|
||||
let nearest = 1000000;
|
||||
for enemy in event.entities {
|
||||
let cx = enemy.x + enemy.width / 2;
|
||||
let cy = enemy.y + enemy.height / 2;
|
||||
if enemy.health <= 0 || struck.contains(enemy.entity)
|
||||
|| cx < 0 || cx >= 320 || cy < 0 || cy >= 200 { continue; }
|
||||
let distance = previous_y - cy;
|
||||
if abs(cx - event.x) <= 12 && distance > 0
|
||||
&& origin_y - cy <= 160 && distance < nearest {
|
||||
chosen = #{entity: enemy.entity, x: cx, y: cy, ground: enemy.ground};
|
||||
nearest = distance;
|
||||
}
|
||||
}
|
||||
if !chosen.contains("entity") { break; }
|
||||
commands.push(#{op: "damage", entity: chosen.entity, amount: damages[hop]});
|
||||
let impact = if chosen.ground { "small_ground" } else { "small_air" };
|
||||
commands.push(#{op: "effect", kind: impact, x: chosen.x, y: chosen.y});
|
||||
struck.push(chosen.entity);
|
||||
previous_y = chosen.y;
|
||||
hits = hits % 100000 + 1;
|
||||
}
|
||||
#{commands: commands, state: #{shots: shots, hits: hits}}
|
||||
}
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user