Retune Iron Coast waves, enemy attacks and weapon presentation

This commit is contained in:
raptorreplica committed 2026-10-17 18:13:20 +11:00
1 parent 00cfcc2031
commit c4a4d821d5
145 files changed
+9242 -2951

No files matched your search

+12 -54
View File
@@ -1,63 +1,21 @@
# Railburst Cannon 1.0.0
# Railburst Cannon
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.
API 1 package, version 1.1.0. Enable it in Mods at the main menu and apply the selection. This package has no mod dependencies.
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.
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.
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.
Buy **Railburst Cannon** for **685,000 credits**; carry at most 1. Select it through Inventory or the Cycle control when it is a special weapon.
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.
`iron_coast_railburst:projectile`: 42 damage, speed 16, 22-tick cooldown, `straight` behavior.
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.
## Feedback
`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.
The unchanged 4×20 SD / 16×80 HD projectile files now display on a proportional **3×15 logical canvas**. Point collision, 16px/tick speed, 22-tick cadence, 42/32/22 damage, 24px penetration lane and 160px follow-through limit are unchanged. Each of at most three struck targets receives a short 8px, 2-tick cosmetic contact streak, instead of long air/ground explosions. Fire/impact volume is 80/66. Price stays 685,000.
From the RustRaptor directory:
Only declared display sizing, cosmetic hit commands and playback levels/priorities change. No PNG/WAV pixels or samples, equipment icon dimensions, projectile collision rules, damage, cooldown, offsets, targeting, ammunition, capacity, controls or prices are changed. No new muzzle explosion or persistent trail is emitted. Normal original enemy deaths and terrain-destruction effects remain engine-controlled.
```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
```
The three rapid weapons retain their prior burst/hit counters and reset only their cosmetic tick/projectile gates when mission ticks restart, accepting older saved script state without new fields. This version changes the package save pin; existing pilot compatibility confirmation remains the normal mod mechanism.
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.
## Validation and history
See the [current Iron Coast retune results and limits](../../docs/iron-coast-retune.md). Package-local acceptance reports, validation results and retained test sources describe pre-retune 1.0 behavior unless a result explicitly identifies the newer package and runner. They remain historical material, not current playthrough guarantees.
+6 -6
View File
@@ -17,8 +17,8 @@
"art": {
"png": "art/projectile-sd.png",
"hd_png": "art/projectile-hd.png",
"logical_width": 4,
"logical_height": 20,
"logical_width": 3,
"logical_height": 15,
"nearest": true
},
"script": "scripts/weapon.rhai"
@@ -49,19 +49,19 @@
"id": "iron_coast_railburst:fire",
"name": "Railburst Cannon Fire",
"file": "audio/fire.wav",
"volume": 88,
"volume": 80,
"pan": 127,
"pitch": 128,
"priority": 1
"priority": 2
},
{
"id": "iron_coast_railburst:impact",
"name": "Railburst Cannon Impact",
"file": "audio/impact.wav",
"volume": 82,
"volume": 66,
"pan": 127,
"pitch": 128,
"priority": 2
"priority": 3
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
id = "iron_coast_railburst"
name = "Railburst Cannon"
version = "1.0.0"
version = "1.1.0"
api_version = 1
description = "Independent Iron Coast II selectable upgrade with SD/HD artwork and procedural fire/impact audio."
content = "content.json"
@@ -12,16 +12,15 @@ 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" };
// A short rail contact streak leaves the struck body readable.
let commands = [#{op: "play_sound", sound: "iron_coast_railburst:impact"},
#{op: "effect", kind: kind, x: event.x, y: event.y}];
#{op: "arc", x: event.x - 2, y: event.y + 4,
to_x: event.x + 2, to_y: event.y - 4, ticks: 2}];
let struck = [event.entity];
let previous_y = origin_y;
let damages = [32, 22];
@@ -42,8 +41,8 @@ fn on_hit(event, state, random) {
}
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});
commands.push(#{op: "arc", x: chosen.x - 2, y: chosen.y + 4,
to_x: chosen.x + 2, to_y: chosen.y - 4, ticks: 2});
struck.push(chosen.entity);
previous_y = chosen.y;
hits = hits % 100000 + 1;