Make RustRaptor the repository root and track its playable bundle
This commit is contained in:
1 parent
4b1edefeec
commit
8f459796b1
9860 files changed
+21084
-61019
No files matched your search
@@ -0,0 +1,344 @@
|
||||
//! Compile with validate.py; runs the actual simulation and Rhai callbacks.
|
||||
use raptor_replica::{
|
||||
assets::Catalog,
|
||||
content::{ContentId, Registry},
|
||||
modding::{self, ActiveMods, Command},
|
||||
persistence::Pilot,
|
||||
simulation::{Input, Outcome, World},
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
const MISSION: &str = "iron_coast_mission_4:mission_04";
|
||||
const MODS: [&str; 8] = [
|
||||
"iron_coast_scout",
|
||||
"iron_coast_turret",
|
||||
"iron_coast_boss_4",
|
||||
"iron_coast_music",
|
||||
"pulse_cannon",
|
||||
"seeker_swarm",
|
||||
"ion_lance",
|
||||
"iron_coast_mission_4",
|
||||
];
|
||||
|
||||
fn active() -> ActiveMods {
|
||||
let project = PathBuf::from(std::env::var("RAPTOR_PROJECT").unwrap());
|
||||
let catalog = Catalog::open(project.join("data")).unwrap();
|
||||
let base = Registry::base(&catalog).unwrap();
|
||||
let discovery = modding::discover(&project.join("RustRaptor/mods"));
|
||||
ActiveMods::build(
|
||||
&base,
|
||||
&discovery,
|
||||
&MODS.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
||||
Some(&catalog),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn pilot(shield: u32) -> Pilot {
|
||||
let mut pilot = Pilot::default();
|
||||
pilot.inventory.insert("base:energy".into(), shield);
|
||||
pilot.credits = if shield == 75 { 10_000 } else { 0 };
|
||||
pilot
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NativeProof {
|
||||
seen: BTreeSet<String>,
|
||||
moved: BTreeSet<String>,
|
||||
positions: BTreeMap<u64, (i32, i32)>,
|
||||
}
|
||||
|
||||
fn play(pilot: &Pilot, active: &mut ActiveMods) -> (World, NativeProof) {
|
||||
let mut world = World::new(&MISSION.into(), pilot, &active.registry).unwrap();
|
||||
let mut proof = NativeProof::default();
|
||||
for _ in 0..4000 {
|
||||
for enemy in &world.enemies {
|
||||
if enemy.definition.0.starts_with("base:enemy_") {
|
||||
proof.seen.insert(enemy.definition.0.clone());
|
||||
let prior = proof.positions.insert(enemy.entity, (enemy.x, enemy.y));
|
||||
if prior.is_some_and(|p| p != (enemy.x, enemy.y)) {
|
||||
proof.moved.insert(enemy.definition.0.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let target = world
|
||||
.enemies
|
||||
.iter()
|
||||
.filter(|e| e.health > 0 && e.y >= 0 && e.y < 130)
|
||||
.min_by_key(|e| {
|
||||
(
|
||||
if e.definition.0 == "iron_coast_turret:unit" {
|
||||
0
|
||||
} else if e.definition.0 == "iron_coast_boss_4:unit" {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
},
|
||||
(world.player.x - e.x).abs(),
|
||||
)
|
||||
});
|
||||
let target_x = target.map(|e| e.x + e.width / 2 - 16).unwrap_or(144);
|
||||
let x = world.player.x + (target_x - world.player.x).clamp(-4, 4);
|
||||
world
|
||||
.step(
|
||||
Input {
|
||||
fire: true,
|
||||
absolute: Some([x, 160]),
|
||||
..Input::default()
|
||||
},
|
||||
&active.registry,
|
||||
&mut active.scripts,
|
||||
)
|
||||
.unwrap();
|
||||
if world.outcome != Outcome::Playing {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(world, proof)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starter_registration_and_default_fixture_complete_with_real_inputs() {
|
||||
for shield in [75, 100] {
|
||||
let mut active = active();
|
||||
let (world, proof) = play(&pilot(shield), &mut active);
|
||||
eprintln!(
|
||||
"Stormwall start={shield}, tick={}, outcome={:?}, shield={}, credits={}, objectives={:?}",
|
||||
world.tick, world.outcome, world.player.shield, world.player.credits, world.objectives
|
||||
);
|
||||
assert_eq!(world.outcome, Outcome::Complete);
|
||||
assert!(world.player.shield > 0);
|
||||
assert_eq!(world.objectives["iron_coast_mission_4:complete"], 1);
|
||||
assert!(world.objectives["iron_coast_turret:defeated"] >= 4);
|
||||
assert!(world.objectives["iron_coast_boss_4:defeated"] >= 1);
|
||||
assert_eq!(proof.seen.len(), 6, "native seen: {:?}", proof.seen);
|
||||
assert_eq!(proof.moved.len(), 6, "native moved: {:?}", proof.moved);
|
||||
for id in &proof.seen {
|
||||
let def = &active.registry.enemies[&ContentId::from(id.as_str())];
|
||||
assert!(def.legacy.is_some());
|
||||
assert!(def.script.is_none());
|
||||
assert!(def.path.len() >= 3);
|
||||
assert_eq!(
|
||||
active.registry.owners[&ContentId::from(id.as_str())],
|
||||
"base"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
!world
|
||||
.player
|
||||
.inventory
|
||||
.contains_key(&ContentId::from("pulse_cannon:equipment"))
|
||||
);
|
||||
assert_eq!(
|
||||
active.scripts.states["iron_coast_mission_4/scripts/mission.rhai"]["loaned"],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_replay_reuses_script_state_without_stale_death_flags() {
|
||||
let mut active = active();
|
||||
for _ in 0..2 {
|
||||
let (world, proof) = play(&pilot(75), &mut active);
|
||||
assert_eq!(world.outcome, Outcome::Complete);
|
||||
assert_eq!(proof.moved.len(), 6);
|
||||
assert_eq!(world.objectives["iron_coast_boss_4:defeated"], 1);
|
||||
assert_eq!(world.objectives["iron_coast_mission_4:debrief"], 1);
|
||||
assert!(
|
||||
!world
|
||||
.player
|
||||
.inventory
|
||||
.contains_key(&ContentId::from("pulse_cannon:equipment"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owned_cannon_and_full_inventory_can_win_without_losing_equipment() {
|
||||
let mut active = active();
|
||||
let mut owned = pilot(75);
|
||||
owned.inventory.insert("pulse_cannon:equipment".into(), 1);
|
||||
let (world, _) = play(&owned, &mut active);
|
||||
assert_eq!(world.outcome, Outcome::Complete);
|
||||
assert_eq!(
|
||||
world.player.inventory[&ContentId::from("pulse_cannon:equipment")],
|
||||
1
|
||||
);
|
||||
|
||||
let mut full = pilot(75);
|
||||
for id in active.registry.items.keys() {
|
||||
if full.inventory_slots() == 20 {
|
||||
break;
|
||||
}
|
||||
if id.0.starts_with("base:") && id.0 != "base:super_shield" {
|
||||
full.inventory.entry(id.clone()).or_insert(1);
|
||||
}
|
||||
}
|
||||
assert_eq!(full.inventory_slots(), 20);
|
||||
let original = full.inventory.clone();
|
||||
let (world, _) = play(&full, &mut active);
|
||||
assert_eq!(world.outcome, Outcome::Complete);
|
||||
assert_eq!(world.objectives["iron_coast_mission_4:trial_issued"], 0);
|
||||
assert!(
|
||||
!world
|
||||
.player
|
||||
.inventory
|
||||
.contains_key(&ContentId::from("pulse_cannon:equipment"))
|
||||
);
|
||||
for (id, count) in original {
|
||||
if id.0 != "base:energy" {
|
||||
assert_eq!(world.player.inventory[&id], count, "changed equipment {id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn late_surviving_turrets_remain_reachable() {
|
||||
let mut active = active();
|
||||
let mut world = World::new(&MISSION.into(), &pilot(100), &active.registry).unwrap();
|
||||
// Real movement avoids incoming aimed fire without killing the objectives.
|
||||
for tick in 0..1050 {
|
||||
let x = if (tick / 40) % 2 == 0 { 32 } else { 128 };
|
||||
world
|
||||
.step(
|
||||
Input {
|
||||
absolute: Some([x, 140]),
|
||||
..Input::default()
|
||||
},
|
||||
&active.registry,
|
||||
&mut active.scripts,
|
||||
)
|
||||
.unwrap();
|
||||
if world.outcome != Outcome::Playing {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(world.outcome, Outcome::Playing);
|
||||
let turrets: Vec<_> = world
|
||||
.enemies
|
||||
.iter()
|
||||
.filter(|e| e.definition.0 == "iron_coast_turret:unit" && e.health > 0)
|
||||
.collect();
|
||||
assert_eq!(turrets.len(), 4);
|
||||
assert!(turrets.iter().all(|e| (0..=72).contains(&e.y)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_inventory_is_unchanged_and_owned_cannon_survives_failure() {
|
||||
let mut active = active();
|
||||
let mut full = pilot(100);
|
||||
for id in active.registry.items.keys() {
|
||||
if full.inventory_slots() == 20 {
|
||||
break;
|
||||
}
|
||||
if id.0 != "pulse_cannon:equipment" && id.0 != "base:super_shield" {
|
||||
full.inventory.entry(id.clone()).or_insert(1);
|
||||
}
|
||||
}
|
||||
assert_eq!(full.inventory_slots(), 20);
|
||||
let before = full.inventory.clone();
|
||||
let mut world = World::new(&MISSION.into(), &full, &active.registry).unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(world.player.inventory, before);
|
||||
assert_eq!(world.objectives["iron_coast_mission_4:trial_issued"], 0);
|
||||
let mut owned = pilot(100);
|
||||
owned.inventory.insert("pulse_cannon:equipment".into(), 1);
|
||||
let mut world = World::new(&MISSION.into(), &owned, &active.registry).unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
world
|
||||
.apply_commands(
|
||||
&[Command::Damage {
|
||||
entity: 0,
|
||||
amount: 100_000,
|
||||
}],
|
||||
&active.registry,
|
||||
)
|
||||
.unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(world.outcome, Outcome::Failed);
|
||||
assert_eq!(
|
||||
world.player.inventory[&ContentId::from("pulse_cannon:equipment")],
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_loan_is_returned_and_retry_resets_mission_state() {
|
||||
let mut active = active();
|
||||
for _ in 0..2 {
|
||||
let mut world = World::new(&MISSION.into(), &pilot(100), &active.registry).unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
world.player.inventory[&ContentId::from("pulse_cannon:equipment")],
|
||||
1
|
||||
);
|
||||
world
|
||||
.apply_commands(
|
||||
&[Command::Damage {
|
||||
entity: 0,
|
||||
amount: 100_000,
|
||||
}],
|
||||
&active.registry,
|
||||
)
|
||||
.unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(world.outcome, Outcome::Failed);
|
||||
assert!(
|
||||
!world
|
||||
.player
|
||||
.inventory
|
||||
.contains_key(&ContentId::from("pulse_cannon:equipment"))
|
||||
);
|
||||
assert_eq!(world.objectives["iron_coast_mission_4:debrief"], 0);
|
||||
assert_eq!(
|
||||
active.scripts.states["iron_coast_mission_4/scripts/mission.rhai"]["loaned"],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boss_alone_cannot_win_and_reward_is_paid_once() {
|
||||
let mut active = active();
|
||||
let mut world = World::new(&MISSION.into(), &pilot(100), &active.registry).unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
world
|
||||
.objectives
|
||||
.insert("iron_coast_boss_4:defeated".into(), 1);
|
||||
world
|
||||
.objectives
|
||||
.insert("iron_coast_turret:defeated".into(), 3);
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(world.outcome, Outcome::Playing);
|
||||
world
|
||||
.objectives
|
||||
.insert("iron_coast_turret:defeated".into(), 4);
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(world.outcome, Outcome::Complete);
|
||||
assert_eq!(world.player.credits, 12000);
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert_eq!(world.player.credits, 12000);
|
||||
assert_eq!(world.objectives["iron_coast_mission_4:debrief"], 1);
|
||||
}
|
||||
Reference in new issue
Block a user