diff --git a/.github/workflows/core-tip.yml b/.github/workflows/core-tip.yml index 5b5409c9..6f515138 100644 --- a/.github/workflows/core-tip.yml +++ b/.github/workflows/core-tip.yml @@ -46,7 +46,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false @@ -85,7 +85,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false @@ -151,7 +151,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target @@ -704,7 +704,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target @@ -926,7 +926,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target @@ -1125,7 +1125,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - name: Install the Rust toolchain and Wasm target diff --git a/.github/workflows/playerbots-capacity.yml b/.github/workflows/playerbots-capacity.yml index 27493712..de3ad19d 100644 --- a/.github/workflows/playerbots-capacity.yml +++ b/.github/workflows/playerbots-capacity.yml @@ -42,7 +42,7 @@ jobs: with: repository: LyraCoreProject/LyraCore # Core supplies the shared private integration fixtures. - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/playerbots-class-behavior.yml b/.github/workflows/playerbots-class-behavior.yml index 1fac9068..dcf2fcb2 100644 --- a/.github/workflows/playerbots-class-behavior.yml +++ b/.github/workflows/playerbots-class-behavior.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - name: Install pinned Rust and SpacetimeDB diff --git a/.github/workflows/playerbots-target-claims.yml b/.github/workflows/playerbots-target-claims.yml index d2a6ebb2..99348669 100644 --- a/.github/workflows/playerbots-target-claims.yml +++ b/.github/workflows/playerbots-target-claims.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: LyraCoreProject/LyraCore - ref: 34f860b8666fc711dc56c3fcbd491203af295546 + ref: aa97e268610e6ac5ffd7d16eecfff678f86ee8fb path: lyracore persist-credentials: false - name: Install pinned Rust and SpacetimeDB diff --git a/dungeons/src/deadmines_verify.rs b/dungeons/src/deadmines_verify.rs index 75ca7393..ae2f08ae 100644 --- a/dungeons/src/deadmines_verify.rs +++ b/dungeons/src/deadmines_verify.rs @@ -1,38 +1,32 @@ +#![cfg(feature = "debug_reducers")] + //! Deadmines verification harness (`debug_reducers` only): stage reducers the durable test //! (`module/tests/deadmines_package.rs`) calls in order over the standalone CLI. Every reducer is //! argument-free — fixture guids exceed the CLI's safe integer range, so each stage resolves its //! own subjects server-side, drives the REAL production path (kill, damage, gameobject use), and //! verifies the durable outcome in the same call. -#[cfg(feature = "debug_reducers")] use spacetimedb::{reducer, ReducerContext, Table}; -#[cfg(feature = "debug_reducers")] -use crate::encounter::{self, DOOR_OPEN_STATE, ENCOUNTER_DONE}; -#[cfg(feature = "debug_reducers")] use super::deadmines::{ DEFIAS_CANNON, ENCOUNTER_CANNON, ENCOUNTER_GILNID, ENCOUNTER_RHAHKZOR, ENCOUNTER_SMITE, ENCOUNTER_SNEED, FACTORY_DOOR, FOUNDRY_DOOR, GILNID, IRON_CLAD_DOOR, MAP_ID, MAST_ROOM_DOOR, MR_SMITE, RHAHKZOR, SMITES_CHEST, SMITES_MIGHTY_HAMMER, SMITES_REAVER, SMITE_FIRST_STAND_PCT, SMITE_SECOND_STAND_PCT, SNEED, SNEEDS_SHREDDER, }; -#[cfg(feature = "debug_reducers")] +use crate::encounter::{self, DOOR_OPEN_STATE, ENCOUNTER_DONE}; use crate::{ game_chat_event, game_creature_spline, game_creature_template, game_encounter_equip, game_gameobject, game_gameobject_template, game_instance, game_item_template, game_world_entity, GameInstance, }; -#[cfg(feature = "debug_reducers")] const FIXTURE_LOW_BAND: u64 = 0x10_0000; -#[cfg(feature = "debug_reducers")] const DEADMINES_INSTANCE: u64 = 936; -#[cfg(feature = "debug_reducers")] const GO_FIXTURE_BAND: u64 = 0xF11D << 48; /// Seed a Deadmines fixture instance: doors, cannon, chest, Smite's weapons, the bosses, and one /// player. Positions come from the classic-db z2815 spawn rows. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_begin(ctx: &ReducerContext) -> Result<(), String> { install_instance(ctx, MAP_ID, DEADMINES_INSTANCE)?; @@ -69,7 +63,6 @@ pub fn debug_deadmines_begin(ctx: &ReducerContext) -> Result<(), String> { } /// Rhahk'Zor's death opens the Factory Door through the production kill path. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_rhahkzor_falls(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, RHAHKZOR)?; @@ -78,7 +71,6 @@ pub fn debug_deadmines_rhahkzor_falls(ctx: &ReducerContext) -> Result<(), String } /// Destroying Sneed's Shredder ejects Sneed; the Mast Room Door stays shut until Sneed dies. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_shredder_ejects_sneed(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, SNEEDS_SHREDDER)?; @@ -101,7 +93,6 @@ pub fn debug_deadmines_shredder_ejects_sneed(ctx: &ReducerContext) -> Result<(), } /// Sneed's death opens the Mast Room Door. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_sneed_falls(ctx: &ReducerContext) -> Result<(), String> { let sneed = live_boss(ctx, SNEED).ok_or_else(|| "Sneed is not alive to kill".to_string())?; @@ -115,7 +106,6 @@ pub fn debug_deadmines_sneed_falls(ctx: &ReducerContext) -> Result<(), String> { } /// Gilnid's death opens the Foundry Door. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_gilnid_falls(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, GILNID)?; @@ -124,7 +114,6 @@ pub fn debug_deadmines_gilnid_falls(ctx: &ReducerContext) -> Result<(), String> } /// Damage Smite through his 66% threshold: yell, chest run, dual Reavers. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_smite_improvises(ctx: &ReducerContext) -> Result<(), String> { damage_smite_to_pct(ctx, 60)?; @@ -134,7 +123,6 @@ pub fn debug_deadmines_smite_improvises(ctx: &ReducerContext) -> Result<(), Stri } /// Damage Smite through his 33% threshold: yell, second chest run, the two-hand hammer. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_smite_gets_angry(ctx: &ReducerContext) -> Result<(), String> { damage_smite_to_pct(ctx, 30)?; @@ -143,7 +131,6 @@ pub fn debug_deadmines_smite_gets_angry(ctx: &ReducerContext) -> Result<(), Stri } /// Firing the Defias Cannon breaches the Iron Clad Door through the production use path. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_cannon_breaches(ctx: &ReducerContext) -> Result<(), String> { let player = fixture_guid(0, 44); @@ -155,7 +142,6 @@ pub fn debug_deadmines_cannon_breaches(ctx: &ReducerContext) -> Result<(), Strin } /// Smite's death closes his encounter. -#[cfg(feature = "debug_reducers")] #[reducer] pub fn debug_deadmines_smite_falls(ctx: &ReducerContext) -> Result<(), String> { kill_fixture_boss(ctx, MR_SMITE)?; @@ -166,7 +152,6 @@ pub fn debug_deadmines_smite_falls(ctx: &ReducerContext) -> Result<(), String> { // Fixture plumbing // ------------------------------------------------------------------------------------------- -#[cfg(feature = "debug_reducers")] fn seed_gameobject( ctx: &ReducerContext, entry: u32, @@ -222,7 +207,6 @@ fn seed_gameobject( /// Clone the seeded starter sword into one of Smite's swap weapons — `equip_swap` only reads the /// display id, so everything else may stay the starter's. -#[cfg(feature = "debug_reducers")] fn seed_smite_weapon( ctx: &ReducerContext, entry: u32, @@ -245,7 +229,6 @@ fn seed_smite_weapon( } /// Clone a seeded creature template into Sneed's so `spawn_wave` can eject him. -#[cfg(feature = "debug_reducers")] fn seed_sneed_template(ctx: &ReducerContext) -> Result<(), String> { let templates = ctx.db.game_creature_template(); if templates.entry().find(SNEED).is_some() { @@ -262,7 +245,6 @@ fn seed_sneed_template(ctx: &ReducerContext) -> Result<(), String> { Ok(()) } -#[cfg(feature = "debug_reducers")] fn kill_fixture_boss(ctx: &ReducerContext, entry: u32) -> Result<(), String> { let boss = live_boss(ctx, entry).ok_or_else(|| format!("boss {entry} is not alive"))?; let player = fixture_guid(0, 44); @@ -272,7 +254,6 @@ fn kill_fixture_boss(ctx: &ReducerContext, entry: u32) -> Result<(), String> { ) } -#[cfg(feature = "debug_reducers")] fn live_boss(ctx: &ReducerContext, entry: u32) -> Option { ctx.db .game_world_entity() @@ -281,16 +262,14 @@ fn live_boss(ctx: &ReducerContext, entry: u32) -> Option { .find(|e| e.instance_id == DEADMINES_INSTANCE && e.entry == entry && !e.dead) } -#[cfg(feature = "debug_reducers")] fn damage_smite_to_pct(ctx: &ReducerContext, target_pct: u32) -> Result<(), String> { let smite = live_boss(ctx, MR_SMITE).ok_or_else(|| "Mr. Smite is not alive".to_string())?; let target_health = smite.max_health * target_pct / 100; let amount = smite.health.saturating_sub(target_health); require(amount > 0, "Smite is already below the target health")?; - crate::debug::debug_apply_damage(ctx, smite.guid, amount, fixture_guid(0, 44)) // package-api: exempt no surface path damages a boss + crate::package_fixture::apply_damage(ctx, smite.guid, amount, fixture_guid(0, 44)) } -#[cfg(feature = "debug_reducers")] fn door_state(ctx: &ReducerContext, entry: u32) -> Result { ctx.db .game_gameobject() @@ -300,12 +279,10 @@ fn door_state(ctx: &ReducerContext, entry: u32) -> Result { .ok_or_else(|| format!("fixture gameobject {entry} disappeared")) } -#[cfg(feature = "debug_reducers")] fn require_door_open(ctx: &ReducerContext, entry: u32, error: &str) -> Result<(), String> { require(door_state(ctx, entry)? == DOOR_OPEN_STATE, error) } -#[cfg(feature = "debug_reducers")] fn require_done(ctx: &ReducerContext, encounter_id: u32, error: &str) -> Result<(), String> { require( encounter::get_encounter_state(ctx, DEADMINES_INSTANCE, encounter_id) == ENCOUNTER_DONE, @@ -313,7 +290,6 @@ fn require_done(ctx: &ReducerContext, encounter_id: u32, error: &str) -> Result< ) } -#[cfg(feature = "debug_reducers")] fn require_yell_containing(ctx: &ReducerContext, fragment: &str) -> Result<(), String> { let smite_guid = fixture_guid(MR_SMITE, 43); require( @@ -325,7 +301,6 @@ fn require_yell_containing(ctx: &ReducerContext, fragment: &str) -> Result<(), S ) } -#[cfg(feature = "debug_reducers")] fn require_smite_at_chest(ctx: &ReducerContext) -> Result<(), String> { let smite_guid = fixture_guid(MR_SMITE, 43); let smite = ctx @@ -350,7 +325,6 @@ fn require_smite_at_chest(ctx: &ReducerContext) -> Result<(), String> { ) } -#[cfg(feature = "debug_reducers")] fn require_smite_equip( ctx: &ReducerContext, main_hand_item: u32, @@ -380,7 +354,6 @@ fn require_smite_equip( ) } -#[cfg(feature = "debug_reducers")] fn install_instance(ctx: &ReducerContext, map_id: u32, instance_id: u64) -> Result<(), String> { let instances = ctx.db.game_instance(); match instances.instance_id().find(instance_id) { @@ -403,7 +376,6 @@ fn install_instance(ctx: &ReducerContext, map_id: u32, instance_id: u64) -> Resu } } -#[cfg(feature = "debug_reducers")] fn spawn_source( ctx: &ReducerContext, entry: u32, @@ -433,7 +405,6 @@ fn spawn_source( Ok(guid) } -#[cfg(feature = "debug_reducers")] fn spawn_fixture_player( ctx: &ReducerContext, map_id: u32, @@ -462,7 +433,6 @@ fn spawn_fixture_player( Ok(guid) } -#[cfg(feature = "debug_reducers")] fn set_fixture_position( ctx: &ReducerContext, guid: u64, @@ -486,12 +456,10 @@ fn set_fixture_position( Ok(()) } -#[cfg(feature = "debug_reducers")] fn fixture_guid(entry: u32, sequence: u64) -> u64 { encounter::wave_guid(entry, FIXTURE_LOW_BAND | sequence) } -#[cfg(feature = "debug_reducers")] fn require(condition: bool, error: &str) -> Result<(), String> { if condition { Ok(()) diff --git a/playerbots/src/acceptance_fixture.rs b/playerbots/src/acceptance_fixture.rs index 43c4d62c..955264a7 100644 --- a/playerbots/src/acceptance_fixture.rs +++ b/playerbots/src/acceptance_fixture.rs @@ -545,8 +545,9 @@ fn apply_incoming_fault( row.incoming_purpose = Some(quest.purpose); row.incoming_destination = Some(quest.destination); let amount = before.health.saturating_sub(1).min(25); - let accepted = crate::debug::debug_apply_damage(ctx, row.character_guid, amount, attacker_guid) // package-api: exempt private acceptance fixture applies real Core damage - .is_ok(); + let accepted = + crate::package_fixture::apply_damage(ctx, row.character_guid, amount, attacker_guid) + .is_ok(); let Some(after) = ctx.db.game_world_entity().guid().find(row.character_guid) else { return; }; diff --git a/playerbots/src/companion_acceptance_fixture.rs b/playerbots/src/companion_acceptance_fixture.rs index 58791aa0..9f84be6e 100644 --- a/playerbots/src/companion_acceptance_fixture.rs +++ b/playerbots/src/companion_acceptance_fixture.rs @@ -8,7 +8,7 @@ use super::{pkg_playerbots_bot, pkg_playerbots_provisioning, Controller}; use crate::nav::game_nav_chunk; -use crate::threat::top_threat_target; // package-api: exempt private observers record authoritative Core threat ordering +use crate::package_fixture::top_threat_target; use crate::{ game_area_trigger, game_areatrigger_teleport, game_aura, game_character, game_character_quest, game_character_shard, game_creature_move_schedule, game_creature_quest, game_creature_spawn, diff --git a/playerbots/src/fixture.rs b/playerbots/src/fixture.rs index 92bba463..04e677ff 100644 --- a/playerbots/src/fixture.rs +++ b/playerbots/src/fixture.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "debug_reducers")] + //! Deterministic staging for private, per-test durable databases. //! Staging replaces shared rotation configuration and is not safe in a shared World Shard. @@ -41,17 +43,7 @@ pub fn playerbots_fixture_class_stage( fighting: bool, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - use crate::import_meta::game_import_meta; // package-api: exempt private fixture refuses imported content - // Family is the primary key, so at most one row can be the built-in weather seed. - if ctx - .db - .game_import_meta() - .iter() - .take(2) - .any(|row| row.family != "weather_seed") - { - return Err("class fixture requires a private, unimported Shard".into()); - } + crate::package_fixture::require_no_imported_content(ctx)?; playerbots_fixture_prepare(ctx)?; playerbots_fixture_runner_stage(ctx, guid, false)?; let mut strike = ctx @@ -500,9 +492,7 @@ pub fn playerbots_fixture_orders_remove_member_body( { return Err("order fixture Character is outside the private role party".to_string()); } - let entity = crate::helpers::live_entity(ctx, character_guid)?; - crate::world::remove_live_character(ctx, entity); // package-api: exempt private fixture models logout - Ok(()) + crate::package_fixture::remove_live_character(ctx, character_guid) } /// Change only one private role-fixture enemy to exercise exact-target death and partition Gates. @@ -1759,15 +1749,7 @@ pub fn playerbots_fixture_companion_client_cast( caster_guid: u64, target_guid: u64, ) -> Result<(), String> { - crate::gw::gw_cast_at( // package-api: exempt fixture proves client and bot cast Gate parity - ctx, - crate::SessionActor { - guid: caster_guid, - ownership: None, - }, - HEAL, - target_guid, - ) + crate::package_fixture::client_cast(ctx, caster_guid, HEAL, target_guid) } #[reducer] diff --git a/playerbots/src/fixture/target_claims.rs b/playerbots/src/fixture/target_claims.rs index cefaf2b2..93c8c79e 100644 --- a/playerbots/src/fixture/target_claims.rs +++ b/playerbots/src/fixture/target_claims.rs @@ -1,10 +1,11 @@ +#![cfg(feature = "debug_reducers")] + //! Target selection through the ordinary Runner on a fresh, private Shard. use super::super::recovery::Work; use super::super::runner::{Controller, RunnerOutcome}; use super::super::target_claims::{Availability, TargetClaims}; use super::*; -use crate::import_meta::game_import_meta; // package-api: exempt private fixture refuses imported content use std::collections::BTreeSet; const ENTRY: u32 = 5_098_095; @@ -55,14 +56,8 @@ pub fn playerbots_fixture_solo_target_claim( case: String, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - if ctx - .db - .game_import_meta() - .iter() - .take(2) - .any(|row| row.family != "weather_seed") - || ctx.db.pkg_playerbots_bot().iter().next().is_some() - { + crate::package_fixture::require_no_imported_content(ctx)?; + if ctx.db.pkg_playerbots_bot().iter().next().is_some() { return Err("target claim fixture requires a fresh, private Shard".into()); } let population = match case.as_str() { diff --git a/playerbots/src/quest_catalog_fixture.rs b/playerbots/src/quest_catalog_fixture.rs index 272eb136..5c5f90ab 100644 --- a/playerbots/src/quest_catalog_fixture.rs +++ b/playerbots/src/quest_catalog_fixture.rs @@ -10,7 +10,6 @@ use super::quest_catalog::{ ObjectiveExecutor, PlayerbotsCatalogObjective, PlayerbotsCatalogQuest, CATALOG_REVISION, CATALOG_WALK_LIMIT, }; -use crate::import_meta::game_import_meta; // package-api: exempt operator fixture refuses imported content before staging use crate::{ game_character_quest, game_corpse_loot, game_creature_loot, game_creature_quest, game_creature_spawn, game_creature_spline, game_creature_template, game_faction_template, @@ -216,18 +215,8 @@ enum FixtureStage { Existing, } -fn reject_imported_content(ctx: &ReducerContext) -> Result<(), String> { - if let Some(import) = ctx.db.game_import_meta().iter().next() { - return Err(format!( - "quest fixture refuses non-empty import catalogue ({})", - import.family - )); - } - Ok(()) -} - fn require_fixture(ctx: &ReducerContext) -> Result<(), String> { - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; let ownership = ctx .db .pkg_playerbots_quest_fixture_ownership() @@ -671,7 +660,7 @@ pub fn playerbots_quest_loop_fixture_stage_simple_gameobject( character_guid: u64, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; if let Some(existing) = ctx .db .pkg_playerbots_seeded_quest_fixture() @@ -889,7 +878,7 @@ fn relation_exists( } fn stage_gate(ctx: &ReducerContext, character_guid: u64) -> Result { - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; if let Some(ownership) = ctx .db .pkg_playerbots_quest_fixture_ownership() @@ -1806,7 +1795,7 @@ pub fn playerbots_quest_loop_fixture_set_partition( #[reducer] pub fn playerbots_quest_loop_fixture_refresh(ctx: &ReducerContext) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; quest_catalog::refresh_catalog(ctx, "unknown"); if ctx .db @@ -1832,7 +1821,7 @@ pub fn playerbots_quest_loop_fixture_set_simple_gameobject_state( state: u8, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; if state > 1 { return Err("simple GameObject fixture state must be 0 or 1".to_string()); } @@ -1852,7 +1841,7 @@ pub fn playerbots_quest_loop_fixture_add_gameobject_alternative( gameobject_entry: u32, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; match gameobject_entry { SEEDED_USE_GAMEOBJECT => { if ctx @@ -3080,7 +3069,7 @@ pub fn playerbots_recovery_fixture_arm_gameobject_respawn( delay_seconds: u32, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; if !(2..=10).contains(&delay_seconds) { return Err("respawn delay must be 2 through 10 seconds".to_string()); } @@ -3102,7 +3091,7 @@ pub fn playerbots_recovery_fixture_position_simple_gameobject( character_guid: u64, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; let character = crate::helpers::live_entity(ctx, character_guid)?; let rows = ctx.db.game_gameobject(); let mut gameobject = rows @@ -3123,7 +3112,7 @@ pub fn playerbots_recovery_fixture_remove_simple_gameobject( ctx: &ReducerContext, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; if ctx .db .pkg_playerbots_seeded_quest_fixture() @@ -3148,7 +3137,7 @@ pub fn playerbots_recovery_fixture_restore_simple_gameobject( character_guid: u64, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; let retained = ctx .db .pkg_playerbots_quest_objective() @@ -3206,7 +3195,7 @@ pub fn playerbots_recovery_fixture_remove_simple_gameobject_and_objective( ctx: &ReducerContext, ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - reject_imported_content(ctx)?; + crate::package_fixture::require_no_imported_content(ctx)?; if ctx .db .game_quest_objective() diff --git a/playerbots/src/transfer_assist_fixture.rs b/playerbots/src/transfer_assist_fixture.rs index bd5a6eca..b22db25f 100644 --- a/playerbots/src/transfer_assist_fixture.rs +++ b/playerbots/src/transfer_assist_fixture.rs @@ -7,10 +7,10 @@ use super::{ pkg_playerbots_bot, pkg_playerbots_companion_order, pkg_playerbots_runner, Controller, }; use crate::{ - game_character, game_creature_move_schedule, game_group, game_group_member, - game_group_member_partition, game_group_roster_revision, game_world_entity, + game_character, game_group, game_group_member, game_group_member_partition, + game_group_roster_revision, game_world_entity, }; -use spacetimedb::{reducer, table, Identity, ReducerContext, ScheduleAt, Table, TimeDuration}; +use spacetimedb::{reducer, table, Identity, ReducerContext, Table, TimeDuration}; const DESTINATION_MAP: u32 = 36; const DESTINATION_INSTANCE: u64 = 5_098_078; @@ -468,8 +468,8 @@ pub fn playerbots_transfer_assist_source_stage( let retained_before = retained_work(ctx, bot_guid, party.order.group_id)?; let leader_source = (leader_body.x, leader_body.y, leader_body.z); let priest_source = (priest_body.x, priest_body.y, priest_body.z); - crate::world::remove_live_character(ctx, leader_body); // package-api: exempt private fixture declares remote party member - crate::world::remove_live_character(ctx, priest_body); // package-api: exempt private fixture declares remote party member + crate::package_fixture::remove_live_character(ctx, leader_guid)?; + crate::package_fixture::remove_live_character(ctx, priest_guid)?; relocate_character(ctx, leader_guid, LEADER_DESTINATION)?; relocate_character(ctx, priest_guid, PRIEST_DESTINATION)?; let leader_after = relocate_partition(ctx, leader_guid)?; @@ -591,17 +591,5 @@ pub fn playerbots_transfer_assist_destination_stage( super::fixture::playerbots_fixture_freeze(ctx, priest_guid)?; relocate_live_character(ctx, leader_guid, LEADER_DESTINATION)?; relocate_live_character(ctx, priest_guid, PRIEST_DESTINATION)?; - let schedules = ctx.db.game_creature_move_schedule(); // package-api: exempt private fixture declares the next Core movement tick before Transfer - let mut ticks: Vec<_> = schedules.iter().take(2).collect(); - if ticks.len() != 1 || ticks[0].instance_id != u64::MAX { - return Err("Assist destination fixture requires one fresh movement tick".to_string()); - } - let at = ctx - .timestamp - .checked_add(TimeDuration::from_micros(60_000_000)) - .ok_or("Assist destination movement tick timestamp exhausted")?; - let mut tick = ticks.remove(0); - tick.scheduled_at = ScheduleAt::Time(at); - schedules.scheduled_id().update(tick); - Ok(()) + crate::package_fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(60_000_000)) } diff --git a/playerbots/src/transfer_destination_fixture.rs b/playerbots/src/transfer_destination_fixture.rs index 5ae47e3e..910d5a96 100644 --- a/playerbots/src/transfer_destination_fixture.rs +++ b/playerbots/src/transfer_destination_fixture.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "debug_reducers")] + //! Private destination content for retained Quest Transfer cases. use super::decision::{Action, Reason}; @@ -8,8 +10,6 @@ use super::quest_catalog::{ PlayerbotsQuestCatalog, CATALOG_BLUEPRINT_REVISION, CATALOG_NAME, CATALOG_REVISION, }; use super::runner::ObjectiveKind; -use crate::import_meta::game_import_meta; // package-api: exempt private fixture refuses imported content before staging -use crate::nav::game_navigation_revision; // package-api: exempt private fixture requires Navigation Inputs staged by the real import reducer use crate::{ game_character_quest, game_creature_quest, game_creature_spawn, game_creature_template, game_quest_objective, game_quest_template, game_world_entity, @@ -31,31 +31,12 @@ fn destination_guid(entry: u32, ordinal: u64) -> u64 { fn require_private_fixture(ctx: &ReducerContext) -> Result<(), String> { crate::helpers::require_operator(ctx)?; - if ctx.db.game_import_meta().iter().next().is_some() { - return Err("destination catalogue fixture refuses imported content".to_string()); - } - Ok(()) + crate::package_fixture::require_no_imported_content(ctx) } -fn prepare_private_fixture(ctx: &ReducerContext) -> Result<(), String> { - crate::helpers::require_operator(ctx)?; - let imports = ctx.db.game_import_meta(); - let rows: Vec<_> = imports.iter().take(2).collect(); - match rows.as_slice() { - [] => {} - [seed] - if seed.family == "weather_seed" - && seed.source_sha.is_empty() - && seed.file_hash.is_empty() - && seed.row_count == 2 => - { - // A fresh Module stamps this temporary Core seed. Remove only the exact bootstrap - // row, as the other Quest harnesses do, before the unchanged imported-content Gate. - imports.family().delete(seed.family.clone()); - } - _ => return Err("destination catalogue fixture refuses imported content".to_string()), - } - require_private_fixture(ctx) +/// The revision the real Navigation Inputs import advances. It does not depend on the map. +fn imported_navigation_revision(ctx: &ReducerContext) -> Option { + crate::nav::inputs(ctx, DESTINATION_MAP).imported_revision } fn quest_template(entry: u32, prerequisite: u32) -> crate::QuestTemplate { @@ -165,7 +146,7 @@ fn preflight_destination( guids: &[u64], ) -> Result<(), String> { super::quest_catalog::clear_private_fixture_catalog(ctx)?; - if ctx.db.game_navigation_revision().id().find(0).is_none() { + if imported_navigation_revision(ctx).is_none() { return Err("destination Navigation Inputs were not imported".to_string()); } if ctx @@ -252,8 +233,8 @@ pub fn playerbots_transfer_quest_source_stage( ctx: &ReducerContext, character_guid: u64, ) -> Result<(), String> { - prepare_private_fixture(ctx)?; - if ctx.db.game_navigation_revision().id().find(0).is_none() { + require_private_fixture(ctx)?; + if imported_navigation_revision(ctx).is_none() { return Err("source Navigation Inputs were not imported".to_string()); } super::quest_catalog_fixture::playerbots_quest_loop_fixture_stage_named(ctx, character_guid)?; @@ -348,7 +329,7 @@ pub fn playerbots_transfer_destination_catalogue_stage( character_guid: u64, mode: u8, ) -> Result<(), String> { - prepare_private_fixture(ctx)?; + require_private_fixture(ctx)?; if crate::helpers::character_by_guid(ctx, character_guid).is_some() || ctx .db diff --git a/playerbots/src/transfer_fixture.rs b/playerbots/src/transfer_fixture.rs index 27ea2396..b866d432 100644 --- a/playerbots/src/transfer_fixture.rs +++ b/playerbots/src/transfer_fixture.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "debug_reducers")] + //! Private AreaTrigger staging for durable Transfer cases. use crate::{ @@ -321,8 +323,7 @@ fn stage_transfer_fixture( )?; } - let leader = crate::helpers::live_entity(ctx, leader_guid)?; - crate::world::remove_live_character(ctx, leader); // package-api: exempt private fixture models a completed leader Transfer + crate::package_fixture::remove_live_character(ctx, leader_guid)?; let leader_instance = if route.target_map == 36 { DESTINATION_INSTANCE } else { diff --git a/playerbots/src/transfer_lifecycle_fixture.rs b/playerbots/src/transfer_lifecycle_fixture.rs index 2d923821..c386e438 100644 --- a/playerbots/src/transfer_lifecycle_fixture.rs +++ b/playerbots/src/transfer_lifecycle_fixture.rs @@ -1,7 +1,8 @@ +#![cfg(feature = "debug_reducers")] + //! A declared scheduler boundary for retained movement across an owned process restart. -use crate::game_creature_move_schedule; -use spacetimedb::{reducer, ReducerContext, ScheduleAt, Table, TimeDuration}; +use spacetimedb::{reducer, ReducerContext, Table, TimeDuration}; #[reducer] pub fn playerbots_lifecycle_stage_movement( @@ -13,18 +14,7 @@ pub fn playerbots_lifecycle_stage_movement( if ctx.db.pkg_playerbots_bot().count() != 1 { return Err("movement restart fixture requires one staged bot".to_string()); } - let schedules = ctx.db.game_creature_move_schedule(); // package-api: exempt private fixture declares the next ordinary Core tick before movement starts - let mut rows: Vec<_> = schedules.iter().take(2).collect(); - if rows.len() != 1 || rows[0].instance_id != u64::MAX { - return Err("movement restart fixture requires one fresh global tick".to_string()); - } - let at = ctx - .timestamp - .checked_add(TimeDuration::from_micros(30_000_000)) - .ok_or("movement restart fixture timestamp exhausted")?; - let mut schedule = rows.remove(0); - schedule.scheduled_at = ScheduleAt::Time(at); - schedules.scheduled_id().update(schedule); + crate::package_fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(30_000_000))?; super::fixture::playerbots_fixture_runner_stage(ctx, character_guid, false)?; super::fixture::playerbots_fixture_runner_select_cohort(ctx, character_guid)?; super::fixture::playerbots_fixture_runner_pass_once(ctx, character_guid) diff --git a/playerbots/src/transfer_orders_fixture.rs b/playerbots/src/transfer_orders_fixture.rs index d423d7c7..bbdc81c2 100644 --- a/playerbots/src/transfer_orders_fixture.rs +++ b/playerbots/src/transfer_orders_fixture.rs @@ -1,12 +1,13 @@ +#![cfg(feature = "debug_reducers")] + //! Private party inputs for order decisions around the audited Deadmines entrance. use super::pkg_playerbots_bot; -use crate::game_creature_move_schedule; use crate::{ game_area_trigger, game_areatrigger_teleport, game_character, game_group, game_group_member_partition, game_group_roster_revision, game_world_entity, }; -use spacetimedb::{reducer, ReducerContext, ScheduleAt, Table, TimeDuration}; +use spacetimedb::{reducer, ReducerContext, Table, TimeDuration}; const GROUP: u64 = 5_098_000; const INSTANCE: u64 = 5_098_078; @@ -187,19 +188,7 @@ pub fn playerbots_transfer_orders_stage( name: "Deadmines - Entering".to_string(), }); // Keep the first real Target leg pending while the caller records and changes partition facts. - let schedules = ctx.db.game_creature_move_schedule(); // package-api: exempt private fixture declares the Core movement tick before orders begin - let mut ticks: Vec<_> = schedules.iter().take(2).collect(); - if ticks.len() != 1 || ticks[0].instance_id != u64::MAX { - return Err("order Transfer fixture requires one fresh movement tick".to_string()); - } - let at = ctx - .timestamp - .checked_add(TimeDuration::from_micros(60_000_000)) - .ok_or("fixture movement tick timestamp exhausted")?; - let mut tick = ticks.remove(0); - tick.scheduled_at = ScheduleAt::Time(at); - schedules.scheduled_id().update(tick); - Ok(()) + crate::package_fixture::declare_next_movement_tick(ctx, TimeDuration::from_micros(60_000_000)) } /// Supply a later Known partition through the normal mirror operation. The Gateway process @@ -244,8 +233,7 @@ pub fn playerbots_transfer_orders_remote( .into_iter() .flatten() { - let body = crate::helpers::live_entity(ctx, guid)?; - crate::world::remove_live_character(ctx, body); // package-api: exempt private fixture declares remote party member + crate::package_fixture::remove_live_character(ctx, guid)?; let mut character = crate::helpers::character_by_guid(ctx, guid).ok_or("fixture Character missing")?; character.map_id = 36;