Package RustRaptor and refine controls, menus, saves, sound and mod selection
This commit is contained in:
1 parent
0844f6dffa
commit
e17bc1003e
649 files changed
+8652
-2677
No files matched your search
+6
-5
@@ -1,9 +1,10 @@
|
||||
# RaptorReplica
|
||||
|
||||
The retained C++ engine described below is the playable reference. An independent
|
||||
Rust rewrite is now selectable with `./play.sh --engine rust`; see
|
||||
[Rust implementation and validation](rust-rewrite.md), [modding API](modding-api.md)
|
||||
and [optional HD art](hd-art.md). Full Rust cutover acceptance remains in progress.
|
||||
RustRaptor is the default game in `../RustRaptor/`; see
|
||||
[Rust implementation and validation](../RustRaptor/docs/rust-rewrite.md),
|
||||
[modding API](../RustRaptor/docs/modding-api.md) and
|
||||
[optional HD art](../RustRaptor/docs/hd-art.md).
|
||||
The historical C++ reference is described below.
|
||||
|
||||
A faithful, playable replica of **Raptor: Call of the Shadows** (Cygnus
|
||||
Studios, 1994) running the reconstructed original engine on Linux, with the
|
||||
@@ -12,7 +13,7 @@ original game data extracted into a modern, editable asset tree.
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
./play.sh
|
||||
./play.sh --engine legacy
|
||||
```
|
||||
|
||||
Builds the engine on first run, then launches fullscreen. Saves and
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
# Building and running
|
||||
|
||||
For the independent Rust engine, use `./play.sh --engine rust`. Rust 1.88+,
|
||||
For RustRaptor, use `./play.sh` or `./RustRaptor/run.sh`. Rust 1.88+,
|
||||
Cargo and SDL2 development libraries are required. The launcher builds in release
|
||||
mode and sets the local SDL2 library path. See [Rust build/test commands and
|
||||
separate profile locations](rust-rewrite.md). The sections below describe the
|
||||
separate profile locations](../RustRaptor/docs/rust-rewrite.md). The sections below describe the
|
||||
retained C++ engine; its launcher now rebuilds the target before each launch.
|
||||
|
||||
## Requirements
|
||||
@@ -18,7 +18,7 @@ retained C++ engine; its launcher now rebuilds the target before each launch.
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
./play.sh
|
||||
./play.sh --engine legacy
|
||||
```
|
||||
|
||||
`play.sh` checks `data/INDEX`, builds `raptor/build/bin/raptor` on first run
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
# Original campaign validation
|
||||
|
||||
Local checks on 2026-10-10 reached mission completion for all 27 original waves
|
||||
at each of the four difficulties. These 108 runs are assisted inspection fixtures,
|
||||
not ordinary player victories and not per-wave C++ parity proofs. Original game
|
||||
data, reference recordings, profiles and CSV reports remain under ignored local
|
||||
directories.
|
||||
|
||||
The separate, complete `DEMO1G1_REC` comparison matches the rebuilt C++ engine
|
||||
across all 1,889 available simulation steps. Captured fields are the RNG seed,
|
||||
player coordinates/banking/shields/credits, ordered enemy asset/position/health/
|
||||
animation/movement coordinates, and ordered projectile asset/position/hostility.
|
||||
The recording starts in sector 1 wave 3 and ends before the boss. Inventory,
|
||||
internal AI counters, terrain damage, effects, audio samples and presentation are
|
||||
outside this gameplay trace; this does not establish full-game parity.
|
||||
|
||||
## Assisted campaign fixture
|
||||
|
||||
`tools/campaign_check.rs` gives the pilot the original arsenal and five mega
|
||||
bombs, fires continuously, aims horizontally at visible enemies while preferring
|
||||
bosses, cycles specials every 180 simulation steps and presses the bomb control
|
||||
every 480 steps. Before each step it replenishes energy and super-shield values
|
||||
to 100 each. Inputs still use the game input path. Enemy definitions, damage,
|
||||
spawn timing, RNG and mission outcomes are not replaced. A run stops at the
|
||||
reported outcome or its 12,000-step inspection limit.
|
||||
|
||||
The final run visited every spawn record, observed every expected boss instance
|
||||
and finished with no remaining enemies. Its local CSV is
|
||||
`.reference/campaign-validation.csv` (SHA-256
|
||||
`8702d190a039239de154e09ebbb5972f6d7ffe07475d7d47b199b53efb3a811a`).
|
||||
|
||||
| Difficulty | Completed waves | Steps per wave | Enabled spawns | Boss instances seen | Nonpositive last health | Positive last health |
|
||||
| --- | ---: | --- | ---: | ---: | ---: | ---: |
|
||||
| 0 | 27 | 4,601–4,873 | 3,751 | 88 | 53 | 35 |
|
||||
| 1 | 27 | 4,607–4,873 | 3,751 | 88 | 53 | 35 |
|
||||
| 2 | 27 | 4,651–5,079 | 6,259 | 88 | 36 | 52 |
|
||||
| 3 | 27 | 4,650–5,082 | 8,668 | 88 | 37 | 51 |
|
||||
|
||||
The 108 runs cover 513,002 simulation steps, 22,429 enabled spawns and 352 boss
|
||||
appearances. Boss retirement counts classify the health observed immediately
|
||||
before removal: 179 nonpositive and 173 positive. Positive health commonly
|
||||
indicates a boss leaving the screen; damage and removal within the same step
|
||||
can also affect this classification. These counts are not an exact death-event
|
||||
audit. The harness deliberately allows the original wave completion condition
|
||||
after all spawned ships have died or left, rather than requiring every boss to
|
||||
be killed.
|
||||
|
||||
Additional opt-in tests exercised all 15 original player weapon definitions with
|
||||
live mission targets and observed emitted projectiles. This verifies availability
|
||||
and basic firing paths, not exact weapon effects. Progression checks exercise
|
||||
training's four-wave route, each normal 27-wave route, difficulty promotion,
|
||||
campaign resets and the training reward. They call the progression API directly
|
||||
and bypass combat; end cinematics and menu transitions need separate checks.
|
||||
|
||||
Run the four original-data integration tests from the project directory:
|
||||
|
||||
```sh
|
||||
RAPTOR_CAMPAIGN_REPORT=.reference/campaign-validation.csv \
|
||||
cargo test --release --no-default-features --test campaigns -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Set `RAPTOR_TEST_DATA` if the original catalog is elsewhere. Set
|
||||
`RAPTOR_REFERENCE_DEMO1` to use a different full C++ reference trace. Without
|
||||
`--ignored`, these tests do not require or distribute original assets.
|
||||
|
||||
The reporting helper is also a standalone Rust CLI. Build the library, supply
|
||||
its resulting `libraptor_replica-*.rlib` path to `rustc --extern`, and retain the
|
||||
dependency directory:
|
||||
|
||||
```sh
|
||||
cargo build --release --no-default-features --lib
|
||||
rustc --edition=2024 tools/campaign_check.rs \
|
||||
--extern raptor_replica=target/release/deps/libraptor_replica-HASH.rlib \
|
||||
--extern anyhow=target/release/deps/libanyhow-HASH.rlib \
|
||||
-L dependency=target/release/deps -o .reference/campaign-check
|
||||
.reference/campaign-check data
|
||||
```
|
||||
|
||||
Its optional second argument selects one difficulty, for example
|
||||
`.reference/campaign-check data 3`.
|
||||
|
||||
## Reproduce the complete demo comparison
|
||||
|
||||
Rebuild the retained C++ source before capturing a reference. The local debug
|
||||
build used `.reference/cpp-rng-build`; use the available CMake executable if it
|
||||
is outside `PATH`. Prepare an isolated legacy profile with `raptorsetup`, or
|
||||
copy an existing `SETUP.INI` into `.reference/full-demo-profile/Raptor/` before
|
||||
running the following commands. The original executable requires `data/` as its
|
||||
working directory. A complete reference takes about 81 seconds at 70/3 Hz.
|
||||
|
||||
```sh
|
||||
cmake --build .reference/cpp-rng-build --parallel 4
|
||||
(
|
||||
cd data
|
||||
XDG_DATA_HOME="$PWD/../.reference/full-demo-profile" \
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
RAPTOR_REFERENCE_TRACE="$PWD/../.reference/cpp-demo1-full.jsonl" \
|
||||
RAPTOR_REFERENCE_TICKS=1889 \
|
||||
../.reference/cpp-rng-build/bin/raptor PLAY demos/DEMO1G1_REC
|
||||
)
|
||||
cargo run --no-default-features -- \
|
||||
--replay DEMO1G1_REC --ticks 1889 --trace .reference/rust-demo1-full.jsonl
|
||||
python3 tools/compare_replays.py \
|
||||
.reference/cpp-demo1-full.jsonl .reference/rust-demo1-full.jsonl
|
||||
```
|
||||
|
||||
The comparator reports `Identical gameplay snapshots across 1889 simulation
|
||||
steps.` and rejects unequal lengths, repeated tick numbers or the first differing
|
||||
field. The local C++ trace SHA-256 is
|
||||
`6f0378dbe4d819b44de86dc22822d2e70cb65546e6aed89dd45a44a8eb3d02fe`.
|
||||
|
||||
This comparison found and repaired beam damage before player-follow artwork,
|
||||
raw recorded positions beyond ordinary input bounds, low-shield equipment loss
|
||||
and automatic selection cycling, and the last authored kamikaze leg's transition
|
||||
into the player chase. Portable synthetic regressions in `tests/gameplay.rs`
|
||||
cover those rules without original pixels or recordings.
|
||||
|
||||
## Remaining gameplay validation
|
||||
|
||||
The catalog inspection found no wave completion blocker under the assisted
|
||||
fixture. Source inspection also found and repaired three original weapon rules,
|
||||
with four additional portable regressions:
|
||||
|
||||
- Energy Grab damages only airborne ships by one point, drains the original
|
||||
resistance counter and disables susceptible enemies' firing. Immunity and
|
||||
continued suppression after animation updates have separate coverage.
|
||||
- Ground Bomb damages terrain before checking a ground enemy and applies the
|
||||
original left/up/right neighbor effects, including the preceding-row pass.
|
||||
One test verifies simultaneous terrain and ground-enemy damage.
|
||||
- Mega Bomb applies its original 50 enemy damage, clears hostile shots and
|
||||
subtracts 20 health only from visible destructible terrain. A test covers
|
||||
distant tiles, inert terrain, destroyed tiles and the final visible tile.
|
||||
|
||||
A longer record set, C++ traces through bosses and departure/death sequences,
|
||||
ordinary pilot playthroughs, all weapon effects, shield recharging and complete
|
||||
save/menu/cinematic routes are still needed for full acceptance. The complete
|
||||
demo match and assisted campaign coverage should be retained as regression
|
||||
evidence while those gaps are resolved.
|
||||
@@ -1,116 +0,0 @@
|
||||
# Original first mission: ordinary-input victory
|
||||
|
||||
The opt-in `tests/first_mission.rs` check completes `base:sector_1_wave_1`
|
||||
through its original boss with a fresh pilot on difficulties 1 and 2. It uses
|
||||
the locally installed original catalog and the production `World::step` path.
|
||||
Both runs start with the default forward guns, shield 100, no super shield,
|
||||
no special weapon, and zero credits. No extra equipment is required.
|
||||
|
||||
Run it with:
|
||||
|
||||
```sh
|
||||
RAPTOR_TEST_DATA=/absolute/path/to/data cargo test --release --no-default-features --test first_mission -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Without `RAPTOR_TEST_DATA`, it reads this checkout's `data/` directory. The
|
||||
test is ignored in the default suite because original game data is not shipped
|
||||
in Git. It writes local results to `.reference/`, which is also ignored.
|
||||
|
||||
## Recorded results
|
||||
|
||||
Local validation on 2026-10-10 produced:
|
||||
|
||||
| Difficulty | Boss spawned | First visible alive | Boss killed / reward paid | Complete | Shield remaining | Final credits | Confirmed kills | Positive-health retirements |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 1 | 4,441 | 4,487 | 4,583 | 4,644 | 80 | 42,775 | 118 | 12 |
|
||||
| 2 | 4,441 | 4,487 | 4,703 | 4,764 | 65 | 59,025 | 161 | 37 |
|
||||
|
||||
Step counts use the original 70/3 Hz simulation. Both runs visit all 299
|
||||
cataloged spawn entries; the original difficulty flags decide which entries
|
||||
actually spawn. The boss is `base:enemy_0_28`, with an original reward of 3,000
|
||||
credits. Its last observed health before removal was 0 on difficulty 1 and
|
||||
−3 on difficulty 2. The test asserts that its death pays the reward, that every
|
||||
eligible boss enters the playfield alive and is killed, and that the surviving
|
||||
player reaches `Outcome::Complete`.
|
||||
|
||||
The healthy departure cue plays at timer 40 (steps 4,603 and 4,723), pauses
|
||||
ordinary controls, and starts the forced flight offscreen. At timer 24
|
||||
(steps 4,619 and 4,739), the surviving ship remains drawable. It ends the
|
||||
departure at logical y = −160 before completing. The test checks these
|
||||
milestones and records every countdown step; it does not substitute a direct
|
||||
victory command or terminate immediately after the boss kill.
|
||||
|
||||
A confirmed kill requires an entity to be observed with health at or below zero
|
||||
before removal. An entity removed after a positive-health observation is kept
|
||||
separately and never counted as a confirmed kill. That count is conservative
|
||||
around a possible same-step collision death; it is not an all-enemies kill
|
||||
claim. Mission completion alone cannot satisfy the boss-kill assertion.
|
||||
|
||||
The local catalog's `INDEX` SHA-256 was
|
||||
`4ce78e5e638b6603114dd233ceaa41f386c0963ba4aa02ecf88ce0215b096681`.
|
||||
|
||||
## Controller and assistance limits
|
||||
|
||||
The controller supplies only normal keyboard direction values (`dx`, `dy`
|
||||
each −1, 0 or 1) and a held fire button. Production acceleration, speed limits,
|
||||
banking, shot cooldowns, collisions, incoming damage, rewards and RNG all run
|
||||
normally. The actual playthrough never uses absolute positioning, scripted
|
||||
commands, bombs, shield refills, inventory grants or direct gameplay-state
|
||||
edits after `World::new`.
|
||||
|
||||
For target selection, it leads visible enemies using their current observed
|
||||
motion and gives the boss priority. To choose movement, it evaluates nine
|
||||
direction pairs in disposable cloned worlds over 12 normal simulation steps:
|
||||
three steps holding that direction and nine steps releasing movement. It
|
||||
penalizes predicted damage, then balances aiming, lower-screen positioning and
|
||||
distance from hostile shots. Cloned predictions are discarded; they cannot
|
||||
change the live world's state or RNG.
|
||||
|
||||
This is an automated viability proof with full simulation-state knowledge and
|
||||
precise timing. It demonstrates that the implemented first mission is winnable
|
||||
with ordinary movement and starter equipment. It does not measure human
|
||||
difficulty, input-device usability, graphics quality, or audio accuracy.
|
||||
|
||||
## Replay artifacts and reproducibility
|
||||
|
||||
For each difficulty, the test generates:
|
||||
|
||||
- `.reference/first-mission-fresh-difficulty-N.dem`: the original 12-byte demo
|
||||
record layout, with a sector/wave header, button bytes, observed player
|
||||
position and bank. It includes the complete boss encounter and ends at the
|
||||
flyby cue, before stats-stage forced departure movement: 4,603 and 4,723
|
||||
frames respectively. The first record is the first completed simulation step.
|
||||
- `...-controls.json`: the actual keyboard direction and fire inputs per step.
|
||||
- `...-report.json`: starting pilot, boss observations and deaths, rewards,
|
||||
conservative retirement classification, shield changes and periodic states.
|
||||
- `....jsonl`: every production reference snapshot, including tick, shared RNG,
|
||||
player state, ordered enemies and ordered projectiles.
|
||||
|
||||
The test independently decodes and replays both recordings from an identical
|
||||
fresh pilot. The full keyboard log must reproduce every reference snapshot
|
||||
through `Outcome::Complete`: all 4,644 and 4,764 steps. Legacy demo playback
|
||||
uses recorded absolute positions and banking, and must reproduce every
|
||||
snapshot through the defeated boss and flyby cue: 4,603 and 4,723 steps.
|
||||
The winning controller run itself uses keyboard movement.
|
||||
|
||||
The retained C++ engine records demos before its stats-stage automatic
|
||||
departure movement and skips that movement during playback. End-of-step
|
||||
coordinates therefore stop being equivalent after the flyby cue. A full
|
||||
end-of-step demo showed its first divergence at step 4,605 in player projectiles
|
||||
and RNG. Keeping the legacy-format recording to the verified prefix preserves
|
||||
that record-phase distinction; the full keyboard replay verifies departure.
|
||||
|
||||
The recorded demo SHA-256 values are:
|
||||
|
||||
| Difficulty | SHA-256 |
|
||||
| --- | --- |
|
||||
| 1 | `c5385b30c600f91dab9c0f5009a24204f114626b7f655d18687799e834da34ef` |
|
||||
| 2 | `8d245a537812535ebf0e8e66594a73afef6ff01a14581b80332b095d971cda63` |
|
||||
|
||||
These boss-encounter recordings exceed the retained C++ engine's 2,800-frame
|
||||
static demo buffer. A checked reference-input loader with a larger buffer is
|
||||
needed before loading them there. It must also preserve the fresh pilot and
|
||||
difficulty, rather than substituting `DEMO_MakePlayer`'s showcase loadout.
|
||||
The new recordings have **not** yet been compared against a C++ first-mission
|
||||
boss victory. The existing 1,889-step `DEMO1G1_REC` parity test covers
|
||||
a different original demo and remains a separate acceptance check.
|
||||
@@ -1,62 +0,0 @@
|
||||
# Optional HD artwork
|
||||
|
||||
The Rust Options screen includes **HD art**. It starts disabled. Enable it to use reviewed artwork from `data/hd/hd.json`; each missing or rejected frame keeps the selected original / 2x / 3x / 4x crisp treatment. The scale setting still determines the indexed rendering resolution, and the HD toggle can be changed while paused. HD art is presentation only: simulation, collision sizes, banking positions and save content IDs stay unchanged.
|
||||
|
||||
The current local proof covers **the light player ship's straight banking frame only** (`0x1011d`, frame 3 of 7). The other six frames, the dark-sector ship, enemies, projectiles and terrain retain original art. Switching into a bank therefore changes visual style. This proof is suitable for evaluating the look; it is not a finished remaster. Original and generated game pixels are excluded from Git.
|
||||
|
||||
The source was decoded with `tools/hd_art_source.py`, inspected, and passed to the **built-in image_gen tool** using the imagegen skill. The optional proof and its exact generation prompt are saved locally at:
|
||||
|
||||
- `data/hd/light-player-straight-hd-v1.png`
|
||||
- `data/hd/prompts/light-player-straight-v1.txt`
|
||||
- `data/hd/provenance.json`
|
||||
- `data/hd/source/light-player-straight.png`
|
||||
|
||||
The PNG has real alpha transparency, is 1254x1254, and was copied unchanged from the tool's output. At the original 32x32 logical resolution, its visible alpha footprint overlaps the source by 92.1% intersection-over-union; its bounds are `(3,0)..(29,31)` against `(2,0)..(29,31)` for the original. The nose, wing layout, silver/gunmetal body and dark red accents remain recognizable, but panel detail and some contours differ. Further artistic review and a complete, consistent banking sequence are required before enabling HD art by default. An additional extreme-bank attempt is saved as `data/hd/light-player-bank-0-hd-v1.png` with `prompts/light-player-bank-0-v1.txt`, but is excluded from the active pack because its footprint overlap is only 62.2% and its engine ends are shortened. The original extreme-bank sprite continues to render.
|
||||
|
||||
## Pack format
|
||||
|
||||
The following is metadata only; artwork belongs in a separately distributed local pack. Decimal IDs are shown alongside their hexadecimal meaning.
|
||||
|
||||
```json
|
||||
{
|
||||
"format_version": 1,
|
||||
"name": "Source-aligned HD artwork",
|
||||
"description": "Optional artwork; original fallback for uncovered frames",
|
||||
"sequences": [
|
||||
{
|
||||
"name": "Light player banking",
|
||||
"base_asset": 65818,
|
||||
"frame_count": 7,
|
||||
"frames": [
|
||||
{
|
||||
"frame": 3,
|
||||
"file": "light-player-straight-hd-v1.png",
|
||||
"logical_width": 32,
|
||||
"logical_height": 32,
|
||||
"origin": [0, 0],
|
||||
"source_sha256": "SHA-256 of the exact original 0x1011d catalog payload"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`base_asset` is `0x1011a`; the supplied frame targets `base_asset + frame`, so frames never depend on filenames or glob ordering. `frame_count` declares the whole original animation sequence and `frames` explicitly declares replacement coverage. An optional `crop: [x,y,width,height]` selects a rectangle from a PNG atlas.
|
||||
|
||||
Version 1 requires exact original logical canvas dimensions and the original top-left anchor `[0,0]`. Canvas alignment is retained even when transparent margins surround the visible image. The PNG or crop must have the same aspect ratio as the original canvas and at least twice its logical resolution. The source hash prevents artwork silently matching a changed original asset. Duplicate IDs, invalid sequence metadata, bad paths, crop bounds, missing transparency and unreasonable image allocations are rejected. Paths must remain inside the pack directory, including after symlink resolution. Open **D: details** from the main menu to inspect rejected frames and coverage.
|
||||
|
||||
HD frames are resampled directly to the selected presentation canvas and quantized to the game palette. They bypass ScaleNx filtering, preserve transparent pixels and use the normal layer order. Indexed output allows palette effects to apply consistently; subtle RGB shades and alpha edges are reduced by the original palette and binary sprite transparency. Terrain is currently composed at original resolution before its joined ScaleNx pass; use crisp terrain until a dedicated HD terrain composition path is validated.
|
||||
|
||||
## Source and sequence review
|
||||
|
||||
Use the existing catalog decoder to prepare a transparent reference without redrawing it:
|
||||
|
||||
```sh
|
||||
python3 tools/hd_art_source.py --asset 1011d \
|
||||
--output data/hd/source/light-player-straight.png --preview-scale 16
|
||||
```
|
||||
|
||||
Inspect every source frame before generating its replacement. Request the same orthographic camera, silhouette, canvas placement, orientation, material colors and lighting for every frame. Preserve the generated alpha. Do not substitute rotations of one frame for the original bank poses: compare the complete sequence at its logical size and at the intended display resolution. Verify that the nose, engine exits, wing extremes and animation pivots remain aligned and that no clipping or background halos appear.
|
||||
|
||||
Keep image-generation prompts and provenance alongside the artwork. Never overwrite original catalog assets. The loader deliberately accepts partial coverage so a reviewed sprite can be previewed before the complete pack is ready, while preserving per-frame fallback.
|
||||
@@ -1,136 +0,0 @@
|
||||
# Iron Coast content packages
|
||||
|
||||
Iron Coast is a local campaign assembled from seven independently toggleable
|
||||
mods. Enable all six dependencies before enabling the campaign package. The
|
||||
Mods manager validates versions and dependency order and applies the change
|
||||
at the main menu. The new weapons can also be used in the original campaigns;
|
||||
the enemy packages can be referenced from other missions.
|
||||
|
||||
| Package | Content |
|
||||
| --- | --- |
|
||||
| `iron_coast_mission_1` | Breakwater mission, campaign, supply depots, field repairs, terrain, briefing art and mission audio |
|
||||
| `iron_coast_scout` | Airborne scout, weaving AI, projectiles, art and audio |
|
||||
| `iron_coast_turret` | Ground turret, aimed firing, projectiles, art and audio |
|
||||
| `iron_coast_boss` | Harbor command ship, persistent patrol and fan volleys, art and audio |
|
||||
| `pulse_cannon` | Primary paired pulses and scripted burst rounds |
|
||||
| `seeker_swarm` | Selectable homing missile volleys |
|
||||
| `ion_lance` | Selectable penetrating beam |
|
||||
|
||||
All packages are version 1.0.0 and use mod API 1. Mission 1 requires compatible
|
||||
1.x versions of the other six. Save compatibility also records each enabled
|
||||
package's exact version and content hash.
|
||||
|
||||
## Mission 1: Breakwater
|
||||
|
||||
The player raids a coastal industrial harbor. Ten scouts cover the approach,
|
||||
four ground turrets protect the docks, two destructible supply depots release
|
||||
repair kits, and the command ship arrives at step 720. Destroy at least two
|
||||
turrets and the command ship to finish. The boss stays in the playfield until
|
||||
killed; waiting out the mission duration cannot produce victory.
|
||||
|
||||
When inventory space allows, the mission grants a trial Pulse Cannon, which
|
||||
fires alongside forward guns, and three field repairs. Granted equipment
|
||||
remains with the pilot. A full inventory can still start; new grants are
|
||||
skipped while existing equipment is preserved. Cycle equipment to **Iron Coast Field Repair** and
|
||||
press the special button to restore 25 shield per repair. Depots drop extra
|
||||
repairs. Victory pays 5,000 credits plus ordinary enemy rewards. The central
|
||||
turrets and boss patrol overlap the starter-gun firing corridor; the mission
|
||||
is intended to remain playable without the trial weapon.
|
||||
|
||||
Script objectives are namespaced and saved with the pilot:
|
||||
|
||||
| Objective | Meaning |
|
||||
| --- | --- |
|
||||
| `iron_coast_mission_1:phase` | 1 approach, 2 docks, 3 command ship, 4 complete |
|
||||
| `iron_coast_mission_1:turrets_required` | Required count of two |
|
||||
| `iron_coast_turret:defeated` | Turrets destroyed |
|
||||
| `iron_coast_boss:defeated` | Command ships destroyed |
|
||||
| `iron_coast_mission_1:depots_opened` | Supply depots destroyed |
|
||||
| `iron_coast_mission_1:complete` | All required targets destroyed |
|
||||
| `iron_coast_mission_1:trial_issued` | Trial weapon was granted or replenished |
|
||||
|
||||
## Art and audio
|
||||
|
||||
Original terrain asset `0x10360` and military sprite `0x102f6` were decoded and
|
||||
inspected before generating the new art with the built-in imagegen skill.
|
||||
Every package includes generated masters, packaged SD/HD art, WAV files and
|
||||
prompt/provenance metadata in Git. Original style-reference images and original
|
||||
game data remain outside Git. Mission sources are a generated shoreline texture,
|
||||
harbor briefing backdrop and transparent supply depot. The source images
|
||||
remain unchanged; the build step normalizes logical footprints and derives
|
||||
palette SD versions and HD counterparts.
|
||||
|
||||
Mission tiles have 32×32 logical canvases, 32×32 palette SD images and 128×128
|
||||
HD images. The 288×384 logical shoreline map is converted globally before
|
||||
cropping into 108 tiles. A reflected copy produces 216 definitions; twelve
|
||||
forward rows followed by twelve reflected rows give exact matching pixels
|
||||
at each vertical repetition boundary. This keeps the map continuous but
|
||||
repeats and reflects port structures. The 72-row mission map uses three such
|
||||
periods. This is a deliberate first-mission content choice, not unique terrain
|
||||
for the whole original game.
|
||||
|
||||
Briefing/menu art uses a 320×200 logical canvas, with 320×200 SD and 1280×800
|
||||
HD files. Supply depots preserve a 32×32 collision canvas and transparent
|
||||
sprite margins; repair icons use 16×16 logical canvases. The asset report
|
||||
records hashes, palette membership, footprints and repeat-boundary checks.
|
||||
HD remains constrained by the renderer's indexed palette, so generated color
|
||||
detail is reduced to the selected palette during play.
|
||||
|
||||
Mission audio is original procedural synthesis: approach signal, dock alarm,
|
||||
boss warning, victory arpeggio, depot impact and repair cue. These are bounded
|
||||
mono PCM16 WAVs at 22,050 Hz, 0.35–1.2 seconds, made from deterministic
|
||||
oscillators and seeded noise. No original sound recording, SoundFont or online
|
||||
audio service is used. The generator and output hashes are tracked.
|
||||
|
||||
The generated mod assets are included in Git, so a fresh checkout includes
|
||||
the complete seven packages. The original base-game asset catalog is still
|
||||
required locally to play the game and remains excluded from version control.
|
||||
To rebuild the mission assets from their included generated masters, run:
|
||||
|
||||
```sh
|
||||
python3 examples/mods/iron_coast_mission_1/build_assets.py --palette data/misc/PALETTE_DAT
|
||||
cargo run --no-default-features -- --mods examples/mods --validate-mods iron_coast_scout,iron_coast_turret,iron_coast_boss,pulse_cannon,seeker_swarm,ion_lance,iron_coast_mission_1
|
||||
```
|
||||
|
||||
## Acceptance checks
|
||||
|
||||
`tests/iron_coast.rs` loads all seven real manifests, definitions and scripts
|
||||
with synthetic PNG/WAV fixtures. It checks dependency activation, artwork
|
||||
footprints, explicit boss/turret completion gating, one-time victory rewards,
|
||||
and actual headless runs through the scout waves and boss using starter guns
|
||||
and the trial Pulse Cannon. These tests use no original images and replace the
|
||||
generated assets with synthetic fixtures. Local package validation additionally checks
|
||||
the actual generated SD/HD images and WAV files.
|
||||
|
||||
Release acceptance passes all six tests in **1.43 seconds**. Synthetic
|
||||
seven-package activation takes 0.179–0.290 seconds including fixture creation
|
||||
and PNG/WAV validation. Actual seven-package validation, including the generated
|
||||
SD/HD artwork and WAV files, passes in **2.234 seconds** with zero overrides.
|
||||
The two playable runs complete as follows:
|
||||
|
||||
| Loadout | Completion step | Final shield | Credits |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Starter forward guns | 882 | 94 | 7,715 |
|
||||
| Trial Pulse Cannon plus forward guns | 786 | 92 | 7,715 |
|
||||
|
||||
Both start at difficulty 2 with shield 100. The test holds fire, fixes the
|
||||
player at (144,160), selects field repairs on step 2 and presses the special
|
||||
button every 90 steps starting at step 61. The starter run removes the granted
|
||||
trial cannon after initial setup. Granted/dropped repairs heal through normal
|
||||
item callbacks; these runs use no direct shield refills, invulnerability,
|
||||
enemy damage commands or victory-objective mutations. They establish that
|
||||
the actual waves and boss can be completed under this controlled input, not
|
||||
that every normal-player route or loadout has been balanced.
|
||||
|
||||
The default debug suite took 34.15 seconds for the earlier five-test version;
|
||||
the release result shows that its slowdown comes mainly from unoptimized
|
||||
simulation, serialization and scripting. PNG color memoization reduced the
|
||||
previous multi-minute asset validation overhead. Release measurements above
|
||||
exclude compilation.
|
||||
|
||||
Visual approval remains provisional: sprite and terrain previews were
|
||||
inspected for readability, canvas alignment and transparency, with numeric
|
||||
palette and repeat-seam checks. Wider player testing should assess difficulty,
|
||||
audio balance and HD details during normal play before treating the new art
|
||||
as final. These packages extend the Rust engine; they do not establish full
|
||||
parity across the original campaigns.
|
||||
@@ -1,194 +0,0 @@
|
||||
# Rust modding API, version 1
|
||||
|
||||
This is an experimental API for the Rust engine. It provides local content packages, deterministic Rhai callbacks, checked commands, an in-game manager and save compatibility checks. The rewrite is still undergoing gameplay parity work; this document describes the implemented API, not a claim that the full remaster has passed its acceptance checks. API compatibility is currently exact: `api_version = 1`.
|
||||
|
||||
## Try the example
|
||||
|
||||
`examples/mods/aurora/` adds two projectiles, a pulse cannon, a repair cell, two enemies, a mission and a campaign. The weapon adds angled shots in Rhai; enemy movement, item rewards and mission objectives also use scripts. Its artwork references asset IDs in the locally extracted original catalog. The example contains no original or generated game artwork.
|
||||
|
||||
Validate the package against your original game data:
|
||||
|
||||
```sh
|
||||
cargo run --no-default-features -- --data data --mods examples/mods --validate-mods aurora
|
||||
```
|
||||
|
||||
Run its mission without a window:
|
||||
|
||||
```sh
|
||||
cargo run --no-default-features -- --data data --mods examples/mods \
|
||||
--enabled aurora --headless aurora:field_trials --ticks 700
|
||||
```
|
||||
|
||||
For the in-game manager, copy `examples/mods/aurora` into the `mods/` directory under `$XDG_DATA_HOME/RaptorReplica` (default `~/.local/share/RaptorReplica/mods/`), or start the Rust engine with `--mods examples/mods`. Open **Mods**, toggle Aurora, then select **Apply selections**. Select its mission from the mission menu. Existing pilots can add mods while preserving required versions, hashes and relative load order; the next save records the expanded active set. Disabling, changing or reordering required mods blocks loading with details. Use another pilot for an incompatible set.
|
||||
|
||||
In the Mods screen, Enter toggles a package or activates the selected action. Rescan after editing a package on disk. Load dependencies before dependents; independent packages retain your chosen priority. Later explicit overrides win. Changes made through the pause menu remain pending until the main menu. Candidate registries and scripts are validated before the active set changes.
|
||||
|
||||
## Package layout and manifest
|
||||
|
||||
```text
|
||||
mods/
|
||||
aurora/
|
||||
mod.toml
|
||||
content.json
|
||||
scripts/
|
||||
weapon.rhai
|
||||
art/
|
||||
cannon.png
|
||||
```
|
||||
|
||||
```toml
|
||||
id = "aurora"
|
||||
name = "Aurora Field Trials"
|
||||
version = "1.0.0"
|
||||
api_version = 1
|
||||
content = "content.json"
|
||||
scripts = ["scripts/weapon.rhai"]
|
||||
description = "A new weapon and mission."
|
||||
conflicts = ["alternate_aurora"]
|
||||
|
||||
[dependencies]
|
||||
common_tools = "^1.2"
|
||||
```
|
||||
|
||||
IDs use lowercase ASCII letters, digits, `_` and `-`, with a maximum of 64 bytes. `base` is reserved. Package versions and dependency requirements use semantic version syntax. Dependencies must be explicitly enabled; the loader will not quietly enable or install them. Cycles, missing packages, unsupported API versions, incompatible dependency versions and declared conflicts reject activation. Two installed folders claiming the same ID make that ID unusable; rename or remove one folder, then rescan.
|
||||
|
||||
All package paths are relative. Absolute paths, parent traversal, symlinks and special files are rejected. Packages are limited to 10,000 files, 16 directory levels, 64 MiB per file and 256 MiB in total. Manifests are limited to 64 KiB; display names to 128 bytes; descriptions to 4 KiB; script declarations to 64, with a 1 MiB limit per source file. Every file, including artwork and documentation, contributes to the package SHA-256 hash. A package changed after discovery requires rescanning before it can be applied.
|
||||
|
||||
## Content definitions and overrides
|
||||
|
||||
`content.json` has optional `weapons`, `items`, `enemies`, `missions`, `campaigns`, `sounds` and `overrides` arrays. Unknown fields are rejected. IDs have the form `namespace:name`, for example `aurora:pulse_cannon`. IDs are stable save references and unique across content types. Keep item and projectile IDs distinct: the item `aurora:pulse_cannon` refers to the weapon/projectile `aurora:pulse_projectile` through its `weapon` property.
|
||||
|
||||
New definitions must belong to the package namespace. An existing definition may be replaced only when its ID appears in `overrides` and the replacement retains the same content type. Overrides are complete replacements, not field merges. An override targeting a missing ID, a replacement without explicit permission, an override without its replacement, or duplicate IDs within a pack rejects the whole pack. The manager exposes the override history and its winning owner.
|
||||
|
||||
```json
|
||||
{
|
||||
"overrides": ["base:projectile_forward_guns"],
|
||||
"weapons": [{
|
||||
"id": "base:projectile_forward_guns",
|
||||
"name": "Modified Forward Guns",
|
||||
"damage": 2,
|
||||
"speed": 8,
|
||||
"cooldown": 2,
|
||||
"behavior": "straight",
|
||||
"target": "air",
|
||||
"offsets": [[-8, 0], [8, 0]]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
`legacy` fields exist to map original numeric definitions into the base registries. New mods should omit them: they select legacy engine behavior and are not a general scripting extension mechanism.
|
||||
|
||||
| Definition | Properties and behavior |
|
||||
| --- | --- |
|
||||
| Weapon/projectile | Damage, integer speed, cooldown in simulation ticks, targeting (`air`, `ground`, `all`), behavior (`straight`, `homing`, `beam`, `spread`, `bomb`, `energy_grab`), firing offsets, optional animation and script. Scripts can emit additional projectiles with explicit velocity. |
|
||||
| Item | Price, maximum and initial count, optional weapon, shield/credit effects, consumable/special flags, artwork and script. `initial_count` controls how many units a purchase or pickup adds. |
|
||||
| Enemy | Health, speed, rewards, air/ground and boss flags, firing cooldown, path points and repeat flag, weapon, dropped item, animation and script. Scripts can query entities and change enemy movement. |
|
||||
| Mission | Duration, `auto_complete` (default true), optional briefing artwork, ordered tick-based spawns, terrain tile definitions and row-major layout, column count, next mission, optional music asset and script. Scripts can manage objectives, spawn waves and decide success/failure. |
|
||||
| Campaign | Name, optional menu artwork and ordered mission IDs. Missions and campaigns appear through the same registries as original content. |
|
||||
|
||||
Simulation uses integer coordinates in the original 320×200 logical canvas and runs at 70/3 Hz: one tick is 3/70 seconds. Artwork detail does not enlarge collision boxes. Script movement and projectile speed are bounded to 128 logical pixels per tick; spawn and movement coordinates must stay within ±32,768.
|
||||
|
||||
JSON Schemas are checked into `schemas/`: `mod-v1.json`, `content-v1.json`, `hook-v1.json`, `weapon-projectile-v1.json`, `item-v1.json`, `enemy-v1.json`, `mission-v1.json` and `campaign-v1.json` and `sound-v1.json`. Schemas describe serialization shape; the validation CLI also checks references, numeric limits, package paths, dependencies, artwork and script compilation. Generate them from the Rust types with:
|
||||
|
||||
```sh
|
||||
cargo run --no-default-features -- --schemas schemas
|
||||
```
|
||||
|
||||
## Artwork
|
||||
|
||||
An `art` object requires exactly one of `asset` (an original numeric catalog ID) or `png` (a package-relative PNG path), plus `logical_width` and `logical_height`. `frames` defaults to 1. `nearest = true` opts that artwork out of Scale2x/3x/4x filtering.
|
||||
|
||||
```json
|
||||
{
|
||||
"png": "art/cannon.png",
|
||||
"logical_width": 16,
|
||||
"logical_height": 16,
|
||||
"frames": 4
|
||||
}
|
||||
```
|
||||
|
||||
PNG animations are horizontal strips of equally sized frames. For example, four 64×64 frames use a 256×64 PNG and may declare logical dimensions of 16×16. The strip width must divide evenly by `frames`; supply consistent canvases and alignment across frames. Alpha below 128 is transparent. Opaque colors are mapped to the selected game palette so palette effects remain compatible. Artwork already detailed enough for the selected rendering scale bypasses automatic sprite enlargement. Original asset animations use consecutive catalog IDs. Terrain artwork and destroyed terrain replacements follow the same format.
|
||||
|
||||
Artwork dimensions and decoding allocation are bounded. PNGs may be up to 4096 pixels in either direction, with at most 4,194,304 pixels and a 64 MiB decoding allocation limit. Logical dimensions are separately validated and stay independent of physical PNG resolution. The extracted original catalog and local base-game remaster proofs stay outside version control; authored mod artwork and sounds are included in Git.
|
||||
|
||||
## Rhai callbacks
|
||||
|
||||
Every callback takes `(event, state, random)` and returns a map containing `commands` and `state`. All scripts referenced by definitions must appear in `manifest.scripts`. A missing optional callback does nothing; a declared callback with the wrong parameter count is rejected during compilation.
|
||||
|
||||
```javascript
|
||||
fn on_fire(event, state, random) {
|
||||
let shots = if state.contains("shots") { state.shots + 1 } else { 1 };
|
||||
#{
|
||||
commands: [#{
|
||||
op: "projectile",
|
||||
weapon: "aurora:pulse_projectile",
|
||||
x: event.x,
|
||||
y: event.y,
|
||||
vx: (random[0] % 3) - 1,
|
||||
vy: -8
|
||||
}],
|
||||
state: #{ shots: shots }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Callback | Definition and event fields |
|
||||
| --- | --- |
|
||||
| `on_spawn` | Enemy: `entity`, `x`, `y`. |
|
||||
| `on_tick` | Enemy: `entity`, `x`, `y`, `health`, `tick`, `player_x`, `player_y`. Mission: `tick`, `player_x`, `player_y`. |
|
||||
| `on_fire` | Weapon or firing enemy: `entity`, `x`, `y`, `hostile`, `tick`; enemy callbacks also include `weapon`. Player entity is zero. The normal shot has already been emitted. Enemy callbacks run for each emitted gun shot. |
|
||||
| `on_hit` | Weapon/projectile and damaged enemy: `entity`, `projectile`, `damage`, `x`, `y`; enemy events also include `weapon` and remaining `health`. Scripted enemy damage uses `scripted = true` and omits projectile-specific fields. |
|
||||
| `on_death` | Enemy: `entity`, `x`, `y`. The entity is retired; use queries to act on surviving entities. |
|
||||
| `on_pickup` | Item: pickup `entity`. The item and its standard effects have already been awarded. |
|
||||
| `on_use` | Selected special item without a weapon: player `entity = 0`, `x`, `y`, `tick`. Triggered on the rising edge of special input; standard effects and consumption happen first. |
|
||||
| `on_mission_start` | Mission: `tick = 0`. Runs when the first simulation step dispatches events. |
|
||||
| `on_mission_end` | Mission: `success`, `tick`. |
|
||||
|
||||
Every dispatched event also includes `tick`, `player`, `entities`, `projectiles`, `pickups` and `objectives`. `player` is a snapshot including position, velocity, shields, credits, inventory and selected equipment. `entities` is the current enemy array; each entry includes `entity`, `definition`, position, health, animation and movement state. Projectiles and pickups likewise carry stable entity IDs. These are read-only snapshots: return commands to change the simulation. Later callbacks in a tick can observe commands committed by earlier callbacks. Events raised by commands, such as a spawned enemy, are dispatched on the following tick.
|
||||
|
||||
`state` is JSON-serializable and shared by all callbacks in the same script file. It starts as an empty map, is keyed by `mod_id/script_path`, and is saved with the pilot. Avoid values dependent on wall time, filesystem access or rendering rate. `random` contains 32 deterministic nonnegative integers in the range 0–32,767. Each successful callback advances that script's saved RNG by exactly 32 values, whether or not the script reads them. Missing callbacks do not advance state or RNG.
|
||||
|
||||
Commands apply only after a callback returns and passes validation. If a callback or any command fails, the complete simulation tick restores its previous world, script state and script RNG. Runtime errors identify the mod, source file, callback and Rhai source location when available. In the desktop engine a script failure returns to the pause screen with the error; fix the package, rescan and reload at the main menu.
|
||||
|
||||
## Commands
|
||||
|
||||
Use the `op` field shown below. All fields listed are required unless stated otherwise. Entity zero identifies the player for damage; movement commands currently address enemies.
|
||||
|
||||
| `op` | Fields | Effect |
|
||||
| --- | --- | --- |
|
||||
| `spawn` | `enemy`, `x`, `y` | Spawn a registered enemy. |
|
||||
| `projectile` | `weapon`, `x`, `y`, `vx`, `vy`, optional `hostile` (false) | Spawn a projectile using a registered weapon. |
|
||||
| `damage` | `entity`, `amount` | Deal 0–100,000 damage to the player or an existing enemy. |
|
||||
| `despawn` | `entity` | Remove an existing enemy or projectile. |
|
||||
| `move` | `entity`, `x`, `y` | Move an enemy and rebase its movement path. |
|
||||
| `velocity` | `entity`, `vx`, `vy` | Set enemy velocity. |
|
||||
| `give_item` | `item`, `count` | Add registered inventory, clamped to item capacity; adding a new entry beyond the 20-item limit fails. |
|
||||
| `consume_item` | `item`, `count` | Remove owned inventory; insufficient quantity fails. |
|
||||
| `credits` | signed `amount` | Adjust credits with saturation at valid limits. |
|
||||
| `shield` | signed `amount` | Adjust ordinary shield, clamped to 0–100. |
|
||||
| `effect` | `x`, `y` | Add an explosion effect. |
|
||||
| `sound` | numeric `asset` | Queue an original digital sound asset for playback. |
|
||||
| `play_sound` | namespaced `sound` ID | Queue a validated package-local sound. |
|
||||
| `objective` | `key`, integer `value` | Set an objective counter. Use a namespaced key. |
|
||||
| `complete` | none | Complete the mission. |
|
||||
| `fail` | none | Fail the mission. |
|
||||
|
||||
Rhai uses an explicit package allowlist for arithmetic, strings, collections, iteration and function helpers. The default language/time packages are excluded, so wall-clock timestamps and native `sleep` cannot be reached, including through function pointers. There are no exposed filesystem, network or OS interfaces. Dynamic `eval` and imports are disabled. Limits are 100,000 operations per callback and a shared 500,000-operation budget per simulation tick; 32 call levels; expression depths of 64/32; arrays of 4,096 entries; maps of 1,024 entries; strings of 16 KiB; 256 commands per callback; and 64 KiB of serialized state per script. See the [Rhai execution-limit API](https://docs.rs/rhai/latest/rhai/struct.Engine.html) for the underlying engine controls. Scripts cannot increase these limits.
|
||||
|
||||
## Saves and compatibility
|
||||
|
||||
Rust pilots use separate, versioned JSON files under `$XDG_DATA_HOME/RaptorReplica`, and settings use TOML under `$XDG_CONFIG_HOME/RaptorReplica`. Atomic writes replace completed saves after file and directory synchronization. Legacy saves are read and imported; their original bytes are not modified.
|
||||
|
||||
Each pilot records the ordered mod IDs, package versions, full content hashes, script states and script RNG positions. Loading requires the same active pins and all referenced equipment, missions and progression. Changing a package without changing its version still changes its hash and blocks that save. Disabling a mod preserves its saved inventory and progression; it never silently discards them. Restore the matching package/version and load order, or use another pilot. This API does not yet provide mod save migrations.
|
||||
|
||||
## Validation tests
|
||||
|
||||
`cargo test --no-default-features --test modding` uses synthetic definitions and temporary packages. It checks dependency ordering, version/API failures, conflicts, cycles, duplicate namespaces, changed hashes, traversal/symlinks, file/depth limits, explicit override priority, registry rollback, script budgets, command failure rollback, RNG resumption, incompatible saves and example callbacks. These tests do not require the original game assets. The validation CLI above provides local integration coverage against the separately installed asset catalog.
|
||||
|
||||
## SD/HD artwork and local sound effects
|
||||
|
||||
An artwork definition may add `hd_png`, a separate package-relative PNG with the same logical canvas, frame count and aspect ratio as the SD `png` or original `asset`. HD inputs require at least twice the logical resolution. The Options HD toggle selects it; missing variants retain SD artwork. Detailed mod terrain is drawn on the higher-resolution canvas over the joined SD fallback. Authored mod art/audio is included in Git; original catalog references remain excluded.
|
||||
|
||||
`sounds` definitions have `id`, `name`, `file` (relative path) and optional `volume` (0–127, default127), `pan` (0–255, default127), `pitch` (0–255, default128) and `priority` (0–100000, default1). Lower numeric priority wins, matching the original DSP. Play one with `#{op:"play_sound",sound:"your_mod:shot"}`. WAV inputs must be uncompressed PCM8/16, mono/stereo, 8–48kHz, at most30seconds/8MiB. Stereo is mixed to mono and converted to the original unsigned8-bit digital format; channel/pan/pitch/volume use the original mixer. Original DMX patches are also accepted. Invalid/missing audio rejects activation.
|
||||
|
||||
For objective-driven missions set `auto_complete=false` and explicitly return `complete` or `fail`. Scripted endings dispatch `on_mission_end` in the same transaction. Commands can create further hook events, bounded by the shared execution budget and an8192-event tick limit.
|
||||
@@ -1,85 +0,0 @@
|
||||
# Rust engine and validation
|
||||
|
||||
The Rust binary never calls or embeds the C++ engine. SDL2 provides window/input/controller/audio services. Simulation and the validation CLI build without SDL2. The C++ checkout stays available for controlled reference comparisons and normal play until full acceptance passes.
|
||||
|
||||
| Module | Responsibility |
|
||||
| --- | --- |
|
||||
| assets | Checked catalog, palettes, raw/span pictures, libraries, maps and demo records. |
|
||||
| content, legacy_tables | Typed registries and generated original numeric mappings. |
|
||||
| simulation | Rational clock, integer motion, shared RNG, collision/weapon/enemy/terrain rules, script transactions. |
|
||||
| render, hd | Indexed canvas, ScaleNx, caches, PNG/palette conversion and optional source-hashed HD art. |
|
||||
| audio, sound | Original MUS/GENMIDI FM and digital DSP behavior; package-local PCM WAV decoding. |
|
||||
| cinematics | Checked AGX delta frames and movie timing. |
|
||||
| persistence | TOML settings, versioned JSON pilots, atomic writes and read-only legacy import. |
|
||||
| modding | Packages, dependency ordering, overrides, bounded Rhai callbacks/state and save pins. |
|
||||
| desktop | SDL2 services, UI, shop/equipment, mod manager, settings and interpolation. |
|
||||
|
||||
## Build and run
|
||||
|
||||
Rust 1.88+, Cargo and SDL2 development libraries are required. A local SDL2 installation in `~/.local/lib` is supported. The launcher sets the library path and uses a release build:
|
||||
|
||||
```sh
|
||||
./play.sh --engine rust
|
||||
./play.sh --engine rust --mods examples/mods
|
||||
```
|
||||
|
||||
For development and tests:
|
||||
|
||||
```sh
|
||||
export LD_LIBRARY_PATH="$HOME/.local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
cargo test --locked
|
||||
cargo test --locked --no-default-features
|
||||
RAPTOR_TEST_DATA="$PWD/data" cargo test --locked
|
||||
RAPTOR_TEST_DATA="$PWD/data" cargo test --locked -- --ignored
|
||||
```
|
||||
|
||||
Synthetic fixtures contain no copyrighted or generated game pixels. Original integration checks are opt-in and local. Native audio comparisons compile a small retained-source harness with g++ and SDL2 headers; pure Rust playback itself uses no C++ runtime.
|
||||
|
||||
## Profiles and settings
|
||||
|
||||
Rust uses `$XDG_DATA_HOME/RaptorReplica/pilot-NNNN.json` and `mods/`, `$XDG_CONFIG_HOME/RaptorReplica/config.toml`, and `$XDG_CACHE_HOME/RaptorReplica/` for derived assets. Normal XDG defaults apply. Pilots record active package versions/hashes and script state/RNG. Missing or changed requirements block loading, without deleting equipment or replacing the save.
|
||||
|
||||
Legacy `~/.local/share/Raptor/SETUP.INI` and pilot files are imported read-only. All difficulties, waves, portrait and training metadata are retained. Rust writes only its separate versioned files. Explicit CLI import output cannot be the original file.
|
||||
|
||||
Options include scaling, scanlines, aspect correction, fullscreen/borderless, input bindings/controller, volumes and optional HD art. New/load pilot controls keep incompatible save slots separate. The Mods screen applies validated selections at the main menu; selections made during flight remain pending until returning there.
|
||||
|
||||
## Local asset and platform checks
|
||||
|
||||
```sh
|
||||
cargo run --locked --no-default-features -- --data data --verify-assets
|
||||
cargo run --locked --no-default-features -- --data data --mods examples/mods --validate-mods aurora
|
||||
cargo run --locked --no-default-features -- --data data --mods examples/mods \
|
||||
--enabled aurora --headless aurora:field_trials --ticks 700
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy cargo run --locked -- \
|
||||
--data data --profile-root .reference/smoke --smoke-frames 5 \
|
||||
--screenshot .reference/menu.png
|
||||
```
|
||||
|
||||
Use real SDL video for interactive/display-performance checks. Dummy screenshots verify raster placement, not GPU/vsync performance. `--preview-mission ID` starts a mission; `--preview-cinematic intro|landing|death|end1|end2|end3` previews movies. Frame-limited runs do not save pilots/settings; cache stays within the selected profile.
|
||||
|
||||
## Controlled reference replays
|
||||
|
||||
Rebuild the current C++ source before recording. Trace output is opt-in during legacy demo playback and records complete updates after palette/stat processing, which also consumes the shared RNG.
|
||||
|
||||
```sh
|
||||
mkdir -p .reference
|
||||
cmake --build raptor/build --target raptor --parallel 8
|
||||
(cd data && env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
XDG_DATA_HOME="$PWD/../.reference/legacy-profile" \
|
||||
RAPTOR_REFERENCE_TRACE="$PWD/../.reference/cpp-demo.jsonl" \
|
||||
RAPTOR_REFERENCE_TICKS=1000 \
|
||||
../raptor/build/bin/raptor PLAY "$PWD/demos/DEMO1G1_REC")
|
||||
cargo run --locked --no-default-features -- --data data \
|
||||
--replay DEMO1G1_REC --ticks 1000 --trace .reference/rust-demo.jsonl
|
||||
python3 tools/compare_replays.py .reference/cpp-demo.jsonl .reference/rust-demo.jsonl
|
||||
```
|
||||
|
||||
The first 1,000 steps of DEMO1G1_REC have matched all captured fields: RNG, player coordinates/bank/shields/credits, ordered enemy position/health/animation/path state and ordered projectile assets/positions. The comparator fails at first divergence or unequal lengths. This is a subset of full state: add inventory, effects, objectives and all internal AI state before declaring complete parity. That demo starts sector 1 wave 3, so it does not validate the first mission or every campaign. Recordings stay ignored in `.reference/`.
|
||||
|
||||
## Audio and remaining acceptance
|
||||
|
||||
The Rust FM/DSP implementation has synthetic sample-by-sample native comparisons and a four-second original intro-music comparison at 44.1kHz. These establish those cases, not every track/input/output rate. The Rust core retains the original envelope/noise semantics and LGPL source provenance.
|
||||
|
||||
Longer replays, all campaign/difficulty paths and end-to-end boss playthroughs still require verification. Original cinematics decode/play locally but exact fade/transition/custom-palette fidelity needs comparison. Some menu layouts differ. HD coverage is partial and display performance must be measured before cutover.
|
||||
|
||||
Scale2x/3x follows the [published algorithm](https://www.scale2x.it/algorithm); Scale4x applies Scale2x twice. WAV parsing follows the [RIFF chunk format](https://learn.microsoft.com/en-us/windows/win32/xaudio2/resource-interchange-file-format--riff-). Original catalog/reference assets remain outside version control. Authored mod artwork and sounds are tracked.
|
||||
@@ -1,83 +0,0 @@
|
||||
# Original mission ending and death
|
||||
|
||||
The Rust simulation now follows the retained engine's `RAP_ThinkStats` ending
|
||||
sequence for missions with `legacy_map`. Authored mod missions retain their
|
||||
immediate completion/failure contract. This is a source-based port with portable
|
||||
synthetic regressions; a C++ recording through a complete ending or death is
|
||||
still required before claiming transition parity.
|
||||
|
||||
## Departure
|
||||
|
||||
Removing the final eligible original enemy arms a 60-step countdown before
|
||||
hostile-shot processing, matching `ENEMY_Remove`. Player damage is suppressed
|
||||
while the countdown is active, matching `OBJS_SubEnergy`. At the start of each
|
||||
subsequent step the countdown decrements after input and before world updates.
|
||||
An entry value of zero still executes that final simulation step before the
|
||||
outcome changes.
|
||||
|
||||
At 40, control pauses and `FX_FLYBY` is emitted. Below 40, the ship moves toward
|
||||
the horizontal corridor 152–168 in eight-pixel increments and upward four
|
||||
pixels. The original paused input phase retains the prior forced delta; the
|
||||
statistics phase then applies the new delta. Consequently most departure steps
|
||||
contain both moves. Boundary clamping is suspended during the ending. A healthy
|
||||
ship remains drawable as it leaves the screen; `Outcome::Complete` follows the
|
||||
final simulation pass, rather than appearing while the ship is stationary.
|
||||
|
||||
`World.control_paused`, `departure_delta`, `end_timer` and `draw_player` expose
|
||||
these states to the SDL-independent renderer/input consumer. The synthetic
|
||||
stationary-pilot fixture, starting with an already armed countdown, completes
|
||||
after 61 steps at `(160, -160)`.
|
||||
|
||||
Original demo playback assigns recorded coordinates and banking directly and
|
||||
bypasses both automatic moves, matching `IPT_MovePlayer`'s playback early return.
|
||||
Coordinates captured at the end of a newly authored step do not necessarily
|
||||
represent the original recording phase; use deterministic keyboard-input replay
|
||||
for strict reproduction of an entire newly recorded ending.
|
||||
|
||||
## Death
|
||||
|
||||
Zero energy starts the same countdown instead of immediately failing the
|
||||
mission. Each statistics pass requests a medium-air and small-air explosion
|
||||
with randomized coordinates. Above 24 it also emits a randomly panned air
|
||||
explosion sound; the original randomized sound pitch consumes the shared RNG.
|
||||
|
||||
At 24, `draw_player` becomes false. The sequence requests its centered explosion,
|
||||
three explosion sounds and 512 alternating large-air/air-blast effects. The
|
||||
original animation pool holds at most 100 effects. All 1,024 burst-coordinate
|
||||
RNG calls still occur when that pool fills. The portable fixture verifies the
|
||||
first randomized positions, sound pan choices, the pool limit and exact RNG
|
||||
advance at the burst, including the separate palette-update RNG call. A pilot
|
||||
whose energy is initially zero remains `Playing` through the visible explosion
|
||||
sequence and reaches `Failed` after 62 steps.
|
||||
|
||||
Low-energy equipment loss runs after the death/departure statistics, preserving
|
||||
the existing shared RNG and automatic weapon-selection behavior.
|
||||
|
||||
## Verification
|
||||
|
||||
Run the portable regressions without original assets:
|
||||
|
||||
```sh
|
||||
cargo test --no-default-features --test transitions --test gameplay
|
||||
```
|
||||
|
||||
The transition suite has five tests covering successful departure, direct demo
|
||||
positions, immunity beginning on final-enemy removal, delayed death with the
|
||||
burst RNG/pool cap, and authored mod outcome compatibility. The gameplay suite
|
||||
also retains beam ordering, raw demo bounds, equipment loss, kamikaze movement,
|
||||
Energy Grab, Ground Bomb and Mega Bomb checks.
|
||||
|
||||
After this change, the complete available `DEMO1G1_REC` still matched the rebuilt
|
||||
C++ engine's gameplay snapshots across all 1,889 simulation steps. That recording
|
||||
ends before its boss, so it proves the transition changes did not disturb the
|
||||
captured pre-ending behavior; it does not validate the new transition itself.
|
||||
See [campaign-validation.md](campaign-validation.md) for reference rebuild and
|
||||
comparison commands, captured fields, original-data checks and limitations.
|
||||
|
||||
The relevant retained sources are `raptor/src/rap.cpp` (`RAP_ThinkStats` and
|
||||
`Do_Game`), `raptor/src/rap.h` (`END_DURATION`, `END_EXPLODE`, `END_FLYOFF`),
|
||||
`raptor/src/input.cpp` (`IPT_MovePlayer`, `IPT_FMovePlayer`),
|
||||
`raptor/src/enemy.cpp` (`ENEMY_Remove`) and `raptor/src/objects.cpp`
|
||||
(`OBJS_SubEnergy`). God-mode/debug behavior, rumble, warning-light presentation,
|
||||
cinematic sequencing and a native transition reference are outside this scoped
|
||||
verification.
|
||||
@@ -1,57 +0,0 @@
|
||||
# Sprite and terrain validation
|
||||
|
||||
`tests/visuals.rs` separates portable synthetic checks from local checks of the original catalog. Generated contact sheets and original pixels stay under ignored `.reference/visuals/`; they are not shipped with the engine or committed.
|
||||
|
||||
The independent test oracle follows the unoptimized corner rules in the [published Scale2x/Scale3x algorithm specification](https://www.scale2x.it/algorithm), with its clamped border sampling. Scale4x applies the independent Scale2x oracle twice. The renderer uses a shared optimized guard, so the oracle and renderer express the rules differently.
|
||||
|
||||
The portable suite checks all 19,683 neighborhoods over two palette colors plus transparency, additional multi-color patterns and one-pixel borders. It verifies exact output indices, opaque palette indices 0/255, transparent composition, palette changes without changing stored indices, clipping on every viewport edge, indexed shadow masks, sharply scaled bitmap text and neighbor context at terrain boundaries. Its independent-tile fixture deliberately produces different results when tiles are filtered separately, so the seam check cannot pass trivially. Shadow checks cover six-bit RGB clamping, the original Manhattan palette-distance and last-equal tie rules, native ground-mask offsets, signed air projection, collapsed-row suppression, sparse spans, clipping at 1×..4× and interpolated translation without coverage loss.
|
||||
|
||||
Run portable checks with:
|
||||
|
||||
```sh
|
||||
cargo test --test visuals
|
||||
```
|
||||
|
||||
Run the original-data checks and create private contact sheets with:
|
||||
|
||||
```sh
|
||||
cargo test --test visuals original_sequences_preserve_canvases_palette_origins_and_terrain_context -- --ignored
|
||||
cargo test --test visuals original_shadow_tables_and_banking_masks_match_indexed_projection -- --ignored
|
||||
```
|
||||
|
||||
The local checks cover all seven light, dark and firing-flash player banking frames; all 42 `GEXPLO_BLK` ground explosion frames; all 16 `AIRBOOM_PIC` and 13 `EXPLO2_BLK` frames; four micro-missile and gun-ejecta frames; six `SHIP22G1_PIC` boss frames and four `SHIP08G1_PIC` enemy frames. Each original frame is compared against the oracle at 1×, 2×, 3× and 4×, with exact logical canvas dimensions and original palette labels retained. The report records frame IDs, logical canvases, full-canvas center anchors and visible bounds at every scale.
|
||||
|
||||
The original 20-byte GFX header contains type, two option fields, width and height; its option fields are not replacement x/y origins. Span x/y coordinates locate pixels within the unchanged canvas. Player rendering uses that top-left canvas origin. Original centered effects use half the full canvas width/height, as recorded by `ANIMS_Register` in `raptor/src/anims.cpp`; visible explosion bounds must not become animation anchors. Missing transparent margins or auto-cropping frames would move those anchors.
|
||||
|
||||
A three-by-three patch from the first original mission is composed at native resolution and filtered as one image. The center tile filtered with two original pixels of neighboring context must exactly match the center of the full joined patch at 2×, 3× and 4×. The renderer's two-pixel terrain gutter covers Scale4x's two successive passes. This establishes filter consistency across the tested boundaries; it does not assess whether different source tiles were artistically seamless.
|
||||
|
||||
Each sequence emits `<NAME>-nearest4.png`, `<NAME>-scale4.png` and `<NAME>-runtime4.png`. The runtime sheet applies the engine's per-asset fallback. Read left-to-right, then top-to-bottom, up to seven frames per row. Terrain comparisons are `terrain-joined-2x.png`, `terrain-joined-3x.png`, `terrain-joined-4x.png` and matching `terrain-independent-*` files. `report.json` stores frame order, bounds, opaque pixel counts, fallback flags, selected terrain row and the number of pixels changed by independent tile filtering.
|
||||
|
||||
## Review findings
|
||||
|
||||
On 2026-10-10, all fourteen portable tests and both local catalog checks passed. The sequence check covered all 110 frames at every scale plus the terrain patch. Complete nearest-neighbor and Scale4 contact sheets were inspected for banking, explosions, projectiles, the sample enemy and the opening boss animation. No canvas shift, clipped replacement canvas, lost palette identity or changed simulation dimensions was found. Banking retains the nose and engine registration across all seven poses. Large explosion lobes and expanding rings remain aligned throughout their sequences.
|
||||
|
||||
Two nearest-neighbor fallback groups are implemented in `render::prefers_nearest`, covering 26 original frames:
|
||||
|
||||
- `NMSHOT_BLK` frames 0..3, assets `0x101c3..0x101c6`: each frame has four visible source pixels. At 4×, ScaleNx keeps 44 opaque pixels against nearest-neighbor's 64, reducing visible coverage by 31.25% and changing the small square mark into a round bead. The implemented nearest-neighbor fallback preserves this tiny projectile's brightness and shape.
|
||||
- Sparse late `GEXPLO_BLK` frames 20..41, assets `0x10178..0x1018d`: Scale4 reduces opaque coverage by approximately 10..25%. The implemented nearest-neighbor fallback preserves those residual embers; the large early flame frames retain the smoother filter. The initial 17-pixel flash also loses about 10.3% coverage and remains a candidate for the tiny-effect fallback after playback review.
|
||||
|
||||
The original-data test exercises the actual `SpriteCache` fallback route at all four factors and asserts exact nearest-neighbor pixels and full original opaque coverage for every fallback frame. Other frames must still match ScaleNx. Supplied HD PNGs bypass this original-asset filtering policy and retain their detail.
|
||||
|
||||
The desktop shadow pass follows `shadow.cpp` and `gfxapi.cpp`: ground masks use the -9 palette table at (-3,+4); aircraft and player masks use a separate -6 table at (-10,+20), projected through view (160,100,1000), depth 1280 and distance 200. The renderer retains signed integer division followed by arithmetic shift, inclusive horizontal span endpoints, and first-row coverage when several source rows collapse to one projected row. It shades existing palette indices rather than painting flat translucent black. Ground shadows precede ground enemies; air and player shadows follow ground enemies and precede ground effects and aircraft. Legacy enemy field 11 controls presence, with the original 25-ground/50-air limits. Mod enemies receive the corresponding ground/air shadow from their supplied SD logical canvas. Optional HD artwork retains this logical shadow mask.
|
||||
|
||||
The original shadow code's preliminary bounds check projects its origin twice and calculates height using the projected x coordinate. Rust clips each actual projected span instead, correcting missing valid shadows near the lower edge. This is a presentation culling repair; original span projection and palette mapping are retained. It does not alter collisions or simulation state.
|
||||
|
||||
The local shadow test compares all 256 entries of the original -9, -6 and +9 tables against an independent distance/tie oracle. It verifies all seven player banking masks against an independent native span raster at all four factors and emits `player-shadow-banking-4x.png`, `ground-shadow-4x.png` and `shadow-tables.json` under the private visual directory. The first contact sheet pairs each complete banking pose with its projected shadow on original terrain; the second checks native ground offset and registration.
|
||||
|
||||
Both shadow contacts were inspected after the tests passed. The seven banking shadows retain their changing silhouette and full-canvas registration without transparent rectangles or missing lower-screen spans. The ground defense shadow retains the native (-3,+4) offset. Shadow pixels remain terrain palette colors, so palette changes and indexed composition remain available.
|
||||
|
||||
The inspected light ship frames retain 97.2..98.3% of nearest-neighbor opaque coverage at 4×, dark/flash frames retain at least 97.8%, the large boss retains at least 99.6% and the sample enemy retains 99.9%. These are presentation measurements, not collision masks. Their full original logical canvases remain unchanged. No nearest-neighbor fallback is recommended for those reviewed sequences.
|
||||
|
||||
The original terrain patch exposed 113 differing pixels at 2×, 297 at 3× and 727 at 4× when the tiles were filtered independently. Filtering the joined patch with the tested two-pixel neighbor gutter exactly matched the full-image center at each factor. The paired contact sheets show subtle texture discontinuities along independent tile boundaries; joined filtering retains the diagonal water transition. This review covers the crisp original terrain path; supplied HD terrain needs its own artistic edge review.
|
||||
|
||||
The optional HD player proof remains a separate review: it replaces only straight flight, so banking still changes visual style. The generated extreme-bank attempt remains excluded because it shortened the engines and failed the footprint check; crisp original fallback is retained for that frame.
|
||||
|
||||
The frontend previously requested `AIRMED_BLK` for effects, but that name does not exist in the original catalog. Actual original sequences include `AIRBOOM_PIC`, `GEXPLO_BLK`, `EXPLO2_BLK`, flak and sparks. This finding was reported to the engine owner. A generic explosion sequence repairs the visible fallback, but full effect identity, duration, layer, origin and update parity require separate comparison with the C++ animation system.
|
||||
|
||||
The new enemy packages include their generated SD/HD artwork and audio in Git. Their original style-reference pixels under `art/source/` remain excluded. Their per-package `art-qa.json` and `provenance.json` provide a separate record of supplied-art canvas alignment and generation prompts.
|
||||
Reference in new issue
Block a user