From b4f498a4635cffac707cee62b9797dfb3c62a666 Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Thu, 3 Sep 2026 22:36:16 +0200 Subject: [PATCH 1/7] style: apply rustfmt across the workspace --- crates/lyracore-shared/src/spatial.rs | 4 +- gateway/src/codec/breath.rs | 10 +- gateway/src/codec/gameobject.rs | 22 +- gateway/src/codec/mod.rs | 10 +- gateway/src/codec/movement.rs | 9 +- gateway/src/codec/npc.rs | 2 +- gateway/src/codec/tests.rs | 40 +- gateway/src/config.rs | 1 - gateway/src/stdb/account_sessions.rs | 1 - gateway/src/stdb/aoi.rs | 6 +- gateway/src/stdb/mod.rs | 2 +- gateway/src/stdb/reads/items.rs | 1 - gateway/src/stdb/reads/talent_reputation.rs | 1 - gateway/src/stdb/subscriptions.rs | 541 +++++++++++++------- gateway/src/stdb/world_index.rs | 54 +- gateway/src/stdb/world_view.rs | 100 +++- gateway/src/world/handlers/cast/mod.rs | 17 +- gateway/src/world/handlers/cast/ranged.rs | 15 +- gateway/src/world/handlers/combat.rs | 3 +- gateway/src/world/handlers/mod.rs | 13 +- gateway/src/world/handlers/quest.rs | 23 +- gateway/src/world/handlers/vendor.rs | 57 ++- gateway/src/world/mail_tests.rs | 1 - gateway/src/world/mod.rs | 21 +- gateway/src/world/social.rs | 4 +- gateway/src/world/tests.rs | 29 +- gateway/src/world/trade_tests.rs | 81 +-- gateway/src/world/whisper.rs | 7 +- importer/src/go_model.rs | 25 +- importer/src/vmap.rs | 13 +- module/src/action_bar.rs | 4 +- module/src/auction.rs | 5 +- module/src/breath_relay.rs | 25 +- module/src/bridge.rs | 4 +- module/src/combat/folds.rs | 6 +- module/src/creatures/tick/mod.rs | 4 +- module/src/encounter.rs | 5 +- module/src/gc.rs | 6 +- module/src/go_model.rs | 29 +- module/src/group.rs | 51 +- module/src/gw.rs | 73 ++- module/src/helpers.rs | 4 +- module/src/items/economy.rs | 8 +- module/src/lib.rs | 10 +- module/src/load.rs | 5 +- module/src/loot/tag.rs | 15 +- module/src/motion.rs | 5 +- module/src/nav.rs | 17 +- module/src/professions.rs | 2 +- module/src/seed.rs | 26 +- module/src/seed/fixtures.rs | 14 +- module/src/spell/math.rs | 3 +- module/src/spell/tables.rs | 4 +- module/src/spell/tests.rs | 5 +- module/src/talent.rs | 4 +- module/src/transfer/harness.rs | 5 +- module/src/tripwires.rs | 20 +- module/src/xp.rs | 4 +- module/tests/typed_taxi_contracts.rs | 8 +- 59 files changed, 986 insertions(+), 498 deletions(-) diff --git a/crates/lyracore-shared/src/spatial.rs b/crates/lyracore-shared/src/spatial.rs index e40cca967..2b9673857 100644 --- a/crates/lyracore-shared/src/spatial.rs +++ b/crates/lyracore-shared/src/spatial.rs @@ -402,7 +402,9 @@ mod tests { let side = (2 * BOX_HALF_SPAN + 1) as usize; assert_eq!(got.len(), side * side); assert_eq!( - got.iter().copied().collect::>() + got.iter() + .copied() + .collect::>() .len(), got.len(), "a duplicate id inside one box would double-deliver a cell" diff --git a/gateway/src/codec/breath.rs b/gateway/src/codec/breath.rs index e85bddec9..c6bff7a23 100644 --- a/gateway/src/codec/breath.rs +++ b/gateway/src/codec/breath.rs @@ -1,10 +1,10 @@ //! Breath mirror-timer and drowning combat-log packets. +use wow_world_messages::vanilla::opcodes::ServerOpcodeMessage; use wow_world_messages::vanilla::{ - EnvironmentalDamageType, SMSG_ENVIRONMENTAL_DAMAGE_LOG, SMSG_START_MIRROR_TIMER, - SMSG_STOP_MIRROR_TIMER, TimerType, + EnvironmentalDamageType, TimerType, SMSG_ENVIRONMENTAL_DAMAGE_LOG, SMSG_START_MIRROR_TIMER, + SMSG_STOP_MIRROR_TIMER, }; -use wow_world_messages::vanilla::opcodes::ServerOpcodeMessage; /// The only timer this relay currently mirrors: underwater breath. const BREATH_TIMER: TimerType = TimerType::Breath; @@ -25,7 +25,9 @@ pub fn build_breath_timer_start(time_remaining_ms: u32, duration_ms: u32) -> Ser /// Client-side countdown stop on the surface edge. pub fn build_breath_timer_stop() -> ServerOpcodeMessage { - ServerOpcodeMessage::SMSG_STOP_MIRROR_TIMER(SMSG_STOP_MIRROR_TIMER { timer: BREATH_TIMER }) + ServerOpcodeMessage::SMSG_STOP_MIRROR_TIMER(SMSG_STOP_MIRROR_TIMER { + timer: BREATH_TIMER, + }) } /// Combat-log line for exactly the health amount the server's drowning resolver removed. diff --git a/gateway/src/codec/gameobject.rs b/gateway/src/codec/gameobject.rs index ca8186119..4e7df2668 100644 --- a/gateway/src/codec/gameobject.rs +++ b/gateway/src/codec/gameobject.rs @@ -107,13 +107,16 @@ pub fn build_gameobject_create_object(go: &GameObjectView) -> SMSG_UPDATE_OBJECT /// matching vanilla's Z-axis-only yaw convention — rot0/rot1 stay 0, i.e. no terrain pitch/roll, which /// is the best a bare `orientation` float can express) instead. pub fn build_gameobject_rotation_values(go: &GameObjectView) -> (u16, Vec) { - let (rot0, rot1, rot2, rot3) = - if go.rotation_0 == 0.0 && go.rotation_1 == 0.0 && go.rotation_2 == 0.0 && go.rotation_3 == 0.0 { - let half = go.orientation * 0.5; - (0.0, 0.0, half.sin(), half.cos()) - } else { - (go.rotation_0, go.rotation_1, go.rotation_2, go.rotation_3) - }; + let (rot0, rot1, rot2, rot3) = if go.rotation_0 == 0.0 + && go.rotation_1 == 0.0 + && go.rotation_2 == 0.0 + && go.rotation_3 == 0.0 + { + let half = go.orientation * 0.5; + (0.0, 0.0, half.sin(), half.cos()) + } else { + (go.rotation_0, go.rotation_1, go.rotation_2, go.rotation_3) + }; let mut mask = update_mask::UpdateMaskValues::new(); mask.set_f32(update_mask::idx::GAMEOBJECT_ROTATION, rot0); mask.set_f32(update_mask::idx::GAMEOBJECT_ROTATION + 1, rot1); @@ -282,7 +285,10 @@ mod tests { let block_count = body[mask_start] as usize; let values_start = mask_start + 1 + block_count * 4; let mut out = [0.0f32; 4]; - for (i, chunk) in body[values_start..values_start + 16].chunks_exact(4).enumerate() { + for (i, chunk) in body[values_start..values_start + 16] + .chunks_exact(4) + .enumerate() + { out[i] = f32::from_le_bytes(chunk.try_into().unwrap()); } out diff --git a/gateway/src/codec/mod.rs b/gateway/src/codec/mod.rs index 1d3af4210..ee04ab2b4 100644 --- a/gateway/src/codec/mod.rs +++ b/gateway/src/codec/mod.rs @@ -20,9 +20,9 @@ pub mod social; pub mod update_mask; // Packet-family submodules. -mod char; mod auction; mod breath; +mod char; mod combat; mod corpse; mod duel; @@ -36,8 +36,8 @@ mod movement; mod npc; mod pet; mod quest; -mod trainer; mod taxi; +mod trainer; mod values; mod weather; @@ -50,9 +50,9 @@ pub use social::{ }; // Re-export packet-family surfaces through `crate::codec::`. -pub use char::*; pub use auction::*; pub use breath::*; +pub use char::*; pub use combat::*; pub use corpse::*; pub use duel::*; @@ -66,8 +66,8 @@ pub use movement::*; pub use npc::*; pub use pet::*; pub use quest::*; -pub use trainer::*; pub use taxi::*; +pub use trainer::*; pub use values::*; pub use weather::*; @@ -80,9 +80,9 @@ use lyracore_shared::opcodes::movement as movement_opcodes; use lyracore_shared::packing::unpack4; use wow_world_messages::vanilla::opcodes::{ClientOpcodeMessage, ServerOpcodeMessage}; use wow_world_messages::vanilla::{ - Area, AllowedClass, AllowedRace, + Area, BagFamily, // Item binding: "Binds when picked up/equipped" tooltip line. Bonding, diff --git a/gateway/src/codec/movement.rs b/gateway/src/codec/movement.rs index 51a8f9ada..1c74fbe71 100644 --- a/gateway/src/codec/movement.rs +++ b/gateway/src/codec/movement.rs @@ -306,9 +306,12 @@ pub fn build_taxi_move_raw( duration_ms: u32, spline_id: u32, ) -> Option<(u16, Vec)> { - if points.is_empty() || points.iter().chain([&start]).any(|p| { - !p.x.is_finite() || !p.y.is_finite() || !p.z.is_finite() - }) { + if points.is_empty() + || points + .iter() + .chain([&start]) + .any(|p| !p.x.is_finite() || !p.y.is_finite() || !p.z.is_finite()) + { return None; } let mut body = Vec::with_capacity(34 + points.len() * 12); diff --git a/gateway/src/codec/npc.rs b/gateway/src/codec/npc.rs index 9276fd77f..6fcdd14c7 100644 --- a/gateway/src/codec/npc.rs +++ b/gateway/src/codec/npc.rs @@ -77,7 +77,7 @@ pub fn option_condition_holds(cond_type: u32, quest_taken: bool, quest_rewarded: gossip_condition::QUEST_TAKEN => quest_taken, gossip_condition::QUEST_REWARDED => quest_rewarded, gossip_condition::NEVER => false, // fail-closed placeholder (seasonal events don't exist here) - _ => true, // unrecognized → fail open, never silently hide an option + _ => true, // unrecognized → fail open, never silently hide an option } } diff --git a/gateway/src/codec/tests.rs b/gateway/src/codec/tests.rs index 545571c91..9bccabda0 100644 --- a/gateway/src/codec/tests.rs +++ b/gateway/src/codec/tests.rs @@ -7,7 +7,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; // Only the loot byte-match test needs gtker's typed loot response (the runtime path is raw). use wow_world_messages::vanilla::ServerMessage; use wow_world_messages::vanilla::{ - EnvironmentalDamageType, SMSG_LOOT_RESPONSE_LootMethod, SMSG_LOOT_RESPONSE, TimerType, + EnvironmentalDamageType, SMSG_LOOT_RESPONSE_LootMethod, TimerType, SMSG_LOOT_RESPONSE, }; // Group loot methods: the vote-kind byte constants + wire RollVote enum. use lyracore_shared::loot_roll::vote_kind; @@ -793,7 +793,9 @@ fn breath_mirror_timer_edges_roundtrip_on_the_vanilla_wire() { let mut stop_bytes = Vec::new(); stop.write_unencrypted_server(&mut stop_bytes).unwrap(); match ServerOpcodeMessage::read_unencrypted(&mut stop_bytes.as_slice()).unwrap() { - ServerOpcodeMessage::SMSG_STOP_MIRROR_TIMER(msg) => assert_eq!(msg.timer, TimerType::Breath), + ServerOpcodeMessage::SMSG_STOP_MIRROR_TIMER(msg) => { + assert_eq!(msg.timer, TimerType::Breath) + } other => panic!("expected SMSG_STOP_MIRROR_TIMER, got {other}"), } } @@ -2064,7 +2066,11 @@ fn gossip_message_renders_imported_options_verbatim_with_a_trailing_farewell() { let opts = vec![ opt(0, "Care for a chat?", gossip_option::GOSSIP), opt(1, "Let me see your goods.", gossip_option::VENDOR), - opt(0, "I'd like to stay here a while.", gossip_option::INNKEEPER), + opt( + 0, + "I'd like to stay here a while.", + gossip_option::INNKEEPER, + ), ]; let m = build_gossip_message( 1, @@ -3078,8 +3084,8 @@ fn active_taxi_mount_is_present_in_the_player_create_mask() { other => panic!("expected a Player CreateObject2, got {other:?}"), } - let unmounted = build_create_object(&warrior_entity(), CreateKind::SelfPlayer, &[], &[]) - .unwrap(); + let unmounted = + build_create_object(&warrior_entity(), CreateKind::SelfPlayer, &[], &[]).unwrap(); match &unmounted.objects[0] { Object::CreateObject2 { mask2: UpdateMask::Player(player), @@ -3443,11 +3449,27 @@ fn monster_move_facing_carries_the_angle_and_a_degenerate_single_point_spline() #[test] fn taxi_move_writes_absolute_catmull_rom_points_byte_for_byte() { - let start = Vector3d { x: -9465.0, y: -1300.0, z: 50.0 }; + let start = Vector3d { + x: -9465.0, + y: -1300.0, + z: 50.0, + }; let points = vec![ - Vector3d { x: -9460.0, y: -1298.0, z: 52.0 }, - Vector3d { x: -9450.0, y: -1290.0, z: 54.0 }, - Vector3d { x: -9440.0, y: -1280.0, z: 56.0 }, + Vector3d { + x: -9460.0, + y: -1298.0, + z: 52.0, + }, + Vector3d { + x: -9450.0, + y: -1290.0, + z: 54.0, + }, + Vector3d { + x: -9440.0, + y: -1280.0, + z: 56.0, + }, ]; let (opcode, bytes) = build_taxi_move_raw(0xF00D, start, points.clone(), 12_345, 77).unwrap(); assert_eq!(opcode, 0x00DD); diff --git a/gateway/src/config.rs b/gateway/src/config.rs index be5432f28..1f441f33b 100644 --- a/gateway/src/config.rs +++ b/gateway/src/config.rs @@ -733,7 +733,6 @@ pub fn synthetic_owner_identity(account_id: u64) -> [u8; 32] { id } - /// A crash-diagnosis probe for the SMSG_COMPRESSED_MOVES corruption at crowd scale: /// `LYRACORE_WRITER_TRACE=1` turns on a per-session black-box ring inside `spawn_writer` — the /// last 32 (opcode, declared size, rolling checksum) triples for the exact bytes handed to the diff --git a/gateway/src/stdb/account_sessions.rs b/gateway/src/stdb/account_sessions.rs index 6d35e65e5..83f34aefe 100644 --- a/gateway/src/stdb/account_sessions.rs +++ b/gateway/src/stdb/account_sessions.rs @@ -53,7 +53,6 @@ impl SessionEpochs { } } - #[cfg(test)] mod session_epoch_tests { use super::SessionEpochs; diff --git a/gateway/src/stdb/aoi.rs b/gateway/src/stdb/aoi.rs index 0d0452401..8d0ce1474 100644 --- a/gateway/src/stdb/aoi.rs +++ b/gateway/src/stdb/aoi.rs @@ -93,7 +93,11 @@ mod frozen_sql_oracle { /// ONE point-probe query per (table, cell) — the shape the missing-AOI-index fix landed. Kept alongside the range /// form because a future change has to satisfy BOTH or it has narrowed visibility on one of /// them. - fn cell_queries(map_id: u32, instance_id: u64, cells: impl Iterator) -> Vec { + fn cell_queries( + map_id: u32, + instance_id: u64, + cells: impl Iterator, + ) -> Vec { let cells: Vec = cells.collect(); let mut out = Vec::with_capacity(BOX_SCOPED_TABLES.len() * cells.len()); for table in BOX_SCOPED_TABLES { diff --git a/gateway/src/stdb/mod.rs b/gateway/src/stdb/mod.rs index 35ebee065..c88ee20cf 100644 --- a/gateway/src/stdb/mod.rs +++ b/gateway/src/stdb/mod.rs @@ -24,8 +24,8 @@ mod reads; mod reducers; pub(crate) mod subscriptions; mod views; -mod world_store; // impl WorldStore for Coordinator (replaces the former WorldCoordinatorStore newtype) pub(crate) mod world_index; +mod world_store; // impl WorldStore for Coordinator (replaces the former WorldCoordinatorStore newtype) pub(crate) mod world_view; // shared per-shard spatial, broadcast, private, and owner dispatch pub use connection::Coordinator; diff --git a/gateway/src/stdb/reads/items.rs b/gateway/src/stdb/reads/items.rs index b0e867f23..d86bc50f7 100644 --- a/gateway/src/stdb/reads/items.rs +++ b/gateway/src/stdb/reads/items.rs @@ -355,5 +355,4 @@ mod tests { ); } } - } diff --git a/gateway/src/stdb/reads/talent_reputation.rs b/gateway/src/stdb/reads/talent_reputation.rs index 9a819a148..b6bebef68 100644 --- a/gateway/src/stdb/reads/talent_reputation.rs +++ b/gateway/src/stdb/reads/talent_reputation.rs @@ -263,5 +263,4 @@ mod tests { let sworn = ft(3, 14, 8, 0, 1, 0); assert!(super::faction_template_hostile(&sworn, &player)); } - } diff --git a/gateway/src/stdb/subscriptions.rs b/gateway/src/stdb/subscriptions.rs index 210602884..e07431e43 100644 --- a/gateway/src/stdb/subscriptions.rs +++ b/gateway/src/stdb/subscriptions.rs @@ -25,11 +25,11 @@ use wow_world_messages::vanilla::opcodes::ServerOpcodeMessage; use wow_world_messages::vanilla::{Vector3d, WeatherChangeType}; use super::aoi::ViewerGates; -use super::world_index::{CellKey, EntityLayer}; -use super::world_view::{self, Viewer, WorldView}; use super::bindings::*; use super::connection::Coordinator; use super::views::{corpse_view, entity_view, go_view, hunter_pet_protocol_view}; +use super::world_index::{CellKey, EntityLayer}; +use super::world_view::{self, Viewer, WorldView}; /// RAII guard for one world session's shared-view registration, held by the world connection. /// Dropping the guard makes the session unreachable from every shard-level dispatcher. @@ -342,7 +342,10 @@ fn viewer_relative_dynamic_flags( let Some(tap) = rows.tap.filter(|tap| tap.creature_guid == entity.guid) else { return entity.dynamic_flags; }; - let entitled = match rows.tag_group.filter(|group| group.creature_guid == entity.guid) { + let entitled = match rows + .tag_group + .filter(|group| group.creature_guid == entity.guid) + { Some(group) => { rows.tag_members.iter().any(|member| { member.creature_guid == entity.guid && member.character_guid == viewer_guid @@ -359,12 +362,11 @@ fn viewer_relative_dynamic_flags( } } -fn projected_dynamic_flags( - db: &RemoteTables, - viewer_guid: u64, - entity: &WorldEntity, -) -> u32 { - let tap = db.game_creature_quest_tap().creature_guid().find(&entity.guid); +fn projected_dynamic_flags(db: &RemoteTables, viewer_guid: u64, entity: &WorldEntity) -> u32 { + let tap = db + .game_creature_quest_tap() + .creature_guid() + .find(&entity.guid); let tag_group = db .game_creature_loot_tag_group() .creature_guid() @@ -839,12 +841,7 @@ pub(crate) fn offer_peer_create_for( { // CREATE is already first in `out`. A resident flight must follow it in the same writer // work item so AOI entry can never observe MONSTER_MOVE for an unknown passenger. - append_resident_taxi_after_create( - &mut out, - &viewer.created, - viewer.self_guid, - &spline, - ); + append_resident_taxi_after_create(&mut out, &viewer.created, viewer.self_guid, &spline); } out } @@ -904,10 +901,11 @@ pub(crate) fn relay_entity_update( let guard = coord.0.coord(); projected_dynamic_flags(&guard.conn.db, viewer.self_guid, new) }; - let mut out: Vec = entity_update_to_outbound_with_dynamic_flags(old, new, dynamic_flags) - .into_iter() - .map(Outbound::One) - .collect(); + let mut out: Vec = + entity_update_to_outbound_with_dynamic_flags(old, new, dynamic_flags) + .into_iter() + .map(Outbound::One) + .collect(); if new.guid == viewer.self_guid { if let Some(is_ghost) = ghost_transition(old.player_flags, new.player_flags, GHOST_PLAYER_FLAG) @@ -1242,9 +1240,9 @@ pub(crate) fn cast_event_outbound(self_guid: u64, row: &SpellCastEvent) -> Vec> 48, 0xF130 | 0xF140); if is_creature || row.caster_guid == self_guid { let m = codec::build_spell_failure(row.caster_guid, row.spell_id); - out.push(Outbound::One( - ServerOpcodeMessage::SMSG_SPELL_FAILURE(Box::new(m)), - )); + out.push(Outbound::One(ServerOpcodeMessage::SMSG_SPELL_FAILURE( + Box::new(m), + ))); } // A row that names a concrete reason (an on-next-swing strike that could not pay its cost at // the swing) follows the teardown with the failed cast result, in vanilla's order @@ -1299,13 +1297,8 @@ pub(crate) fn cast_event_outbound(self_guid: u64, row: &SpellCastEvent) -> Vec 0 { // Cast-START (a timed spell): SMSG_SPELL_START with the cast-bar duration so observers // see the bar FILL. The GO/COOLDOWN follow on the cast-GO COMPLETION event. - let start = codec::build_spell_start( - row.caster_guid, - row.spell_id, - row.cast_time_ms, - 0, - None, - ); + let start = + codec::build_spell_start(row.caster_guid, row.spell_id, row.cast_time_ms, 0, None); out.push(Outbound::One(ServerOpcodeMessage::SMSG_SPELL_START( Box::new(start), ))); @@ -1334,8 +1327,7 @@ pub(crate) fn cast_event_outbound(self_guid: u64, row: &SpellCastEvent) -> Vec Vec Vec Vec 0 { - let cd = - codec::build_spell_cooldown(row.caster_guid, row.spell_id, row.cooldown_ms); + let cd = codec::build_spell_cooldown(row.caster_guid, row.spell_id, row.cooldown_ms); out.push(Outbound::One(ServerOpcodeMessage::SMSG_SPELL_COOLDOWN( Box::new(cd), ))); @@ -1458,7 +1448,11 @@ pub(crate) fn aura_sync( // both land the right value, exactly like `run_speed_packet`). Returns `None` unless `changed` is a // self armor aura, so ordinary buffs/debuffs don't spam the opcode. Self-scoped: the sheet shows // only your own armor, so no peer relay is needed. -pub(crate) fn armor_packet(coord: &Coordinator, changed: &Aura, self_guid: u64) -> Option { +pub(crate) fn armor_packet( + coord: &Coordinator, + changed: &Aura, + self_guid: u64, +) -> Option { const A_MOD_RESISTANCE: u8 = 0xA1; // taxonomy A_MOD_RESISTANCE const RESIST_ARMOR_MASK: u32 = 0x01; // taxonomy RESIST_ARMOR bit (eff_p0 is a school MASK) if changed.target_guid != self_guid @@ -1492,7 +1486,11 @@ pub(crate) fn armor_packet(coord: &Coordinator, changed: &Aura, self_guid: u64) // `recompute_sheet` actually reacts to (`aura_moves_sheet`'s mirror), so an unrelated buff/debuff // (a DoT, a slow) doesn't spam the opcode — purely a re-push filter, not aura interpretation. Self- // scoped: the sheet shows only your own numbers, so no peer relay is needed. -pub(crate) fn sheet_packet(coord: &Coordinator, changed: &Aura, self_guid: u64) -> Option { +pub(crate) fn sheet_packet( + coord: &Coordinator, + changed: &Aura, + self_guid: u64, +) -> Option { const A_MOD_STAT: u8 = 0xA0; // taxonomy A_MOD_STAT const A_MOD_COMBAT: u8 = 0xA3; // taxonomy A_MOD_COMBAT const COMBAT_ATTACK_POWER: i32 = 0; // taxonomy COMBAT_ATTACK_POWER @@ -1606,7 +1604,8 @@ pub(crate) fn aura_insert_outbound( let mut out = Vec::new(); // A stealth-hidden peer (not in `created`) must get NO per-peer relay: a partial VALUES on // a DESTROYed object is a client crash/desync vector. - let visible = row.target_guid == self_guid || created.lock().unwrap().contains(&row.target_guid); + let visible = + row.target_guid == self_guid || created.lock().unwrap().contains(&row.target_guid); let current = visible.then(|| view.auras.on_target(shard, row.target_guid)); if let Some(current) = ¤t { out.push(aura_sync(current.iter().cloned(), row.target_guid)); @@ -1650,7 +1649,8 @@ pub(crate) fn aura_update_outbound( expires_changed: bool, ) -> Vec { let mut out = Vec::new(); - let visible = row.target_guid == self_guid || created.lock().unwrap().contains(&row.target_guid); + let visible = + row.target_guid == self_guid || created.lock().unwrap().contains(&row.target_guid); let current = visible.then(|| view.auras.on_target(shard, row.target_guid)); if let Some(current) = ¤t { out.push(aura_sync(current.iter().cloned(), row.target_guid)); @@ -1689,7 +1689,8 @@ pub(crate) fn aura_delete_outbound( stealth_count: usize, ) -> Vec { let mut out = Vec::new(); - let visible = row.target_guid == self_guid || created.lock().unwrap().contains(&row.target_guid); + let visible = + row.target_guid == self_guid || created.lock().unwrap().contains(&row.target_guid); if visible { let current = view.auras.on_target(shard, row.target_guid); out.push(aura_sync(current.iter().cloned(), row.target_guid)); @@ -1940,13 +1941,14 @@ pub(crate) fn duel_event_outbound( raw_values(row.initiator_guid, None, Some(1)), raw_values(row.challenged_guid, None, Some(2)), ], - event_kind::OUT_OF_BOUNDS => vec![Outbound::One(ServerOpcodeMessage::SMSG_DUEL_OUTOFBOUNDS)], + event_kind::OUT_OF_BOUNDS => { + vec![Outbound::One(ServerOpcodeMessage::SMSG_DUEL_OUTOFBOUNDS)] + } event_kind::IN_BOUNDS => vec![Outbound::One(ServerOpcodeMessage::SMSG_DUEL_INBOUNDS)], event_kind::COMPLETE => { let mut outbound = vec![Outbound::One(ServerOpcodeMessage::SMSG_DUEL_COMPLETE( SMSG_DUEL_COMPLETE { - ended_without_interruption: row.completion_kind - != completion_kind::INTERRUPTED, + ended_without_interruption: row.completion_kind != completion_kind::INTERRUPTED, }, ))]; outbound.extend(duel_winner_outbound(row)); @@ -1987,13 +1989,13 @@ pub(crate) fn duel_winner_outbound(row: &DuelEvent) -> Vec { }; // `opponent_name` is the first CString on the wire and carries the WINNER: mangos sends // this packet from the loser, announcing its opponent as victor. - vec![Outbound::One(ServerOpcodeMessage::SMSG_DUEL_WINNER(Box::new( - SMSG_DUEL_WINNER { + vec![Outbound::One(ServerOpcodeMessage::SMSG_DUEL_WINNER( + Box::new(SMSG_DUEL_WINNER { reason, opponent_name: row.winner_name.clone(), initiator_name: row.loser_name.clone(), - }, - )))] + }), + ))] } /// Decode an `OFFER_*` payload into the fixed-444-byte `SMSG_TRADE_STATUS_EXTENDED` (#121): @@ -2086,14 +2088,22 @@ pub(crate) fn group_event_outbound( group_kind::LIST => match lyracore_shared::group::decode_roster(&row.payload) { Some((leader, loot_method, loot_threshold, master_looter_guid, members)) => { let members = name_the_roster(coord, members); - Some(ServerOpcodeMessage::SMSG_GROUP_LIST(Box::new(codec::build_group_list( - self_guid, leader, loot_method, loot_threshold, master_looter_guid, &members, - )))) + Some(ServerOpcodeMessage::SMSG_GROUP_LIST(Box::new( + codec::build_group_list( + self_guid, + leader, + loot_method, + loot_threshold, + master_looter_guid, + &members, + ), + ))) } None => { log::warn!( "group LIST relay: unparseable roster payload {:?} (event {})", - row.payload, row.id + row.payload, + row.id ); None } @@ -2112,63 +2122,104 @@ pub(crate) fn group_event_outbound( codec::build_party_chat(row.other_guid, message), ))), None => { - log::warn!("party PARTY_CHAT relay: unparseable payload {:?} (event {})", row.payload, row.id); + log::warn!( + "party PARTY_CHAT relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); None } }, roll_kind::ROLL_START => match lyracore_shared::loot_roll::decode_start(&row.payload) { Some((corpse_guid, slot, item_entry, countdown_ms)) => { - Some(ServerOpcodeMessage::SMSG_LOOT_START_ROLL(Box::new(codec::build_loot_start_roll( - corpse_guid, slot, item_entry, countdown_ms, - )))) + Some(ServerOpcodeMessage::SMSG_LOOT_START_ROLL(Box::new( + codec::build_loot_start_roll(corpse_guid, slot, item_entry, countdown_ms), + ))) } None => { - log::warn!("loot ROLL_START relay: unparseable payload {:?} (event {})", row.payload, row.id); + log::warn!( + "loot ROLL_START relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); None } }, roll_kind::ROLL_VOTE => match lyracore_shared::loot_roll::decode_vote(&row.payload) { - Some((corpse_guid, slot, item_entry, roll_number, vote, auto_pass)) => { - Some(ServerOpcodeMessage::SMSG_LOOT_ROLL(Box::new(codec::build_loot_roll( - corpse_guid, slot, row.other_guid, item_entry, roll_number, vote, auto_pass, - )))) - } + Some((corpse_guid, slot, item_entry, roll_number, vote, auto_pass)) => Some( + ServerOpcodeMessage::SMSG_LOOT_ROLL(Box::new(codec::build_loot_roll( + corpse_guid, + slot, + row.other_guid, + item_entry, + roll_number, + vote, + auto_pass, + ))), + ), None => { - log::warn!("loot ROLL_VOTE relay: unparseable payload {:?} (event {})", row.payload, row.id); + log::warn!( + "loot ROLL_VOTE relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); None } }, roll_kind::ROLL_WON => match lyracore_shared::loot_roll::decode_won(&row.payload) { - Some((corpse_guid, slot, item_entry, winning_roll, winning_vote)) => { - Some(ServerOpcodeMessage::SMSG_LOOT_ROLL_WON(Box::new(codec::build_loot_roll_won( - corpse_guid, slot, item_entry, row.other_guid, winning_roll, winning_vote, - )))) - } + Some((corpse_guid, slot, item_entry, winning_roll, winning_vote)) => Some( + ServerOpcodeMessage::SMSG_LOOT_ROLL_WON(Box::new(codec::build_loot_roll_won( + corpse_guid, + slot, + item_entry, + row.other_guid, + winning_roll, + winning_vote, + ))), + ), None => { - log::warn!("loot ROLL_WON relay: unparseable payload {:?} (event {})", row.payload, row.id); + log::warn!( + "loot ROLL_WON relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); None } }, - roll_kind::MASTER_LIST => match lyracore_shared::loot_roll::decode_master_list(&row.payload) { - Some((_corpse_guid, eligible)) => Some(ServerOpcodeMessage::SMSG_LOOT_MASTER_LIST(Box::new( - codec::build_loot_master_list(&eligible), - ))), - None => { - log::warn!("loot MASTER_LIST relay: unparseable payload {:?} (event {})", row.payload, row.id); - None + roll_kind::MASTER_LIST => { + match lyracore_shared::loot_roll::decode_master_list(&row.payload) { + Some((_corpse_guid, eligible)) => Some(ServerOpcodeMessage::SMSG_LOOT_MASTER_LIST( + Box::new(codec::build_loot_master_list(&eligible)), + )), + None => { + log::warn!( + "loot MASTER_LIST relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); + None + } } - }, + } // A grouped money-loot split's per-recipient share → the SAME // `SMSG_LOOT_MONEY_NOTIFY` the (now-removed) unconditional gateway send used to build, // just per-recipient and carrying the SHARE instead of the total (`amount` here IS the // wire field, matching `codec::build_loot_money_notify`'s single `amount: u32`). - roll_kind::MONEY_SHARE => match lyracore_shared::loot_roll::decode_money_share(&row.payload) { - Some(share) => Some(ServerOpcodeMessage::SMSG_LOOT_MONEY_NOTIFY(codec::build_loot_money_notify(share))), - None => { - log::warn!("loot MONEY_SHARE relay: unparseable payload {:?} (event {})", row.payload, row.id); - None + roll_kind::MONEY_SHARE => { + match lyracore_shared::loot_roll::decode_money_share(&row.payload) { + Some(share) => Some(ServerOpcodeMessage::SMSG_LOOT_MONEY_NOTIFY( + codec::build_loot_money_notify(share), + )), + None => { + log::warn!( + "loot MONEY_SHARE relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); + None + } } - }, + } // Quest sharing: an eligible party member receives the shared quest — // `row.other_guid` is the SHARER (resolved/pushed by `group::push_event`), `row.payload` // is the quest entry. Opens the DETAILS screen with the SHARER as "giver" (the @@ -2181,16 +2232,26 @@ pub(crate) fn group_event_outbound( return vec![Outbound::Raw { opcode, body }]; } Ok(None) => { - log::warn!("quest QUEST_SHARE relay: quest {quest_id} not loaded (event {})", row.id); + log::warn!( + "quest QUEST_SHARE relay: quest {quest_id} not loaded (event {})", + row.id + ); None } Err(e) => { - log::warn!("quest QUEST_SHARE relay: quest_detail lookup failed (event {}): {e}", row.id); + log::warn!( + "quest QUEST_SHARE relay: quest_detail lookup failed (event {}): {e}", + row.id + ); None } }, Err(_) => { - log::warn!("quest QUEST_SHARE relay: unparseable payload {:?} (event {})", row.payload, row.id); + log::warn!( + "quest QUEST_SHARE relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); None } }, @@ -2202,7 +2263,11 @@ pub(crate) fn group_event_outbound( codec::build_quest_push_result(row.other_guid, code), ))), Err(_) => { - log::warn!("quest QUEST_PUSH_RESULT relay: unparseable payload {:?} (event {})", row.payload, row.id); + log::warn!( + "quest QUEST_PUSH_RESULT relay: unparseable payload {:?} (event {})", + row.payload, + row.id + ); None } }, @@ -2269,7 +2334,9 @@ pub(crate) fn impact_event_outbound(row: &SpellImpactEvent) -> Vec { row.resisted, row.absorbed, ); - vec![Outbound::One(ServerOpcodeMessage::SMSG_SPELLNONMELEEDAMAGELOG(Box::new(log)))] + vec![Outbound::One( + ServerOpcodeMessage::SMSG_SPELLNONMELEEDAMAGELOG(Box::new(log)), + )] } /// Nearby chat: the one body both legs run. A player speaker always hears @@ -2412,12 +2479,9 @@ pub(crate) fn emote_event_outbound(coord: &Coordinator, row: &EmoteEvent) -> Vec None }; let mut out = Vec::new(); - if let Some(m) = codec::build_text_emote( - row.sender_guid, - row.text_emote, - row.emote_anim, - target_name, - ) { + if let Some(m) = + codec::build_text_emote(row.sender_guid, row.text_emote, row.emote_anim, target_name) + { out.push(Outbound::One(ServerOpcodeMessage::SMSG_TEXT_EMOTE( Box::new(m), ))); @@ -2526,9 +2590,14 @@ pub(crate) fn creature_leg_outbound( // `FacingAngle` variant — the only 1.12 `SMSG_MONSTER_MOVE` shape that turns a STATIONARY // creature without a spline the client would otherwise have nothing to interpolate from. if row.facing { - return vec![Outbound::One(ServerOpcodeMessage::SMSG_MONSTER_MOVE(Box::new( - codec::build_monster_move_facing(row.guid, start, row.facing_angle, row.spline_id), - )))]; + return vec![Outbound::One(ServerOpcodeMessage::SMSG_MONSTER_MOVE( + Box::new(codec::build_monster_move_facing( + row.guid, + start, + row.facing_angle, + row.spline_id, + )), + ))]; } let dest = Vector3d { x: row.dx, @@ -2586,7 +2655,9 @@ pub(crate) fn taxi_spline_outbound( row.duration_ms, row.spline_id, ) - .map_or_else(Vec::new, |(opcode, body)| vec![Outbound::Raw { opcode, body }]) + .map_or_else(Vec::new, |(opcode, body)| { + vec![Outbound::Raw { opcode, body }] + }) } fn append_resident_taxi_after_create( @@ -2924,15 +2995,15 @@ pub(crate) fn quest_update_feedback( row: &CharacterQuest, objectives: &[(u8, u8, u32, u32)], ) -> Vec { - let mut outbound = codec::kill_progress_add_kills( - row.quest_entry, - &old.counts, - &row.counts, - objectives, - ) - .into_iter() - .map(|packet| Outbound::One(ServerOpcodeMessage::SMSG_QUESTUPDATE_ADD_KILL(Box::new(packet)))) - .collect::>(); + let mut outbound = + codec::kill_progress_add_kills(row.quest_entry, &old.counts, &row.counts, objectives) + .into_iter() + .map(|packet| { + Outbound::One(ServerOpcodeMessage::SMSG_QUESTUPDATE_ADD_KILL(Box::new( + packet, + ))) + }) + .collect::>(); if !old.failed && row.failed { outbound.push(Outbound::One( ServerOpcodeMessage::SMSG_QUESTUPDATE_FAILEDTIMER( @@ -3142,7 +3213,8 @@ pub(crate) fn item_instance_update_outbound( } let moved_into_equipment = old.slot != row.slot && row.slot <= 18; let emptied_equipment = old.slot != row.slot && old.slot <= 18 && old_slot_is_empty; - if moved_into_equipment || emptied_equipment + if moved_into_equipment + || emptied_equipment || row.slot <= 18 && (old.durability == 0) != (row.durability == 0) { append_item_armor_and_sheet(db, self_guid, &mut out); @@ -3415,8 +3487,6 @@ impl Coordinator { view: Some(view), }) } - - } impl Coordinator { @@ -3646,8 +3716,7 @@ mod tests { ..swing_event() }; let out = combat_event_outbound(&created, &row); - let [Outbound::One(ServerOpcodeMessage::SMSG_SPELL_GO(go))] = out.as_slice() - else { + let [Outbound::One(ServerOpcodeMessage::SMSG_SPELL_GO(go))] = out.as_slice() else { panic!("a ranged launch must emit exactly one SPELL_GO"); }; assert_eq!((go.caster.guid(), go.spell), (7, 75)); @@ -3690,7 +3759,12 @@ mod tests { panic!("an impact must emit exactly one damage log"); }; assert_eq!( - (log.attacker.guid(), log.target.guid(), log.spell, log.damage), + ( + log.attacker.guid(), + log.target.guid(), + log.spell, + log.damage + ), (7, 11, 75, 23) ); } @@ -3702,7 +3776,9 @@ mod tests { let out = combat_event_outbound(&created, &row); assert!(matches!( out.as_slice(), - [Outbound::One(ServerOpcodeMessage::SMSG_ATTACKERSTATEUPDATE(_))] + [Outbound::One( + ServerOpcodeMessage::SMSG_ATTACKERSTATEUPDATE(_) + )] )); row.killing_blow = true; @@ -3786,12 +3862,8 @@ mod tests { (equipment.clone(), true, (0, 0, 0)), ] { let out = encounter_equip_outbound(&viewer, &row, cleared); - let (expected_opcode, expected_body) = codec::build_virtual_item_values( - creature_guid, - expected.0, - expected.1, - expected.2, - ); + let (expected_opcode, expected_body) = + codec::build_virtual_item_values(creature_guid, expected.0, expected.1, expected.2); assert!(matches!( out.as_slice(), [Outbound::Raw { opcode, body }] @@ -3937,8 +4009,14 @@ mod tests { }) .collect(); assert_eq!(requests.len(), 1); - assert_eq!(requests[0].initiator, wow_world_messages::Guid::new(row.flag_guid)); - assert_eq!(requests[0].target, wow_world_messages::Guid::new(row.initiator_guid)); + assert_eq!( + requests[0].initiator, + wow_world_messages::Guid::new(row.flag_guid) + ); + assert_eq!( + requests[0].target, + wow_world_messages::Guid::new(row.initiator_guid) + ); } #[test] @@ -4051,38 +4129,87 @@ mod tests { "BeginTrade must carry the initiator guid off the row" ); assert_eq!(status_of(kind::OPEN_WINDOW), SMSG_TRADE_STATUS::OpenWindow); - assert_eq!(status_of(kind::TRADE_CANCELED), SMSG_TRADE_STATUS::TradeCanceled); + assert_eq!( + status_of(kind::TRADE_CANCELED), + SMSG_TRADE_STATUS::TradeCanceled + ); assert_eq!(status_of(kind::BUSY), SMSG_TRADE_STATUS::Busy); assert_eq!(status_of(kind::NO_TARGET), SMSG_TRADE_STATUS::NoTarget); - assert_eq!(status_of(kind::TARGET_TO_FAR), SMSG_TRADE_STATUS::TargetToFar); - assert_eq!(status_of(kind::WRONG_FACTION), SMSG_TRADE_STATUS::WrongFaction); + assert_eq!( + status_of(kind::TARGET_TO_FAR), + SMSG_TRADE_STATUS::TargetToFar + ); + assert_eq!( + status_of(kind::WRONG_FACTION), + SMSG_TRADE_STATUS::WrongFaction + ); assert_eq!(status_of(kind::YOU_DEAD), SMSG_TRADE_STATUS::YouDead); assert_eq!(status_of(kind::TARGET_DEAD), SMSG_TRADE_STATUS::TargetDead); assert_eq!(status_of(kind::IGNORE_YOU), SMSG_TRADE_STATUS::IgnoreYou); - assert_eq!(status_of(kind::TRADE_ACCEPT), SMSG_TRADE_STATUS::TradeAccept); - assert_eq!(status_of(kind::BACK_TO_TRADE), SMSG_TRADE_STATUS::BackToTrade); - assert_eq!(status_of(kind::TRADE_COMPLETE), SMSG_TRADE_STATUS::TradeComplete); + assert_eq!( + status_of(kind::TRADE_ACCEPT), + SMSG_TRADE_STATUS::TradeAccept + ); + assert_eq!( + status_of(kind::BACK_TO_TRADE), + SMSG_TRADE_STATUS::BackToTrade + ); + assert_eq!( + status_of(kind::TRADE_COMPLETE), + SMSG_TRADE_STATUS::TradeComplete + ); // The bag-space refusal closes the window naming WHOSE bags overflowed. let full_self = status_of(kind::INV_FULL_SELF); let full_partner = status_of(kind::INV_FULL_PARTNER); match (&full_self, &full_partner) { ( - SMSG_TRADE_STATUS::CloseWindow { target_error: false, inventory_result: a, .. }, - SMSG_TRADE_STATUS::CloseWindow { target_error: true, inventory_result: b, .. }, + SMSG_TRADE_STATUS::CloseWindow { + target_error: false, + inventory_result: a, + .. + }, + SMSG_TRADE_STATUS::CloseWindow { + target_error: true, + inventory_result: b, + .. + }, ) => { - assert_eq!(*a, wow_world_messages::vanilla::InventoryResult::InventoryFull); - assert_eq!(*b, wow_world_messages::vanilla::InventoryResult::InventoryFull); + assert_eq!( + *a, + wow_world_messages::vanilla::InventoryResult::InventoryFull + ); + assert_eq!( + *b, + wow_world_messages::vanilla::InventoryResult::InventoryFull + ); } other => panic!("expected CloseWindow pair with flipped target_error, got {other:?}"), } // The purse-failure pair mirrors it with NotEnoughMoney. - match (&status_of(kind::GOLD_FAIL_SELF), &status_of(kind::GOLD_FAIL_PARTNER)) { + match ( + &status_of(kind::GOLD_FAIL_SELF), + &status_of(kind::GOLD_FAIL_PARTNER), + ) { ( - SMSG_TRADE_STATUS::CloseWindow { target_error: false, inventory_result: a, .. }, - SMSG_TRADE_STATUS::CloseWindow { target_error: true, inventory_result: b, .. }, + SMSG_TRADE_STATUS::CloseWindow { + target_error: false, + inventory_result: a, + .. + }, + SMSG_TRADE_STATUS::CloseWindow { + target_error: true, + inventory_result: b, + .. + }, ) => { - assert_eq!(*a, wow_world_messages::vanilla::InventoryResult::NotEnoughMoney); - assert_eq!(*b, wow_world_messages::vanilla::InventoryResult::NotEnoughMoney); + assert_eq!( + *a, + wow_world_messages::vanilla::InventoryResult::NotEnoughMoney + ); + assert_eq!( + *b, + wow_world_messages::vanilla::InventoryResult::NotEnoughMoney + ); } other => panic!("expected NotEnoughMoney CloseWindow pair, got {other:?}"), } @@ -4136,9 +4263,15 @@ mod tests { // Wire polarity (mangoszero): the `self_player` BYTE is "1 means traders data, 0 means // own", so your OWN echo carries 0 and the partner's side carries 1. let own = extended(kind::OFFER_SELF); - assert!(!own.self_player, "OFFER_SELF describes your own window: wire byte 0"); + assert!( + !own.self_player, + "OFFER_SELF describes your own window: wire byte 0" + ); let theirs = extended(kind::OFFER_PARTNER); - assert!(theirs.self_player, "OFFER_PARTNER describes the partner's side: wire byte 1"); + assert!( + theirs.self_player, + "OFFER_PARTNER describes the partner's side: wire byte 1" + ); assert_eq!(own.money_in_trade.as_int(), 1_2345); assert_eq!((own.trade_slot_count1, own.trade_slot_count2), (7, 7)); @@ -4406,7 +4539,15 @@ mod tests { let members = [tag_member(7), tag_member(8)]; let current = [current_member(8, 42)]; assert_eq!( - project_flags(8, &entity, Some(&tap), Some(&group), &members, ¤t, &[]), + project_flags( + 8, + &entity, + Some(&tap), + Some(&group), + &members, + ¤t, + &[] + ), entity.dynamic_flags | lyracore_shared::constants::unit_dynamic_flags::TAPPED_BY_PLAYER ); } @@ -4432,7 +4573,15 @@ mod tests { let members = [tag_member(7)]; let current = [current_member(8, 42)]; assert_eq!( - project_flags(8, &entity, Some(&tap), Some(&group), &members, ¤t, &[]), + project_flags( + 8, + &entity, + Some(&tap), + Some(&group), + &members, + ¤t, + &[] + ), entity.dynamic_flags ); } @@ -4448,7 +4597,15 @@ mod tests { let members = [tag_member(7), tag_member(8)]; let current = [current_member(8, 99)]; assert_eq!( - project_flags(8, &entity, Some(&tap), Some(&group), &members, ¤t, &[]), + project_flags( + 8, + &entity, + Some(&tap), + Some(&group), + &members, + ¤t, + &[] + ), entity.dynamic_flags ); } @@ -4479,7 +4636,10 @@ mod tests { corpse_guid: entity.guid, eligible_guid: 7, }]; - assert_eq!(project_flags(8, &entity, None, None, &[], &[], &eligible), 0); + assert_eq!( + project_flags(8, &entity, None, None, &[], &[], &eligible), + 0 + ); } #[test] @@ -4572,8 +4732,8 @@ mod tests { old.dynamic_flags = 0; let mut new = old.clone(); new.dynamic_flags = lyracore_shared::constants::unit_dynamic_flags::TAPPED; - let projected = new.dynamic_flags - | lyracore_shared::constants::unit_dynamic_flags::TAPPED_BY_PLAYER; + let projected = + new.dynamic_flags | lyracore_shared::constants::unit_dynamic_flags::TAPPED_BY_PLAYER; assert_eq!( entity_update_to_outbound_with_dynamic_flags(&old, &new, projected), vec![ServerOpcodeMessage::SMSG_UPDATE_OBJECT(Box::new( @@ -5650,7 +5810,6 @@ mod tests { ); } - /// Tripwire for the realm-core PRIVATE tier (#22 → #483): the cross-shard whisper/group /// twins are armed ONCE per realm-core connection (`arm_realm_private`), gated on realm-core /// being a DISTINCT database. Without the gate a single-database gateway registers a SECOND @@ -5660,7 +5819,10 @@ mod tests { /// coordinator connection), so the wiring is pinned in source, comment-stripped. #[test] fn the_realm_private_tier_is_armed_once_gated_on_a_distinct_database() { - let body = decommented(top_level_fn_body_of("connection.rs", "arm_shared_world_view")); + let body = decommented(top_level_fn_body_of( + "connection.rs", + "arm_shared_world_view", + )); assert!( body.contains("if let Ok(realm) = self.realm_core() {"), "arming no longer resolves the realm-core handle — pointed anywhere else it hears \ @@ -5674,7 +5836,9 @@ mod tests { whisper line twice." ); assert!( - body.contains("super::world_view::arm_realm_private(view.clone(), realm.clone(), world.clone());"), + body.contains( + "super::world_view::arm_realm_private(view.clone(), realm.clone(), world.clone());" + ), "the realm-core private tier is never armed — a cross-shard whisper is written on \ realm-core and delivered to nobody" ); @@ -5711,7 +5875,8 @@ mod tests { let whisper = decommented(top_level_fn_body_of("world_view.rs", "whisper_appeared")); assert!( whisper.contains("session_of_owner(row.recipient_guid)") - && whisper.contains("private_recipient_audience(row.recipient_guid, viewer.self_guid)"), + && whisper + .contains("private_recipient_audience(row.recipient_guid, viewer.self_guid)"), "whisper_appeared is no longer recipient-keyed — on an owner-token read that is a \ privacy leak: every session would receive every player's private whispers" ); @@ -5759,7 +5924,11 @@ mod tests { } } - fn relay_creature_leg(tx: &SessionTx, created: &Arc>>, row: &CreatureSpline) { + fn relay_creature_leg( + tx: &SessionTx, + created: &Arc>>, + row: &CreatureSpline, + ) { if shed_motion_at_depth(tx.depth()) { MOTION_DROPPED.fetch_add(1, std::sync::atomic::Ordering::Relaxed); return; @@ -6077,8 +6246,14 @@ mod tests { fn the_motionstat_line_reports_delivery_dropped_and_fanout_287() { // 1000 callbacks, 900 packets queued, 7 discarded, from 10 submitted movements. let line = motionstat_line(5000, 4500, 21, 1000, 900, 7, 10, 400); - assert!(line.contains("calls=5000"), "cumulative calls missing: {line}"); - assert!(line.contains("sent=4500"), "cumulative sent missing: {line}"); + assert!( + line.contains("calls=5000"), + "cumulative calls missing: {line}" + ); + assert!( + line.contains("sent=4500"), + "cumulative sent missing: {line}" + ); assert!( line.contains("dropped=21"), "cumulative dropped count missing: {line}" @@ -6095,9 +6270,18 @@ mod tests { line.contains("fanout=100.0/move"), "per-movement fan-out missing/wrong: {line}" ); - assert!(line.contains("submitted=400"), "submitted count missing: {line}"); - assert!(!line.contains("completed="), "retired completed count remains: {line}"); - assert!(!line.contains("outstanding="), "retired outstanding count remains: {line}"); + assert!( + line.contains("submitted=400"), + "submitted count missing: {line}" + ); + assert!( + !line.contains("completed="), + "retired completed count remains: {line}" + ); + assert!( + !line.contains("outstanding="), + "retired outstanding count remains: {line}" + ); // An idle window must not divide by zero or print a fake 0 %. let idle = motionstat_line(5000, 4500, 21, 0, 0, 0, 0, 400); @@ -6439,13 +6623,34 @@ mod tests { // `arm_shard`'s own body — what's left to scan for is the (helper, table, label) triple // that proves the callback is still wired, not skipped. for (helper, table, label, what) in [ - ("wire_insert", "game_entity_motion", "game_entity_motion.insert", "a mover's FIRST heartbeat"), - ("wire_update", "game_entity_motion", "game_entity_motion.update", "every heartbeat after the first — i.e. all peer movement"), - ("wire_insert", "game_creature_spline", "game_creature_spline.insert", "a creature's first leg"), - ("wire_update", "game_creature_spline", "game_creature_spline.update", "every leg after the first"), + ( + "wire_insert", + "game_entity_motion", + "game_entity_motion.insert", + "a mover's FIRST heartbeat", + ), + ( + "wire_update", + "game_entity_motion", + "game_entity_motion.update", + "every heartbeat after the first — i.e. all peer movement", + ), + ( + "wire_insert", + "game_creature_spline", + "game_creature_spline.insert", + "a creature's first leg", + ), + ( + "wire_update", + "game_creature_spline", + "game_creature_spline.update", + "every leg after the first", + ), ] { assert!( - arm.contains(&format!("{helper}(db.{table}()")) && arm.contains(&format!("\"{label}\"")), + arm.contains(&format!("{helper}(db.{table}()")) + && arm.contains(&format!("\"{label}\"")), "the {table} {label} half is no longer registered on the shared coordinator \ dispatch — {what} is silently dropped for every session" ); @@ -6459,17 +6664,13 @@ mod tests { ); } - /// And the instrument's wiring: the counters are only ever read by the 10-second task in /// `world::run`. Without this scan, reverting that task to the old hand-formatted line — no /// delivery ratio, no dropped count — leaves the whole suite green, which is precisely how the /// 63 % under-delivery went unlogged in the first place. #[test] fn the_10s_task_prints_the_delivery_instrument_287() { - let body = crate::test_scan::code_of( - include_str!("../world/mod.rs"), - "pub async fn run(", - ); + let body = crate::test_scan::code_of(include_str!("../world/mod.rs"), "pub async fn run("); assert!( body.contains("crate::stdb::subscriptions::MOTION_DROPPED.load(Relaxed)") && body.contains("crate::stdb::subscriptions::motionstat_line("), @@ -6488,10 +6689,7 @@ mod tests { /// "reported the call, not the effect" defect class). #[test] fn the_10s_task_warns_on_a_fanout_collapse_b1() { - let body = crate::test_scan::code_of( - include_str!("../world/mod.rs"), - "pub async fn run(", - ); + let body = crate::test_scan::code_of(include_str!("../world/mod.rs"), "pub async fn run("); assert!( body.contains("crate::stdb::subscriptions::fanout_health_step(fan, fanout, submitted_delta)"), "the 10s task no longer runs the fan-out collapse check — peer movement can degrade by \ @@ -6504,5 +6702,4 @@ mod tests { something out loud. Body was:\n{body}" ); } - } diff --git a/gateway/src/stdb/world_index.rs b/gateway/src/stdb/world_index.rs index 619434be8..aedddc423 100644 --- a/gateway/src/stdb/world_index.rs +++ b/gateway/src/stdb/world_index.rs @@ -208,7 +208,9 @@ impl WorldIndex { // Matches the file-wide `.lock().unwrap()` discipline in `connection.rs`: a poisoned index // must degrade to a possibly-stale read, never take every future relay down with it. self.inner.lock().unwrap_or_else(|p| { - log::error!("world index lock poisoned (a prior panic in a critical section) — recovering"); + log::error!( + "world index lock poisoned (a prior panic in a critical section) — recovering" + ); p.into_inner() }) } @@ -503,11 +505,7 @@ impl WorldIndex { /// Every entity a viewer can see, with the shard whose cache holds each row — the login / /// world-entry sweep, and the inverse the differential test checks. O(25 + E) with cell scoping /// on; O(world) with `LYRACORE_AOI=0`. - pub fn visible_entities( - &self, - layer: EntityLayer, - session: SessionId, - ) -> Vec<(u64, ShardId)> { + pub fn visible_entities(&self, layer: EntityLayer, session: SessionId) -> Vec<(u64, ShardId)> { let inner = self.lock(); let Some(anchor) = inner.viewer_cell.get(&session).copied() else { return Vec::new(); @@ -601,7 +599,9 @@ mod tests { /// AOI builders emit — `col = n`, `col >= n`, `col <= n`, joined by ` AND ` — and PANICS on /// anything else, so a future query shape cannot silently pass by being unparsed. fn sql_selects(query: &str, row: &Row) -> bool { - let (_, where_clause) = query.split_once(" WHERE ").expect("an AOI query has a WHERE"); + let (_, where_clause) = query + .split_once(" WHERE ") + .expect("an AOI query has a WHERE"); where_clause.split(" AND ").all(|term| { let mut parts = term.split_whitespace(); let col = parts.next().expect("a column"); @@ -611,14 +611,19 @@ mod tests { .expect("a literal") .parse() .expect("an integer literal"); - assert!(parts.next().is_none(), "unexpected trailing token in `{term}`"); + assert!( + parts.next().is_none(), + "unexpected trailing token in `{term}`" + ); let actual: i64 = match col { "map_id" => row.map_id as i64, "instance_id" => row.instance_id as i64, "grid_x" => row.gx as i64, "grid_y" => row.gy as i64, "cell" => grid_cell_id(row.gx, row.gy), - other => panic!("the oracle does not know column `{other}` — teach it or fix the query"), + other => { + panic!("the oracle does not know column `{other}` — teach it or fix the query") + } }; match op { "=" => actual == literal, @@ -823,7 +828,10 @@ mod tests { let delta = index.move_viewer_delta(1, to); let after = sql_visible(EntityLayer::WorldEntity, to, &rows); match delta { - None => assert_eq!(from, to, "a delta of None must mean the anchor did not move"), + None => assert_eq!( + from, to, + "a delta of None must mean the anchor did not move" + ), Some(d) => { for (layer, layer_delta) in [ (EntityLayer::WorldEntity, d.world_entities), @@ -831,8 +839,7 @@ mod tests { ] { let entered: HashSet = layer_delta.entered.iter().map(|(g, _)| *g).collect(); - let left: HashSet = - layer_delta.left.iter().map(|(g, _)| *g).collect(); + let left: HashSet = layer_delta.left.iter().map(|(g, _)| *g).collect(); assert_eq!( entered, after.difference(&before).copied().collect::>(), @@ -862,7 +869,9 @@ mod tests { .neighbourhood() .map(|k| lyracore_shared::spatial::grid_cell_of_id(k.cell)) .collect(); - let want: HashSet<(i32, i32)> = (x0..=x1).flat_map(|x| (y0..=y1).map(move |y| (x, y))).collect(); + let want: HashSet<(i32, i32)> = (x0..=x1) + .flat_map(|x| (y0..=y1).map(move |y| (x, y))) + .collect(); assert_eq!(got, want); assert_eq!(got.len(), ((2 * BOX_HALF_SPAN + 1) as usize).pow(2)); // Every neighbour keeps the partition — a leak here is a dungeon copy visible from the @@ -972,8 +981,13 @@ mod tests { let index = WorldIndex::new(true); let a = CellKey::at(0, 0, 0, 0); index.add_viewer(1, a); - assert!(index.move_viewer_delta(1, a).is_none(), "same cell is not a recenter"); - assert!(index.move_viewer_delta(1, CellKey::at(0, 0, 1, 0)).is_some()); + assert!( + index.move_viewer_delta(1, a).is_none(), + "same cell is not a recenter" + ); + assert!(index + .move_viewer_delta(1, CellKey::at(0, 0, 1, 0)) + .is_some()); assert_eq!(index.viewer_cell(1), Some(CellKey::at(0, 0, 1, 0))); // Only one anchor is ever held: walking away must not leave the session visible from the // cell it started in. @@ -1007,12 +1021,17 @@ mod tests { index.upsert_entity(EntityLayer::WorldEntity, 3, CellKey::at(0, 77, 0, 0), 0, 0); // another instance index.upsert_entity(EntityLayer::WorldEntity, 4, CellKey::at(1, 0, 0, 0), 0, 0); // another map index.add_viewer(1, CellKey::at(0, 0, 0, 0)); - assert_eq!(index_visible(&index, EntityLayer::WorldEntity, 1), HashSet::from([1, 2])); + assert_eq!( + index_visible(&index, EntityLayer::WorldEntity, 1), + HashSet::from([1, 2]) + ); assert_eq!( index.viewers_of(EntityLayer::WorldEntity, CellKey::at(0, 0, 900, 900)), vec![1] ); - assert!(index.viewers_of(EntityLayer::WorldEntity, CellKey::at(0, 77, 0, 0)).is_empty()); + assert!(index + .viewers_of(EntityLayer::WorldEntity, CellKey::at(0, 77, 0, 0)) + .is_empty()); } /// `can_see` is a point query and `visible_entities` is the set form of the same question — @@ -1068,5 +1087,4 @@ mod tests { index.add_viewer(1, CellKey::at(0, 0, 0, 0)); assert_eq!(index.stats(EntityLayer::WorldEntity), (3, 1, 2)); } - } diff --git a/gateway/src/stdb/world_view.rs b/gateway/src/stdb/world_view.rs index 5547be001..2e4691aec 100644 --- a/gateway/src/stdb/world_view.rs +++ b/gateway/src/stdb/world_view.rs @@ -2640,9 +2640,18 @@ mod family_audience_tests { #[test] fn private_rows_reach_their_addressee_and_nobody_else() { assert!(private_recipient_audience(42, 42)); - assert!(!private_recipient_audience(42, 43), "a third party must never receive a private row"); - assert!(!private_recipient_audience(0, 42), "recipient 0 (unaddressed) matches no viewer"); - assert!(!private_recipient_audience(42, 0), "an uninitialized viewer guid matches no row"); + assert!( + !private_recipient_audience(42, 43), + "a third party must never receive a private row" + ); + assert!( + !private_recipient_audience(0, 42), + "recipient 0 (unaddressed) matches no viewer" + ); + assert!( + !private_recipient_audience(42, 0), + "an uninitialized viewer guid matches no row" + ); assert!( !private_recipient_audience(0, 0), "the all-zero case is a leak, not a match — both sides unset must still deny" @@ -2868,7 +2877,9 @@ mod family_audience_tests { assert_eq!(view.viewers_on_shard(4).len(), 1); view.remove_viewer(replacement.session); - assert!(view.viewer_of_owner(OwnerGuid(replacement.self_guid)).is_none()); + assert!(view + .viewer_of_owner(OwnerGuid(replacement.self_guid)) + .is_none()); assert!(view .viewer_of_identity(BoundIdentity(replacement.bound_identity)) .is_none()); @@ -2888,19 +2899,28 @@ mod family_audience_tests { teleport_appeared(&view, 0, &teleport(owner.self_guid)); - assert!(other_rx.try_recv().is_err(), "an unrelated viewer receives nothing"); + assert!( + other_rx.try_recv().is_err(), + "an unrelated viewer receives nothing" + ); let out = queued_job(&owner_rx); assert_eq!(out.len(), 1, "one teleport row creates one writer result"); match &out[0] { Outbound::Batch(messages) => { assert!(matches!( messages.as_slice(), - [ServerOpcodeMessage::SMSG_TRANSFER_PENDING(_), ServerOpcodeMessage::SMSG_NEW_WORLD(_)] + [ + ServerOpcodeMessage::SMSG_TRANSFER_PENDING(_), + ServerOpcodeMessage::SMSG_NEW_WORLD(_) + ] )); } _ => panic!("cross-map teleport must remain one ordered writer batch"), } - assert!(owner_rx.try_recv().is_err(), "the owner receives exactly one job"); + assert!( + owner_rx.try_recv().is_err(), + "the owner receives exactly one job" + ); } #[test] @@ -2917,27 +2937,48 @@ mod family_audience_tests { view.add_viewer_on_shard(other, anchor, 0); item_owner_job(&view, 0, source.self_guid, |_| { - vec![Outbound::Raw { opcode: 1, body: vec![1] }] + vec![Outbound::Raw { + opcode: 1, + body: vec![1], + }] }); - assert!(matches!(queued_job(&source_rx).as_slice(), [Outbound::Raw { .. }])); - assert!(other_rx.try_recv().is_err(), "an unrelated item owner receives nothing"); + assert!(matches!( + queued_job(&source_rx).as_slice(), + [Outbound::Raw { .. }] + )); + assert!( + other_rx.try_recv().is_err(), + "an unrelated item owner receives nothing" + ); view.remove_viewer(source.session); item_owner_job(&view, 0, source.self_guid, |_| { - vec![Outbound::Raw { opcode: 2, body: vec![2] }] + vec![Outbound::Raw { + opcode: 2, + body: vec![2], + }] }); - assert!(source_rx.try_recv().is_err(), "a removed source viewer receives no cascade delete"); + assert!( + source_rx.try_recv().is_err(), + "a removed source viewer receives no cascade delete" + ); view.add_viewer_on_shard(destination, anchor, 1); item_owner_job(&view, 0, source.self_guid, |_| { - vec![Outbound::Raw { opcode: 2, body: vec![2] }] + vec![Outbound::Raw { + opcode: 2, + body: vec![2], + }] }); assert!( destination_rx.try_recv().is_err(), "a source-shard delta applied after destination registration must be dropped" ); item_owner_job(&view, 1, source.self_guid, |_| { - vec![Outbound::Raw { opcode: 3, body: vec![3] }] + vec![Outbound::Raw { + opcode: 3, + body: vec![3], + }] }); assert!(matches!( queued_job(&destination_rx).as_slice(), @@ -3055,12 +3096,18 @@ mod family_audience_tests { let (discovery_opcode, discovery_body) = crate::codec::build_exploration_experience_raw(88, 121); match fresh_out.as_slice() { - [ - Outbound::Raw { opcode: fog_opcode, body: fog_body }, - Outbound::Raw { opcode: toast_opcode, body: toast_body }, - ] => { + [Outbound::Raw { + opcode: fog_opcode, + body: fog_body, + }, Outbound::Raw { + opcode: toast_opcode, + body: toast_body, + }] => { assert_eq!((*fog_opcode, fog_body), (new_fog_opcode, &new_fog_body)); - assert_eq!((*toast_opcode, toast_body), (discovery_opcode, &discovery_body)); + assert_eq!( + (*toast_opcode, toast_body), + (discovery_opcode, &discovery_body) + ); } _ => panic!("fog VALUES must precede discovery feedback for a new area"), } @@ -3080,18 +3127,27 @@ mod family_audience_tests { reputation_appeared(&view, 0, &reputation(9001, 19, 3175)); reputation_appeared(&view, 0, &reputation(9001, 19, 3200)); - assert!(other_rx.try_recv().is_err(), "an unrelated owner receives nothing"); + assert!( + other_rx.try_recv().is_err(), + "an unrelated owner receives nothing" + ); for standing in [3175, 3200] { let (opcode, body) = crate::codec::build_set_faction_standing_raw(19, standing) .expect("a valid reputation index has a packet"); match queued_job(&owner_rx).as_slice() { - [Outbound::Raw { opcode: actual_opcode, body: actual_body }] => { + [Outbound::Raw { + opcode: actual_opcode, + body: actual_body, + }] => { assert_eq!((*actual_opcode, actual_body), (opcode, &body)); } _ => panic!("the reputation row must use its reputation_index, not faction_id"), } } - assert!(owner_rx.try_recv().is_err(), "each reputation row queues exactly one job"); + assert!( + owner_rx.try_recv().is_err(), + "each reputation row queues exactly one job" + ); } #[test] diff --git a/gateway/src/world/handlers/cast/mod.rs b/gateway/src/world/handlers/cast/mod.rs index 583bc1c10..ffb1d1771 100644 --- a/gateway/src/world/handlers/cast/mod.rs +++ b/gateway/src/world/handlers/cast/mod.rs @@ -709,10 +709,12 @@ pub(super) mod tests { if let Some(e) = &self.ranged_error { return Err(anyhow!("{e}")); } - self.ranged_attacks - .lock() - .unwrap() - .push((account_id, self_guid, target_guid, spell_id)); + self.ranged_attacks.lock().unwrap().push(( + account_id, + self_guid, + target_guid, + spell_id, + )); Ok(()) } @@ -728,7 +730,12 @@ pub(super) mod tests { /// The ranged teardown the cast module shares with the melee seam. `start_attack` is never /// reached from a cast route; it exists because the two share one durable engagement row. impl MeleeActionStore for InMemoryCasts { - fn start_attack(&self, _account_id: u64, _actor_guid: u64, _target_guid: u64) -> Result<()> { + fn start_attack( + &self, + _account_id: u64, + _actor_guid: u64, + _target_guid: u64, + ) -> Result<()> { unreachable!("no cast route arms a melee engagement") } diff --git a/gateway/src/world/handlers/cast/ranged.rs b/gateway/src/world/handlers/cast/ranged.rs index 27ceede52..be5b25e24 100644 --- a/gateway/src/world/handlers/cast/ranged.rs +++ b/gateway/src/world/handlers/cast/ranged.rs @@ -75,7 +75,10 @@ fn refuse( ); let outbound = vec![Outbound::Raw { opcode: OP_CAST_RESULT, - body: codec::build_cast_result_failed(spell, codec::cast_failure_reason_for(&e.to_string())), + body: codec::build_cast_result_failed( + spell, + codec::cast_failure_reason_for(&e.to_string()), + ), }]; // A refused RETARGET drops the client's toggle on that failure result, so the still-firing OLD // loop has to go too — otherwise the server keeps shooting a target the client believes it @@ -261,7 +264,10 @@ mod tests { "no CAST_RESULT(OK) and no GO: the activation is not a completed shot" ); let start = spell_start(&outbound); - assert_eq!(start.timer, 0, "the wind-up is an attack timer, not a cast bar"); + assert_eq!( + start.timer, 0, + "the wind-up is an attack timer, not a cast bar" + ); assert_eq!( start .targets @@ -465,7 +471,10 @@ mod tests { #[test] fn the_lowest_slot_live_stack_wins() { - let store = armed_with(launcher(100, 2), vec![(9, 300, 20), (4, 201, 60), (2, 200, 0)]); + let store = armed_with( + launcher(100, 2), + vec![(9, 300, 20), (4, 201, 60), (2, 200, 0)], + ); assert_eq!( activate_ammo(&store), diff --git a/gateway/src/world/handlers/combat.rs b/gateway/src/world/handlers/combat.rs index c4742cd5c..9396cfde0 100644 --- a/gateway/src/world/handlers/combat.rs +++ b/gateway/src/world/handlers/combat.rs @@ -81,7 +81,8 @@ pub(crate) fn handle_combat( } } ClientOpcodeMessage::CMSG_LEAVE_CHANNEL(c) => { - if let Err(e) = store.leave_channel(conn.account_id, self_guid, c.channel_name.clone()) { + if let Err(e) = store.leave_channel(conn.account_id, self_guid, c.channel_name.clone()) + { log::debug!( "world: leave_channel failed (account {}): {e}", conn.account_id diff --git a/gateway/src/world/handlers/mod.rs b/gateway/src/world/handlers/mod.rs index b64ff8955..21d9ba31e 100644 --- a/gateway/src/world/handlers/mod.rs +++ b/gateway/src/world/handlers/mod.rs @@ -10,8 +10,8 @@ use super::*; // a player context, decides refusal-versus-fatal itself, and returns the outbound batch for the // world session to send, so the family can be tested without a socket. -mod bank; mod auction; +mod bank; mod cast; mod char; mod combat; @@ -28,16 +28,15 @@ mod trainer; mod vendor; mod weather; -pub(crate) use bank::handle_bank; pub(crate) use auction::{ decode_auction_browse, dispatch_auction_action, dispatch_auction_browse_action, AuctionActionOutcome, AuctionActionPlayer, AuctionActionStore, AuctionBrowseRequest, AuctionPage, AuctionQuery, CreateAuctionOutcome, CreateAuctionRequest, PlaceBidOutcome, - PlaceBidRequest, - CMSG_AUCTION_LIST_ITEMS_OPCODE, + PlaceBidRequest, CMSG_AUCTION_LIST_ITEMS_OPCODE, }; #[cfg(test)] pub(crate) use auction::{AuctionHousePolicy, AuctionInteraction}; +pub(crate) use bank::handle_bank; pub(crate) use cast::{dispatch_cast, CastOutcome, CastPlayer, CastStore, CastTransition}; pub(crate) use char::handle_char; pub(crate) use combat::handle_combat; @@ -55,10 +54,12 @@ pub(crate) use melee::{ }; pub(crate) use query::handle_query; pub(crate) use quest::{ - dispatch_quest_action, quest_giver_menu, QuestActionOutcome, QuestActionPlayer, QuestActionStore, + dispatch_quest_action, quest_giver_menu, QuestActionOutcome, QuestActionPlayer, + QuestActionStore, }; pub(crate) use taxi::{ - dispatch_taxi_action, queue_reply_then_arm, TaxiActionOutcome, TaxiActionPlayer, TaxiActionStore, + dispatch_taxi_action, queue_reply_then_arm, TaxiActionOutcome, TaxiActionPlayer, + TaxiActionStore, }; pub(crate) use trade::handle_trade; pub(crate) use trainer::{handle_trainer, TrainerBuyOutcome}; diff --git a/gateway/src/world/handlers/quest.rs b/gateway/src/world/handlers/quest.rs index 412d424ee..0a9eb527e 100644 --- a/gateway/src/world/handlers/quest.rs +++ b/gateway/src/world/handlers/quest.rs @@ -329,7 +329,8 @@ pub(crate) fn dispatch_quest_action( // The gate fails open — missing standing data must not lock a player out of a // giver — but a dead reducer transport is not missing data and ends the session. Err(e) - if classify_quest_action_error(&e) == QuestActionErrorClass::GameplayRefusal => + if classify_quest_action_error(&e) + == QuestActionErrorClass::GameplayRefusal => { log::debug!( "world: questgiver {giver} interaction gate unavailable (account {}): {e}", @@ -371,7 +372,8 @@ pub(crate) fn dispatch_quest_action( match store.accept_quest(player.account_id, self_guid, a.guid.guid(), a.quest_id) { Ok(()) => {} Err(e) - if classify_quest_action_error(&e) == QuestActionErrorClass::GameplayRefusal => + if classify_quest_action_error(&e) + == QuestActionErrorClass::GameplayRefusal => { log::debug!( "world: accept_quest ignored (account {}): {e}", @@ -436,7 +438,8 @@ pub(crate) fn dispatch_quest_action( }, }), Err(e) - if classify_quest_action_error(&e) == QuestActionErrorClass::GameplayRefusal => + if classify_quest_action_error(&e) + == QuestActionErrorClass::GameplayRefusal => { log::debug!( "world: turn_in_quest refused (quest {}, reward index {}): {e}", @@ -493,7 +496,8 @@ pub(crate) fn dispatch_quest_action( match store.push_quest(player.account_id, self_guid, p.quest_id) { Ok(()) => {} Err(e) - if classify_quest_action_error(&e) == QuestActionErrorClass::GameplayRefusal => + if classify_quest_action_error(&e) + == QuestActionErrorClass::GameplayRefusal => { log::debug!( "world: push_quest ignored (account {}): {e}", @@ -796,7 +800,9 @@ mod tests { fn outbound(outcome: QuestActionOutcome) -> Vec { match outcome { QuestActionOutcome::Handled { outbound } => outbound, - QuestActionOutcome::PassThrough(_) => panic!("expected the quest module to handle this"), + QuestActionOutcome::PassThrough(_) => { + panic!("expected the quest module to handle this") + } } } @@ -828,7 +834,8 @@ mod tests { ..Default::default() }; - let batch = outbound(dispatch_quest_action(&actions, player(), status_query(GIVER)).unwrap()); + let batch = + outbound(dispatch_quest_action(&actions, player(), status_query(GIVER)).unwrap()); assert!(matches!( batch.as_slice(), @@ -850,7 +857,9 @@ mod tests { ..one_quest(codec::ROLE_START, false, false) }; - assert!(outbound(dispatch_quest_action(&actions, player(), hello(GIVER)).unwrap()).is_empty()); + assert!( + outbound(dispatch_quest_action(&actions, player(), hello(GIVER)).unwrap()).is_empty() + ); assert_eq!( actions.gate_requests.lock().unwrap().as_slice(), &[(GIVER, SELF_GUID)] diff --git a/gateway/src/world/handlers/vendor.rs b/gateway/src/world/handlers/vendor.rs index bac4b5de1..41d4245f2 100644 --- a/gateway/src/world/handlers/vendor.rs +++ b/gateway/src/world/handlers/vendor.rs @@ -30,13 +30,23 @@ pub(crate) trait VendorActionStore: Send + Sync { fn vendor_repair(&self, account_id: u64, self_guid: u64, npc_guid: u64, slot: u8) -> Result<()>; - fn vendor_sell(&self, account_id: u64, self_guid: u64, vendor_guid: u64, slot: u8) - -> Result<()>; + fn vendor_sell( + &self, + account_id: u64, + self_guid: u64, + vendor_guid: u64, + slot: u8, + ) -> Result<()>; /// Re-purchase the ring entry at 0-based `slot` from `vendor_guid`. The gateway maps the wire /// `BuybackSlot` enum via [`BUYBACK_WIRE_SLOT_BASE`] before calling. - fn vendor_buyback(&self, account_id: u64, self_guid: u64, vendor_guid: u64, slot: u8) - -> Result<()>; + fn vendor_buyback( + &self, + account_id: u64, + self_guid: u64, + vendor_guid: u64, + slot: u8, + ) -> Result<()>; } impl VendorActionStore for crate::stdb::Coordinator { @@ -57,7 +67,12 @@ impl VendorActionStore for crate::stdb::Coordinator { count: u32, ) -> Result<()> { crate::stdb::Coordinator::buy_item( - self, account_id, self_guid, vendor_guid, item_entry, count, + self, + account_id, + self_guid, + vendor_guid, + item_entry, + count, ) } @@ -214,13 +229,20 @@ pub(crate) fn dispatch_vendor_action( c.amount as u32, ) { Ok(()) => Vec::new(), - Err(e) if classify_vendor_action_error(&e) == VendorActionErrorClass::GameplayRefusal => { + Err(e) + if classify_vendor_action_error(&e) + == VendorActionErrorClass::GameplayRefusal => + { log::debug!( "world: vendor_buy failed (account {}): {e}", player.account_id ); vec![Outbound::One(ServerOpcodeMessage::SMSG_BUY_FAILED( - Box::new(codec::build_buy_failed(vendor_guid, item_entry, &e.to_string())), + Box::new(codec::build_buy_failed( + vendor_guid, + item_entry, + &e.to_string(), + )), ))] } Err(e) => return Err(e), @@ -286,7 +308,10 @@ pub(crate) fn dispatch_vendor_action( Ok(()) => Ok(VendorActionOutcome::Handled { outbound: build_buyback_view(store, self_guid), }), - Err(e) if classify_vendor_action_error(&e) == VendorActionErrorClass::GameplayRefusal => { + Err(e) + if classify_vendor_action_error(&e) + == VendorActionErrorClass::GameplayRefusal => + { log::debug!( "world: sell_item ignored (account {}): {e}", player.account_id @@ -319,7 +344,10 @@ pub(crate) fn dispatch_vendor_action( None => Vec::new(), }, }), - Err(e) if classify_vendor_action_error(&e) == VendorActionErrorClass::GameplayRefusal => { + Err(e) + if classify_vendor_action_error(&e) + == VendorActionErrorClass::GameplayRefusal => + { log::debug!( "world: buyback_item ignored (account {}): {e}", player.account_id @@ -456,10 +484,13 @@ mod tests { item_entry: u32, count: u32, ) -> Result<()> { - self.buy_requests - .lock() - .unwrap() - .push((account_id, self_guid, vendor_guid, item_entry, count)); + self.buy_requests.lock().unwrap().push(( + account_id, + self_guid, + vendor_guid, + item_entry, + count, + )); match &self.buy_error { Some(error) => Err(anyhow::anyhow!("{error}")), None => Ok(()), diff --git a/gateway/src/world/mail_tests.rs b/gateway/src/world/mail_tests.rs index eb712ebdb..effbaef2d 100644 --- a/gateway/src/world/mail_tests.rs +++ b/gateway/src/world/mail_tests.rs @@ -1,4 +1,3 @@ - use super::party_tests::{character, DORMANT, GINGER, TRIN, VIM}; use super::*; const MAILBOX: u64 = 0xF110_0000_0000_0042; diff --git a/gateway/src/world/mod.rs b/gateway/src/world/mod.rs index 4e8b88caf..eddf9bf70 100644 --- a/gateway/src/world/mod.rs +++ b/gateway/src/world/mod.rs @@ -50,25 +50,22 @@ use coalesce::CoalesceState; use handlers::{ decode_auction_browse, dispatch_auction_action, dispatch_auction_browse_action, dispatch_cast, dispatch_duel_action, dispatch_item_action, dispatch_loot_window, dispatch_melee_action, - dispatch_quest_action, - dispatch_taxi_action, dispatch_vendor_action, handle_bank, handle_char, handle_combat, - handle_loot, handle_mail, handle_query, handle_trade, handle_trainer, queue_reply_then_arm, - AuctionActionOutcome, AuctionActionPlayer, CastOutcome, CastPlayer, CastTransition, - DuelActionOutcome, DuelActionPlayer, ItemActionOutcome, ItemActionPlayer, LootWindowOutcome, - LootWindowPlayer, MeleeActionOutcome, - MeleeActionPlayer, OpenLootState, QuestActionOutcome, QuestActionPlayer, TaxiActionOutcome, - TaxiActionPlayer, VendorActionOutcome, VendorActionPlayer, - CMSG_AUCTION_LIST_ITEMS_OPCODE, quest_giver_menu, + dispatch_quest_action, dispatch_taxi_action, dispatch_vendor_action, handle_bank, handle_char, + handle_combat, handle_loot, handle_mail, handle_query, handle_trade, handle_trainer, + quest_giver_menu, queue_reply_then_arm, AuctionActionOutcome, AuctionActionPlayer, CastOutcome, + CastPlayer, CastTransition, DuelActionOutcome, DuelActionPlayer, ItemActionOutcome, + ItemActionPlayer, LootWindowOutcome, LootWindowPlayer, MeleeActionOutcome, MeleeActionPlayer, + OpenLootState, QuestActionOutcome, QuestActionPlayer, TaxiActionOutcome, TaxiActionPlayer, + VendorActionOutcome, VendorActionPlayer, CMSG_AUCTION_LIST_ITEMS_OPCODE, }; pub(crate) use handlers::{ zone_weather_message, AuctionBrowseRequest, AuctionPage, AuctionQuery, CreateAuctionOutcome, CreateAuctionRequest, ItemActionResult, LootActionStatus, LootWindowRefusal, - LootWindowRequestStatus, - PlaceBidOutcome, PlaceBidRequest, TrainerBuyOutcome, WeatherStore, + LootWindowRequestStatus, PlaceBidOutcome, PlaceBidRequest, TrainerBuyOutcome, WeatherStore, }; use login_queue::{Admission, LoginQueue}; -pub(crate) use social::ContactOutcome; use social::handle_social; +pub(crate) use social::ContactOutcome; pub use store::WorldStore; use transfer::{EscrowedTransfer, TransferPlan}; diff --git a/gateway/src/world/social.rs b/gateway/src/world/social.rs index 05f6bafcc..d929be2b1 100644 --- a/gateway/src/world/social.rs +++ b/gateway/src/world/social.rs @@ -312,9 +312,7 @@ fn friend_result_for(refusal: ContactRefusal, is_ignore: bool) -> FriendResult { // row that is not there, both read as the one "no such entry" line vanilla has. (ContactRefusal::NotOnList, false) | (ContactRefusal::NoSuchPlayer, _) - | (ContactRefusal::ActorUnavailable, _) => { - FriendResult::NotFound - } + | (ContactRefusal::ActorUnavailable, _) => FriendResult::NotFound, } } diff --git a/gateway/src/world/tests.rs b/gateway/src/world/tests.rs index 155c8b13a..e260b21c7 100644 --- a/gateway/src/world/tests.rs +++ b/gateway/src/world/tests.rs @@ -167,6 +167,7 @@ use wow_world_messages::vanilla::{ ItemQuality, Language, Level, + MSG_AUCTION_HELLO_Client, Map, Object, Race, @@ -179,13 +180,14 @@ use wow_world_messages::vanilla::{ TrainingFailureReason, WeatherType, WorldResult, + CMSG_ACTIVATETAXI, CMSG_ADD_FRIEND, CMSG_ADD_IGNORE, + CMSG_ATTACKSTOP, + CMSG_ATTACKSWING, CMSG_AUCTION_LIST_BIDDER_ITEMS, CMSG_AUCTION_LIST_ITEMS, CMSG_AUCTION_LIST_OWNER_ITEMS, - CMSG_ATTACKSTOP, - CMSG_ATTACKSWING, CMSG_AUTH_SESSION, CMSG_AUTOBANK_ITEM, CMSG_AUTOEQUIP_ITEM, @@ -233,13 +235,11 @@ use wow_world_messages::vanilla::{ CMSG_SETSHEATHED, CMSG_SET_SELECTION, CMSG_SPIRIT_HEALER_ACTIVATE, - CMSG_ACTIVATETAXI, CMSG_TAXINODE_STATUS_QUERY, CMSG_TAXIQUERYAVAILABLENODES, CMSG_TRAINER_BUY_SPELL, CMSG_TRAINER_LIST, CMSG_WHO, - MSG_AUCTION_HELLO_Client, // Cross-map teleport: the client's world-port-finished ack. MSG_MOVE_WORLDPORT_ACK, SMSG_WEATHER, @@ -2906,11 +2906,7 @@ impl TaxiActionStore for InMemoryStore { Ok(self.taxi_status) } - fn open_taxi( - &self, - character_guid: u64, - npc_guid: u64, - ) -> Result> { + fn open_taxi(&self, character_guid: u64, npc_guid: u64) -> Result> { self.taxi_calls .lock() .unwrap() @@ -5488,7 +5484,10 @@ fn item_query_preserves_imported_eligibility_through_the_encrypted_world_session assert_eq!(found.allowed_class.as_int(), template.allowed_class); assert_eq!(found.allowed_race.as_int(), template.allowed_race); - assert_eq!(u32::from(found.required_skill.as_int()), template.required_skill); + assert_eq!( + u32::from(found.required_skill.as_int()), + template.required_skill + ); assert_eq!(found.required_skill_rank, template.required_skill_rank); assert_eq!( u32::from(found.required_faction.as_int()), @@ -7993,7 +7992,10 @@ fn trainer_buy_rank_upgrade_supersedes_the_previous_rank_spell() { m.new_spell_id, 1233, "first wire slot carries the OLD rank (cmangos order)" ); - assert_eq!(m.old_spell_id, 1234, "second wire slot carries the NEW rank"); + assert_eq!( + m.old_spell_id, 1234, + "second wire slot carries the NEW rank" + ); } other => panic!("expected SMSG_SUPERCEDED_SPELL, got {other}"), } @@ -8332,10 +8334,7 @@ fn taxi_status_query_returns_the_persisted_bit_without_opening() { } other => panic!("expected SMSG_TAXINODE_STATUS, got {other}"), } - assert_eq!( - *store.taxi_calls.lock().unwrap(), - vec![("status", 1, 90)] - ); + assert_eq!(*store.taxi_calls.lock().unwrap(), vec![("status", 1, 90)]); drop(client); server.join().unwrap(); } diff --git a/gateway/src/world/trade_tests.rs b/gateway/src/world/trade_tests.rs index 02ae067ab..c5367a9af 100644 --- a/gateway/src/world/trade_tests.rs +++ b/gateway/src/world/trade_tests.rs @@ -8,9 +8,9 @@ use super::*; use wow_world_messages::vanilla::{ - CMSG_ACCEPT_TRADE, CMSG_BEGIN_TRADE, CMSG_BUSY_TRADE, CMSG_CANCEL_TRADE, - CMSG_CLEAR_TRADE_ITEM, CMSG_IGNORE_TRADE, CMSG_INITIATE_TRADE, CMSG_SET_TRADE_GOLD, - CMSG_SET_TRADE_ITEM, CMSG_UNACCEPT_TRADE, + CMSG_ACCEPT_TRADE, CMSG_BEGIN_TRADE, CMSG_BUSY_TRADE, CMSG_CANCEL_TRADE, CMSG_CLEAR_TRADE_ITEM, + CMSG_IGNORE_TRADE, CMSG_INITIATE_TRADE, CMSG_SET_TRADE_GOLD, CMSG_SET_TRADE_ITEM, + CMSG_UNACCEPT_TRADE, }; /// **AC: initiating a trade with a targeted player reaches the store with the wire's target.** @@ -18,11 +18,9 @@ use wow_world_messages::vanilla::{ fn initiate_trade_dispatches_with_the_wire_target_guid() { let store = std::sync::Arc::new(quest_store()); let (mut client, mut c_enc, _c_dec, server) = enter_world(store.clone(), 1); - CMSG_INITIATE_TRADE { - guid: Guid::new(2), - } - .write_encrypted_client(&mut client, &mut c_enc) - .unwrap(); + CMSG_INITIATE_TRADE { guid: Guid::new(2) } + .write_encrypted_client(&mut client, &mut c_enc) + .unwrap(); drop(client); // every status rides the game_trade_event relay, no direct SMSG here server.join().unwrap(); assert_eq!(store.initiated_trades.lock().unwrap().as_slice(), &[(1, 2)]); @@ -34,11 +32,9 @@ fn initiate_trade_dispatches_with_the_wire_target_guid() { fn initiate_trade_dispatches_from_the_other_side_too() { let store = std::sync::Arc::new(quest_store()); let (mut client, mut c_enc, _c_dec, server) = enter_world(store.clone(), 2); - CMSG_INITIATE_TRADE { - guid: Guid::new(1), - } - .write_encrypted_client(&mut client, &mut c_enc) - .unwrap(); + CMSG_INITIATE_TRADE { guid: Guid::new(1) } + .write_encrypted_client(&mut client, &mut c_enc) + .unwrap(); drop(client); server.join().unwrap(); assert_eq!(store.initiated_trades.lock().unwrap().as_slice(), &[(2, 1)]); @@ -52,11 +48,9 @@ fn the_handshake_flow_dispatches_initiate_then_begin_then_cancel() { let store = std::sync::Arc::new(quest_store()); let (mut a, mut a_enc, _a_dec, a_server) = enter_world(store.clone(), 1); - CMSG_INITIATE_TRADE { - guid: Guid::new(2), - } - .write_encrypted_client(&mut a, &mut a_enc) - .unwrap(); + CMSG_INITIATE_TRADE { guid: Guid::new(2) } + .write_encrypted_client(&mut a, &mut a_enc) + .unwrap(); drop(a); a_server.join().unwrap(); @@ -108,9 +102,18 @@ fn offer_mutations_dispatch_with_wire_arguments() { .unwrap(); drop(client); server.join().unwrap(); - assert_eq!(store.set_trade_items.lock().unwrap().as_slice(), &[(1, 2, 23)]); - assert_eq!(store.cleared_trade_items.lock().unwrap().as_slice(), &[(1, 2)]); - assert_eq!(store.set_trade_golds.lock().unwrap().as_slice(), &[(1, 1_2345)]); + assert_eq!( + store.set_trade_items.lock().unwrap().as_slice(), + &[(1, 2, 23)] + ); + assert_eq!( + store.cleared_trade_items.lock().unwrap().as_slice(), + &[(1, 2)] + ); + assert_eq!( + store.set_trade_golds.lock().unwrap().as_slice(), + &[(1, 1_2345)] + ); } /// **AC (#121): sub-bag items are out of scope, not mis-addressed** — a `CMSG_SET_TRADE_ITEM` @@ -145,9 +148,13 @@ fn the_full_loop_dispatches_offer_and_dual_accept_in_order() { CMSG_INITIATE_TRADE { guid: Guid::new(2) } .write_encrypted_client(&mut a, &mut a_enc) .unwrap(); - CMSG_SET_TRADE_ITEM { trade_slot: 0, bag: 255, slot: 23 } - .write_encrypted_client(&mut a, &mut a_enc) - .unwrap(); + CMSG_SET_TRADE_ITEM { + trade_slot: 0, + bag: 255, + slot: 23, + } + .write_encrypted_client(&mut a, &mut a_enc) + .unwrap(); CMSG_ACCEPT_TRADE { unknown1: 0 } .write_encrypted_client(&mut a, &mut a_enc) .unwrap(); @@ -158,9 +165,11 @@ fn the_full_loop_dispatches_offer_and_dual_accept_in_order() { CMSG_BEGIN_TRADE {} .write_encrypted_client(&mut b, &mut b_enc) .unwrap(); - CMSG_SET_TRADE_GOLD { gold: wow_world_messages::vanilla::Gold::new(500) } - .write_encrypted_client(&mut b, &mut b_enc) - .unwrap(); + CMSG_SET_TRADE_GOLD { + gold: wow_world_messages::vanilla::Gold::new(500), + } + .write_encrypted_client(&mut b, &mut b_enc) + .unwrap(); CMSG_ACCEPT_TRADE { unknown1: 0 } .write_encrypted_client(&mut b, &mut b_enc) .unwrap(); @@ -168,8 +177,14 @@ fn the_full_loop_dispatches_offer_and_dual_accept_in_order() { b_server.join().unwrap(); assert_eq!(store.initiated_trades.lock().unwrap().as_slice(), &[(1, 2)]); - assert_eq!(store.set_trade_items.lock().unwrap().as_slice(), &[(1, 0, 23)]); - assert_eq!(store.set_trade_golds.lock().unwrap().as_slice(), &[(2, 500)]); + assert_eq!( + store.set_trade_items.lock().unwrap().as_slice(), + &[(1, 0, 23)] + ); + assert_eq!( + store.set_trade_golds.lock().unwrap().as_slice(), + &[(2, 500)] + ); assert_eq!(store.accepted_trades.lock().unwrap().as_slice(), &[1, 2]); } @@ -183,9 +198,11 @@ fn unaccept_and_post_accept_mutations_dispatch_for_the_acting_seat() { CMSG_ACCEPT_TRADE { unknown1: 0 } .write_encrypted_client(&mut client, &mut c_enc) .unwrap(); - CMSG_SET_TRADE_GOLD { gold: wow_world_messages::vanilla::Gold::new(9) } - .write_encrypted_client(&mut client, &mut c_enc) - .unwrap(); + CMSG_SET_TRADE_GOLD { + gold: wow_world_messages::vanilla::Gold::new(9), + } + .write_encrypted_client(&mut client, &mut c_enc) + .unwrap(); CMSG_UNACCEPT_TRADE {} .write_encrypted_client(&mut client, &mut c_enc) .unwrap(); diff --git a/gateway/src/world/whisper.rs b/gateway/src/world/whisper.rs index 8a58d2cb7..4a8b05217 100644 --- a/gateway/src/world/whisper.rs +++ b/gateway/src/world/whisper.rs @@ -85,7 +85,12 @@ pub(crate) fn run( message: String, ) -> Result<()> { let Some(realm) = store.realm_store() else { - return store.send_whisper(account_id, self_guid.unwrap_or(0), target_name.to_string(), message); + return store.send_whisper( + account_id, + self_guid.unwrap_or(0), + target_name.to_string(), + message, + ); }; let Some(sender_guid) = self_guid.filter(|&g| party::live_anywhere(store, g)) else { anyhow::bail!(lyracore_shared::whisper::NOT_IN_WORLD); diff --git a/importer/src/go_model.rs b/importer/src/go_model.rs index 622ffd136..47717e521 100644 --- a/importer/src/go_model.rs +++ b/importer/src/go_model.rs @@ -183,7 +183,10 @@ pub(crate) fn run(args: &crate::Args) -> Result<()> { ); if !args.apply { - println!("-- DRY RUN: would import {} game_go_model row(s)", rows.len()); + println!( + "-- DRY RUN: would import {} game_go_model row(s)", + rows.len() + ); return Ok(()); } if rows.is_empty() { @@ -214,7 +217,11 @@ pub(crate) fn run(args: &crate::Args) -> Result<()> { }; crate::call_reducer(args, reducer, batch)?; } - println!("go-models: applied {} row(s) across {} batch(es).", rows.len(), batches.len()); + println!( + "go-models: applied {} row(s) across {} batch(es).", + rows.len(), + batches.len() + ); Ok(()) } @@ -243,7 +250,11 @@ mod tests { (4,10,40,'Goober',0,0,0,1.0,0,0);"; let templates = door_templates(dump); let entries: Vec = templates.iter().map(|t| t.entry).collect(); - assert_eq!(entries, vec![1, 2], "only the DOOR (type 0) and BUTTON (type 1) rows survive"); + assert_eq!( + entries, + vec![1, 2], + "only the DOOR (type 0) and BUTTON (type 1) rows survive" + ); assert_eq!(templates[0].display_id, 10); assert_eq!(templates[1].display_id, 20); } @@ -252,7 +263,8 @@ mod tests { fn door_templates_drops_a_row_with_no_classification() { // Real cmangos type 25 (FISHINGHOLE) collides with the synthetic GATHER marker and is // always dropped by `classify_go_type` — must not appear as a DOOR/BUTTON either. - let dump = "INSERT INTO `gameobject_template` VALUES (5,25,50,'Fishing Hole',0,0,0,1.0,0,0);"; + let dump = + "INSERT INTO `gameobject_template` VALUES (5,25,50,'Fishing Hole',0,0,0,1.0,0,0);"; assert!(door_templates(dump).is_empty()); } @@ -292,7 +304,10 @@ mod tests { + (v[1] - center[1]).powi(2) + (v[2] - center[2]).powi(2)) .sqrt(); - assert!(d <= radius + 1e-4, "vertex {v:?} lies outside radius {radius}"); + assert!( + d <= radius + 1e-4, + "vertex {v:?} lies outside radius {radius}" + ); } } assert!(radius > 0.0); diff --git a/importer/src/vmap.rs b/importer/src/vmap.rs index 5530fec6b..f3a359c53 100644 --- a/importer/src/vmap.rs +++ b/importer/src/vmap.rs @@ -823,9 +823,7 @@ fn generation_info(args: &crate::Args, generation_id: u64) -> Result = all.iter().copied().collect(); - assert!(pending_cells(&all, &all_covered).is_empty(), "a fully resumed run submits nothing"); + assert!( + pending_cells(&all, &all_covered).is_empty(), + "a fully resumed run submits nothing" + ); } /// A fixture too large for one `BATCH_BYTES` call must split into several, and the split must @@ -1250,7 +1251,9 @@ mod tests { /// coverage identity (the reducer keys each cell independently). #[test] fn coverage_batches_split_a_fixture_larger_than_one_batch_and_reassemble_exactly() { - let cells: Vec<(u16, u16)> = (0..2000u32).map(|i| (i as u16, (i * 7 % 997) as u16)).collect(); + let cells: Vec<(u16, u16)> = (0..2000u32) + .map(|i| (i as u16, (i * 7 % 997) as u16)) + .collect(); let batches = coverage_batches(&cells); assert!(batches.len() > 1, "fixture must not fit in one batch"); for b in &batches { diff --git a/module/src/action_bar.rs b/module/src/action_bar.rs index 2f6b323f4..e95549db9 100644 --- a/module/src/action_bar.rs +++ b/module/src/action_bar.rs @@ -11,9 +11,7 @@ //! login, byte-identical to today. Import lands → creation copies the real rows → the gateway prefers //! them. [entity] -use spacetimedb::{ - table, Identity, ReducerContext, Table, -}; +use spacetimedb::{table, Identity, ReducerContext, Table}; use crate::spell::spellbook::createinfo_row_matches; // shared (race,class) wildcard-match helper diff --git a/module/src/auction.rs b/module/src/auction.rs index 5af0c1928..0922d83e5 100644 --- a/module/src/auction.rs +++ b/module/src/auction.rs @@ -3473,7 +3473,10 @@ mod tests { "source cleanup may not treat a sentinel as receipt evidence" ); assert!(source.hold.is_some()); - assert_eq!(source.receipt.as_ref().map(|receipt| receipt.auction_id), Some(0)); + assert_eq!( + source.receipt.as_ref().map(|receipt| receipt.auction_id), + Some(0) + ); } #[test] diff --git a/module/src/breath_relay.rs b/module/src/breath_relay.rs index 5f6be3294..40b9ceb49 100644 --- a/module/src/breath_relay.rs +++ b/module/src/breath_relay.rs @@ -46,7 +46,14 @@ crate::character_owned!(delete, fn sweep_delete_game_breath_relay_event(ctx, cha // its next movement edge instead. crate::character_owned!(not_transported, fn sweep_transfer_game_breath_relay_event()); -fn insert(ctx: &ReducerContext, character_guid: u64, kind: u8, time_remaining_ms: u32, duration_ms: u32, damage: u32) { +fn insert( + ctx: &ReducerContext, + character_guid: u64, + kind: u8, + time_remaining_ms: u32, + duration_ms: u32, + damage: u32, +) { ctx.db.game_breath_relay_event().insert(BreathRelayEvent { id: 0, character_guid, @@ -59,8 +66,20 @@ fn insert(ctx: &ReducerContext, character_guid: u64, kind: u8, time_remaining_ms } /// Write the single packet-worthy edge when a player submerges. -pub(crate) fn start(ctx: &ReducerContext, character_guid: u64, time_remaining_ms: u32, duration_ms: u32) { - insert(ctx, character_guid, KIND_START, time_remaining_ms, duration_ms, 0); +pub(crate) fn start( + ctx: &ReducerContext, + character_guid: u64, + time_remaining_ms: u32, + duration_ms: u32, +) { + insert( + ctx, + character_guid, + KIND_START, + time_remaining_ms, + duration_ms, + 0, + ); } /// Write the single packet-worthy edge when a player surfaces. diff --git a/module/src/bridge.rs b/module/src/bridge.rs index a94bac24e..335e9b0c4 100644 --- a/module/src/bridge.rs +++ b/module/src/bridge.rs @@ -13,9 +13,7 @@ //! fn. The `game_client_command!` build.rs marker (the 143-substrate ideal from the 184 plan) can //! replace the match when a second package needs registration without touching this file. -use spacetimedb::{ - table, Identity, ReducerContext, Table, Timestamp, -}; +use spacetimedb::{table, Identity, ReducerContext, Table, Timestamp}; use crate::game_character; diff --git a/module/src/combat/folds.rs b/module/src/combat/folds.rs index abc84d859..4dcbc5278 100644 --- a/module/src/combat/folds.rs +++ b/module/src/combat/folds.rs @@ -390,7 +390,10 @@ pub(crate) fn equipped_offhand_weapon_damage( /// ranged slot (17) — a bow/gun/crossbow (Auto Shot) or a wand (Shoot) — or `None` when nothing usable /// is equipped there. The ranged swing tick uses `delay_ms` as its swing interval and rolls damage from /// `[damage_min, damage_max]`. [entity] -pub(crate) fn equipped_ranged_weapon(ctx: &ReducerContext, player_guid: u64) -> Option<(u32, u32, u32, u8)> { +pub(crate) fn equipped_ranged_weapon( + ctx: &ReducerContext, + player_guid: u64, +) -> Option<(u32, u32, u32, u8)> { weapon_profile_in_slot(ctx, player_guid, crate::items::equip_slot::RANGED) } @@ -718,7 +721,6 @@ pub(crate) fn roll_money(ctx: &ReducerContext, min: u32, max: u32) -> u32 { min + ctx.random::() % (max - min + 1) } - #[cfg(test)] mod seal_tests { use super::seal_swing_holy; diff --git a/module/src/creatures/tick/mod.rs b/module/src/creatures/tick/mod.rs index 40f08c218..824396be0 100644 --- a/module/src/creatures/tick/mod.rs +++ b/module/src/creatures/tick/mod.rs @@ -343,9 +343,7 @@ pub(crate) fn active_cell_creatures( if e.owner_guid != 0 { pets.push(e.guid); } - if global - && e.unit_flags & lyracore_shared::constants::unit_flags::IN_COMBAT != 0 - { + if global && e.unit_flags & lyracore_shared::constants::unit_flags::IN_COMBAT != 0 { in_combat.push(e.guid); } } diff --git a/module/src/encounter.rs b/module/src/encounter.rs index 08cfb2f70..2a31c3cd7 100644 --- a/module/src/encounter.rs +++ b/module/src/encounter.rs @@ -956,7 +956,10 @@ mod tests { assert!(matches!(package_handler_for(binding, &[]), Ok(None))); let claimed = [(binding, handler as EncounterPackageHandler)]; - assert!(matches!(package_handler_for(binding, &claimed), Ok(Some(_)))); + assert!(matches!( + package_handler_for(binding, &claimed), + Ok(Some(_)) + )); let duplicate = [(binding, handler as EncounterPackageHandler); 2]; let duplicate = package_handler_for(binding, &duplicate) diff --git a/module/src/gc.rs b/module/src/gc.rs index a413918ef..a80ee7a92 100644 --- a/module/src/gc.rs +++ b/module/src/gc.rs @@ -3,6 +3,7 @@ use spacetimedb::{reducer, table, ReducerContext, ScheduleAt, Table}; +use crate::breath_relay::game_breath_relay_event; use crate::{ game_addon_message, game_bot_invite_intent, game_channel_event, game_chat_event, game_combat_event, game_duel_event, game_emote_event, game_group_event, game_group_invite, @@ -10,7 +11,6 @@ use crate::{ game_spell_impact_event, game_system_message_event, game_teleport_event, game_trade_event, game_trade_session, game_whisper_event, game_xp_event, EVENT_TTL_MICROS, INVITE_TTL_MICROS, }; -use crate::breath_relay::game_breath_relay_event; // `rest` isn't re-exported at crate scope (`mod rest;`, no `pub use rest::*;` in lib.rs) — every // other event table's accessor trait rides that glob, so this is the one accessor here needing its // own import. @@ -125,7 +125,9 @@ pub fn reap_movement_events(ctx: &ReducerContext, _schedule: EventReaperSchedule let now = ctx.timestamp.to_micros_since_unix_epoch(); let stale: Vec = sessions .iter() - .filter(|s| crate::trade::session_is_stale(s.created_at.to_micros_since_unix_epoch(), now)) + .filter(|s| { + crate::trade::session_is_stale(s.created_at.to_micros_since_unix_epoch(), now) + }) .map(|s| s.initiator_guid) .collect(); for guid in stale { diff --git a/module/src/go_model.rs b/module/src/go_model.rs index e97895e60..df38b19a4 100644 --- a/module/src/go_model.rs +++ b/module/src/go_model.rs @@ -48,9 +48,15 @@ fn parse_row(row: &str) -> Result<(u32, f32, f32, Vec), String> { if f.len() != 4 { return Err(format!("go_model row needs 4 fields, got {}", f.len())); } - let entry = f[0].parse::().map_err(|_| format!("bad entry: {}", f[0]))?; - let scale = f[1].parse::().map_err(|_| format!("bad scale: {}", f[1]))?; - let radius = f[2].parse::().map_err(|_| format!("bad radius: {}", f[2]))?; + let entry = f[0] + .parse::() + .map_err(|_| format!("bad entry: {}", f[0]))?; + let scale = f[1] + .parse::() + .map_err(|_| format!("bad scale: {}", f[1]))?; + let radius = f[2] + .parse::() + .map_err(|_| format!("bad radius: {}", f[2]))?; let blob = hex_decode(f[3])?; lyracore_shared::vmap::decode(&blob) .map_err(|err| format!("invalid go_model blob: {err:?}"))?; @@ -171,7 +177,11 @@ mod tests { h.import(&batch).unwrap(); let after_second: Vec<_> = h.rows.keys().copied().collect(); assert_eq!(after_first, after_second); - assert_eq!(h.rows.len(), 2, "re-importing the same batch must not duplicate rows"); + assert_eq!( + h.rows.len(), + 2, + "re-importing the same batch must not duplicate rows" + ); } #[test] @@ -180,7 +190,10 @@ mod tests { h.import(&packed_row(1, 1.0, 2.0)).unwrap(); h.append(&packed_row(1, 1.0, 5.0)).unwrap(); // corrected re-extract, same entry assert_eq!(h.rows.len(), 1); - assert_eq!(h.rows[&1].1, 5.0, "the newer radius must win, not error or duplicate"); + assert_eq!( + h.rows[&1].1, 5.0, + "the newer radius must win, not error or duplicate" + ); } #[test] @@ -188,7 +201,11 @@ mod tests { let mut h = GoModelHarness::default(); h.import(&packed_row(1, 1.0, 2.0)).unwrap(); h.import(&packed_row(2, 1.0, 3.0)).unwrap(); - assert_eq!(h.rows.len(), 1, "import (not append) must clear the prior batch"); + assert_eq!( + h.rows.len(), + 1, + "import (not append) must clear the prior batch" + ); assert!(h.rows.contains_key(&2)); assert!(!h.rows.contains_key(&1)); } diff --git a/module/src/group.rs b/module/src/group.rs index 675765dd8..4e3a01a10 100644 --- a/module/src/group.rs +++ b/module/src/group.rs @@ -489,10 +489,7 @@ fn checked_group_membership( /// all; [`GroupRefusal::NotLeader`] if it does but isn't the leader. `invite_core_on` treats /// `NotInGroup` as an ALLOWED case rather than propagating it — starting a brand-new group (where /// the inviter becomes leader) is fine; see its call site. -fn led_group_of( - ctx: &ReducerContext, - guid: u64, -) -> Result<(GroupMember, Group), GroupOpError> { +fn led_group_of(ctx: &ReducerContext, guid: u64) -> Result<(GroupMember, Group), GroupOpError> { let (m, group) = checked_group_membership(ctx, guid)?.ok_or(GroupRefusal::NotInGroup)?; if group.leader_guid != guid { return Err(GroupRefusal::NotLeader.into()); @@ -546,13 +543,12 @@ pub(crate) fn invite_core( inviter_guid: u64, target_guid: u64, ) -> Result<(), String> { - invite_core_on(ctx, Plane::Shard, inviter_guid, target_guid) - .map_err(|error| { - group_op_error( - error, - &format!("{inviter_guid} could not invite {target_guid}"), - ) - }) + invite_core_on(ctx, Plane::Shard, inviter_guid, target_guid).map_err(|error| { + group_op_error( + error, + &format!("{inviter_guid} could not invite {target_guid}"), + ) + }) } fn invite_core_on( @@ -646,13 +642,12 @@ fn invite_core_on( /// The identity-free accept core: shared by `gw::gw_accept_group_invite` and any server-driven /// acceptor (a playerbot's auto-accept hook calls this with the bot's guid). pub(crate) fn accept_invite_for(ctx: &ReducerContext, acceptor_guid: u64) -> Result<(), String> { - accept_invite_on(ctx, Plane::Shard, acceptor_guid) - .map_err(|error| { - group_op_error( - error, - &format!("{acceptor_guid} could not accept its invite"), - ) - }) + accept_invite_on(ctx, Plane::Shard, acceptor_guid).map_err(|error| { + group_op_error( + error, + &format!("{acceptor_guid} could not accept its invite"), + ) + }) } fn accept_invite_on( @@ -775,12 +770,8 @@ fn decline_invite_on(ctx: &ReducerContext, decliner_guid: u64) -> Result<(), Gro /// The identity-free leave core — the body `group_leave` used to inline. pub(crate) fn leave_group_for(ctx: &ReducerContext, leaver_guid: u64) -> Result<(), String> { - leave_group_on(ctx, leaver_guid).map_err(|error| { - group_op_error( - error, - &format!("{leaver_guid} could not leave its party"), - ) - }) + leave_group_on(ctx, leaver_guid) + .map_err(|error| group_op_error(error, &format!("{leaver_guid} could not leave its party"))) } fn leave_group_on(ctx: &ReducerContext, leaver_guid: u64) -> Result<(), GroupOpError> { @@ -811,8 +802,8 @@ fn uninvite_on( target_guid: u64, ) -> Result<(), GroupOpError> { let (m, _group) = led_group_of(ctx, leader_guid)?; - let (target, _target_group) = checked_group_membership(ctx, target_guid)? - .ok_or(GroupRefusal::TargetNotInGroup)?; + let (target, _target_group) = + checked_group_membership(ctx, target_guid)?.ok_or(GroupRefusal::TargetNotInGroup)?; if target.group_id != m.group_id { return Err(GroupRefusal::TargetNotInGroup.into()); } @@ -853,8 +844,8 @@ fn set_loot_method_on( return Err(GroupRefusal::InvalidLootRules.into()); } let resolved_master = if loot_setting == loot_method::MASTER { - let (target, _target_group) = checked_group_membership(ctx, master_guid)? - .ok_or(GroupRefusal::TargetNotInGroup)?; + let (target, _target_group) = + checked_group_membership(ctx, master_guid)?.ok_or(GroupRefusal::TargetNotInGroup)?; if target.group_id != m.group_id { return Err(GroupRefusal::TargetNotInGroup.into()); } @@ -1037,9 +1028,7 @@ pub fn realm_group_op( realm_op::LOOT_METHOD => set_loot_method_on(ctx, actor_guid, arg_a, target_guid, arg_b), other => return Err(format!("unknown realm group op {other}")), }; - ran.map_err(|error| { - group_op_error(error, &format!("realm group op {op} for {actor_guid}")) - }) + ran.map_err(|error| group_op_error(error, &format!("realm group op {op} for {actor_guid}"))) } /// Replace this database's MIRROR of one party with realm-core's authoritative roster. diff --git a/module/src/gw.rs b/module/src/gw.rs index 7af1414de..f24cce03b 100644 --- a/module/src/gw.rs +++ b/module/src/gw.rs @@ -169,8 +169,8 @@ pub fn reap_gateway_leases( /// `is_desync_error` on the gateway classifies on "not in world", and the two entries must be /// indistinguishable to that classifier. fn actor(ctx: &ReducerContext, actor_guid: u64) -> Result { - let actor = acting_entity_by_guid(ctx, actor_guid) - .ok_or_else(|| "mover not in world".to_string())?; + let actor = + acting_entity_by_guid(ctx, actor_guid).ok_or_else(|| "mover not in world".to_string())?; crate::taxi::reject_action_while_in_flight(ctx, actor_guid)?; Ok(actor) } @@ -421,8 +421,8 @@ pub fn gw_turn_in_quest( /// Taxi flight is an expected item Refusal. Missing actors still carry the generic actor error, /// which the Gateway treats as a failure with an unknown result. fn item_actor(ctx: &ReducerContext, actor_guid: u64) -> Result { - let actor = acting_entity_by_guid(ctx, actor_guid) - .ok_or_else(|| "mover not in world".to_string())?; + let actor = + acting_entity_by_guid(ctx, actor_guid).ok_or_else(|| "mover not in world".to_string())?; if crate::taxi::is_in_flight(ctx, actor_guid) { return Err(crate::items::refused(crate::items::refuse( lyracore_shared::item::ItemRefusal::NotRightNow, @@ -456,7 +456,11 @@ pub fn gw_cast_item_target( /// [`crate::actor::loot_money`] behind the gateway gate. #[reducer] -pub fn gw_loot_money(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> Result<(), String> { +pub fn gw_loot_money( + ctx: &ReducerContext, + actor_guid: u64, + target_guid: u64, +) -> Result<(), String> { require_operator(ctx).map_err(loot_operator_error)?; loot_actor(ctx, actor_guid)?; crate::actor::loot_money(ctx, actor_guid, target_guid) @@ -512,8 +516,8 @@ pub fn gw_use_gameobject( /// Taxi flight is an expected trainer Refusal. Missing actors still carry the generic actor error, /// which the Gateway treats as a failure with an unknown result. fn trainer_actor(ctx: &ReducerContext, actor_guid: u64) -> Result { - let actor = acting_entity_by_guid(ctx, actor_guid) - .ok_or_else(|| "mover not in world".to_string())?; + let actor = + acting_entity_by_guid(ctx, actor_guid).ok_or_else(|| "mover not in world".to_string())?; if crate::taxi::is_in_flight(ctx, actor_guid) { return Err(crate::trainer::refused( lyracore_shared::trainer::TrainerRefusal::Unavailable, @@ -676,7 +680,10 @@ pub fn gw_leave_world(ctx: &ReducerContext, actor_guid: u64) -> Result<(), Strin if let Some(entity) = ctx.db.game_world_entity().guid().find(actor_guid) { crate::world::remove_from_world(ctx, entity.owner_identity); } - ctx.db.game_gateway_session().entity_guid().delete(actor_guid); + ctx.db + .game_gateway_session() + .entity_guid() + .delete(actor_guid); Ok(()) } @@ -758,8 +765,8 @@ pub fn gw_send_channel_message( /// Taxi flight is an expected contact Refusal. Missing actors still carry the generic actor error, /// which the Gateway treats as a failure with an unknown result. fn contact_actor(ctx: &ReducerContext, actor_guid: u64) -> Result { - let actor = acting_entity_by_guid(ctx, actor_guid) - .ok_or_else(|| "mover not in world".to_string())?; + let actor = + acting_entity_by_guid(ctx, actor_guid).ok_or_else(|| "mover not in world".to_string())?; if crate::taxi::is_in_flight(ctx, actor_guid) { return Err(crate::chat::refused_contact( lyracore_shared::social::ContactRefusal::ActorUnavailable, @@ -771,7 +778,11 @@ fn contact_actor(ctx: &ReducerContext, actor_guid: u64) -> Result Result<(), String> { +pub fn gw_add_friend( + ctx: &ReducerContext, + actor_guid: u64, + target_guid: u64, +) -> Result<(), String> { require_operator(ctx)?; let sender = contact_actor(ctx, actor_guid)?; crate::chat::add_contact(ctx, sender, target_guid, false) @@ -779,7 +790,11 @@ pub fn gw_add_friend(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> /// [`crate::chat::remove_contact`] (friend arm) with the owner named by guid. #[reducer] -pub fn gw_del_friend(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> Result<(), String> { +pub fn gw_del_friend( + ctx: &ReducerContext, + actor_guid: u64, + target_guid: u64, +) -> Result<(), String> { require_operator(ctx)?; let sender = contact_actor(ctx, actor_guid)?; crate::chat::remove_contact(ctx, sender, target_guid, false) @@ -787,7 +802,11 @@ pub fn gw_del_friend(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> /// [`crate::chat::add_contact`] (ignore arm) with the owner named by guid. #[reducer] -pub fn gw_add_ignore(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> Result<(), String> { +pub fn gw_add_ignore( + ctx: &ReducerContext, + actor_guid: u64, + target_guid: u64, +) -> Result<(), String> { require_operator(ctx)?; let sender = contact_actor(ctx, actor_guid)?; crate::chat::add_contact(ctx, sender, target_guid, true) @@ -795,7 +814,11 @@ pub fn gw_add_ignore(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> /// [`crate::chat::remove_contact`] (ignore arm) with the owner named by guid. #[reducer] -pub fn gw_del_ignore(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> Result<(), String> { +pub fn gw_del_ignore( + ctx: &ReducerContext, + actor_guid: u64, + target_guid: u64, +) -> Result<(), String> { require_operator(ctx)?; let sender = contact_actor(ctx, actor_guid)?; crate::chat::remove_contact(ctx, sender, target_guid, true) @@ -808,8 +831,8 @@ pub fn gw_del_ignore(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> /// Taxi flight is an expected group Refusal. Missing actors still carry the generic actor error, /// which the Gateway treats as a failure with an unknown result. fn group_actor(ctx: &ReducerContext, actor_guid: u64) -> Result { - let actor = acting_entity_by_guid(ctx, actor_guid) - .ok_or_else(|| "mover not in world".to_string())?; + let actor = + acting_entity_by_guid(ctx, actor_guid).ok_or_else(|| "mover not in world".to_string())?; if crate::taxi::is_in_flight(ctx, actor_guid) { return Err(crate::group::refused( lyracore_shared::group::GroupRefusal::ActorUnavailable, @@ -985,13 +1008,7 @@ pub fn gw_group_loot_method( ) -> Result<(), String> { require_operator(ctx)?; group_actor(ctx, actor_guid)?; - crate::group::set_loot_method_for( - ctx, - actor_guid, - loot_setting, - master_guid, - loot_threshold, - ) + crate::group::set_loot_method_for(ctx, actor_guid, loot_setting, master_guid, loot_threshold) } /// [`crate::quest::apply_push_quest_to_party`] with the sharer named by guid. @@ -1229,7 +1246,11 @@ pub fn gw_inspect(ctx: &ReducerContext, actor_guid: u64, target_guid: u64) -> Re /// [`crate::talent::do_learn_talent`] with the learner named by guid. The owner identity stamped on /// the learned rows is the ACTOR's binding, never `ctx.sender()` (the shared connection's operator). #[reducer] -pub fn gw_learn_talent(ctx: &ReducerContext, actor_guid: u64, talent_id: u32) -> Result<(), String> { +pub fn gw_learn_talent( + ctx: &ReducerContext, + actor_guid: u64, + talent_id: u32, +) -> Result<(), String> { require_operator(ctx)?; let learner = actor(ctx, actor_guid)?; crate::talent::do_learn_talent(ctx, actor_guid, learner.owner_identity, talent_id).map(|_| ()) @@ -1335,10 +1356,6 @@ pub fn gw_pet_command( crate::creatures::apply_pet_command(ctx, actor_guid, data, target_guid) } - - - - /// [`crate::bridge::apply_client_command`] with the sender named by guid (operator call: /// the GM/bridge surface rides the gateway path like every other verb; authorization was never /// the connection identity). diff --git a/module/src/helpers.rs b/module/src/helpers.rs index 480a407cc..c0d57c80e 100644 --- a/module/src/helpers.rs +++ b/module/src/helpers.rs @@ -56,7 +56,9 @@ pub fn entity_by_owner(ctx: &ReducerContext, owner: Identity) -> Option Option { - gate_by_guid(ctx, ctx.db.game_world_entity().guid().find(guid), |e| e.guid) + gate_by_guid(ctx, ctx.db.game_world_entity().guid().find(guid), |e| { + e.guid + }) } /// THE by-guid chokepoint — `entity_by_owner`'s twin for every reducer that reaches a diff --git a/module/src/items/economy.rs b/module/src/items/economy.rs index 18736dec9..669493b1f 100644 --- a/module/src/items/economy.rs +++ b/module/src/items/economy.rs @@ -77,7 +77,13 @@ fn npc_interaction_gate( /// must be alive with a live BANKER npc in reach. pub(crate) fn bank_access_gate(ctx: &ReducerContext, player_guid: u64) -> Result<(), String> { use lyracore_shared::constants::npc_flags; - flagged_npc_in_reach_gate(ctx, player_guid, npc_flags::BANKER, "banker", "use the bank") + flagged_npc_in_reach_gate( + ctx, + player_guid, + npc_flags::BANKER, + "banker", + "use the bank", + ) } /// The hearth-bind trust gate: binding a home needs a live INNKEEPER npc in reach. diff --git a/module/src/lib.rs b/module/src/lib.rs index 3e7550bda..8fcbcbbac 100644 --- a/module/src/lib.rs +++ b/module/src/lib.rs @@ -249,9 +249,9 @@ mod action_bar; mod actor; mod auction; mod auth; +mod breath; mod breath_relay; mod bridge; -mod breath; mod character; mod chat; mod combat; @@ -269,8 +269,8 @@ mod exploration; mod debug; // Encounter kernel (work-item 228): instance-scoped encounter state, HP-threshold/death/GO-use // hooks, and the shared choreography primitives Packages consume (`packages/dungeons/` owns it). -mod encounter; mod duel; +mod encounter; mod faction; mod gameobject; mod gc; @@ -382,15 +382,13 @@ pub use debug::*; pub use duel::*; pub use encounter::*; pub use exploration::CharacterExplored; // re-exported for the gateway schema-parity test (282) -pub use rest::RestStateEvent; // re-exported for the gateway schema-parity test (4c) -pub use spell::stacking::SpellGroupRule; // Keeps this generated table in schema-parity coverage. pub use faction::*; pub use gameobject::*; -pub use gw::*; pub use gc::*; pub use gm::*; pub use graveyard::*; pub use group::*; +pub use gw::*; pub use instance::*; pub use items::*; pub use load::*; @@ -402,8 +400,10 @@ pub use quest::*; pub use realm_core::*; pub use region::*; pub use reputation::*; +pub use rest::RestStateEvent; // re-exported for the gateway schema-parity test (4c) pub use skill::*; pub use skilldata::*; +pub use spell::stacking::SpellGroupRule; // Keeps this generated table in schema-parity coverage. pub use spell::*; pub use stats::*; pub use talent::*; diff --git a/module/src/load.rs b/module/src/load.rs index 6f53a1059..e72d933f4 100644 --- a/module/src/load.rs +++ b/module/src/load.rs @@ -133,7 +133,10 @@ pub(crate) fn realm_wide_sessions(samples: impl Iterator }) .or_insert((id, sessions)); } - latest_per_gateway.values().map(|(_, sessions)| *sessions).sum() + latest_per_gateway + .values() + .map(|(_, sessions)| *sessions) + .sum() } /// The occupancy sanity gate, decided purely so both reducer and test share the exact rule. See diff --git a/module/src/loot/tag.rs b/module/src/loot/tag.rs index d5090cd97..c851b20a9 100644 --- a/module/src/loot/tag.rs +++ b/module/src/loot/tag.rs @@ -373,7 +373,14 @@ pub fn debug_verify_loot_tag_fixture(ctx: &ReducerContext) -> Result<(), String> crate::combat::disengage(ctx, aggression); let pet = fixture_creature_guid(3); - insert_fixture_entity(ctx, &origin, pet, base_x, false, LOOT_TAG_FIXTURE_CHARACTER_B); + insert_fixture_entity( + ctx, + &origin, + pet, + base_x, + false, + LOOT_TAG_FIXTURE_CHARACTER_B, + ); let pet_target = fixture_creature_guid(4); insert_fixture_entity(ctx, &origin, pet_target, base_x, false, 0); crate::threat::add_threat(ctx, pet_target, pet, 10); @@ -496,7 +503,11 @@ pub fn debug_verify_loot_tag_fixture(ctx: &ReducerContext) -> Result<(), String> leash_x: 0.0, leash_y: 0.0, }); - crate::combat::kill_player(ctx, LOOT_TAG_FIXTURE_CHARACTER_F, LOOT_TAG_FIXTURE_CHARACTER_B); + crate::combat::kill_player( + ctx, + LOOT_TAG_FIXTURE_CHARACTER_F, + LOOT_TAG_FIXTURE_CHARACTER_B, + ); expect_tagger(ctx, retained, LOOT_TAG_FIXTURE_CHARACTER_F)?; crate::combat::disengage(ctx, retained); if tagger(ctx, retained).is_some() diff --git a/module/src/motion.rs b/module/src/motion.rs index cdb74a78c..10d99ee0f 100644 --- a/module/src/motion.rs +++ b/module/src/motion.rs @@ -675,7 +675,10 @@ mod tests { body.contains("ctx.sender() != ctx.database_identity()"), "the tick must stay scheduler-only. Body was:\n{body}" ); - let drain = crate::test_scan::code_of(include_str!("motion.rs"), "pub(crate) fn publish_staged(ctx: &ReducerContext) {"); + let drain = crate::test_scan::code_of( + include_str!("motion.rs"), + "pub(crate) fn publish_staged(ctx: &ReducerContext) {", + ); assert!( drain.contains("tick_action(entities.guid().find(row.guid).is_some())"), "every queued row must go through `tick_action` with a REAL liveness probe — a \ diff --git a/module/src/nav.rs b/module/src/nav.rs index c07d0bfa4..1b1791178 100644 --- a/module/src/nav.rs +++ b/module/src/nav.rs @@ -281,8 +281,12 @@ pub fn nav_step( let stepped = if !nav_enabled(ctx) { crate::creatures::chase_step(cur.0, cur.1, dest.0, dest.1, max_step, stop_dist) } else { - match lyracore_shared::nav::find_leg(&mut fetcher(ctx, map_id), cur, dest, LEG_MAX_EXPANSIONS) - { + match lyracore_shared::nav::find_leg( + &mut fetcher(ctx, map_id), + cur, + dest, + LEG_MAX_EXPANSIONS, + ) { // Real detour: head for the first corner, no stop-short (that's for the final approach). Some(path) if path.len() > 1 || path[0] != dest => { let wp = path[0]; @@ -293,9 +297,7 @@ pub fn nav_step( crate::creatures::chase_step(cur.0, cur.1, dest.0, dest.1, max_step, stop_dist) } // Keep aiming at an unreachable goal; the commit gate truncates the move at geometry. - None => { - crate::creatures::chase_step(cur.0, cur.1, dest.0, dest.1, max_step, stop_dist) - } + None => crate::creatures::chase_step(cur.0, cur.1, dest.0, dest.1, max_step, stop_dist), } }; step_gate(ctx, map_id, cur, stepped, z) @@ -338,7 +340,10 @@ fn step_gate( if land_dist <= 0.0 { cur // hit inside the clearance margin — hold in place } else { - (cur.0 + dx / hit_dist * land_dist, cur.1 + dy / hit_dist * land_dist) + ( + cur.0 + dx / hit_dist * land_dist, + cur.1 + dy / hit_dist * land_dist, + ) } } None => stepped, // clear (or no data under this segment) — the plain step stands diff --git a/module/src/professions.rs b/module/src/professions.rs index d3ca1a9ea..734b37c20 100644 --- a/module/src/professions.rs +++ b/module/src/professions.rs @@ -12,7 +12,7 @@ //! `apply_enchant_item`/`entry_is_beast` call sites (debug.rs's twins) keep compiling unchanged — //! `loot.rs` re-exports this module's public surface. -use spacetimedb::{ReducerContext}; +use spacetimedb::ReducerContext; use crate::game_creature_template; use crate::game_fishing_loot; diff --git a/module/src/seed.rs b/module/src/seed.rs index da25244df..65be63f11 100644 --- a/module/src/seed.rs +++ b/module/src/seed.rs @@ -1393,12 +1393,14 @@ fn seed_scheduler_arming(ctx: &ReducerContext) { scheduled_at: ScheduleAt::Interval(TimeDuration::from_micros(1_000_000)), }); - ctx.db.game_duel_schedule().insert(crate::duel::DuelSchedule { - scheduled_id: 0, - scheduled_at: ScheduleAt::Interval(TimeDuration::from_micros( - crate::duel::DUEL_TICK_MICROS, - )), - }); + ctx.db + .game_duel_schedule() + .insert(crate::duel::DuelSchedule { + scheduled_id: 0, + scheduled_at: ScheduleAt::Interval(TimeDuration::from_micros( + crate::duel::DUEL_TICK_MICROS, + )), + }); // Ground-AoE damage tick every 500ms (118): drives game_ground_area (Consecration/…). 500ms so a // 1s/2s area period fires within ~½ tick of due. Areas gate on their own next_tick_micros. @@ -1937,14 +1939,22 @@ mod tests { crate::skill::LEARN_JOURNEYMAN_RIDING_SPELL_ID, ]; let unique: std::collections::BTreeSet = ids.iter().copied().collect(); - assert_eq!(unique.len(), ids.len(), "fixture spell ids collide: {ids:?}"); + assert_eq!( + unique.len(), + ids.len(), + "fixture spell ids collide: {ids:?}" + ); // Creature template + item entry: the same reserved families the other fixtures use. assert!( (51_000..52_000).contains(&RIDING_TRAINER_ENTRY), "the riding trainer must use a reserved 51xxx creature entry" ); - for entry in [TEST_WOLF_ENTRY, PROFESSION_TRAINER_ENTRY, TEST_TAME_BOAR_ENTRY] { + for entry in [ + TEST_WOLF_ENTRY, + PROFESSION_TRAINER_ENTRY, + TEST_TAME_BOAR_ENTRY, + ] { assert_ne!( entry, RIDING_TRAINER_ENTRY, "the riding trainer shadows an existing fixture creature" diff --git a/module/src/seed/fixtures.rs b/module/src/seed/fixtures.rs index 7266d6a5f..90eb52067 100644 --- a/module/src/seed/fixtures.rs +++ b/module/src/seed/fixtures.rs @@ -71,7 +71,7 @@ pub(crate) fn test_wolf_template() -> CreatureTemplate { // 0 ⇒ `skin_corpse` falls back to the flat Light Leather — the pre-210 verify flow // (debug_skin_nearest → 1x Light Leather) stays byte-identical without a seeded skin table. skin_loot_id: 0, - trainer_type: 0, // the test wolf is a beast, not a trainer + trainer_type: 0, // the test wolf is a beast, not a trainer trainer_class: 0, } } @@ -107,7 +107,7 @@ pub(crate) fn profession_trainer_template() -> CreatureTemplate { armor: 0, // a trainer never takes damage anyway pickpocket_loot_id: 0, // not imported — a friendly trainer is never pickpocketed skin_loot_id: 0, // not imported — a Humanoid trainer isn't skinnable anyway - trainer_type: 2, // TRADESKILLS — serves every class; the gate keys on trainer_class, which stays 0 + trainer_type: 2, // TRADESKILLS — serves every class; the gate keys on trainer_class, which stays 0 trainer_class: 0, } } @@ -404,7 +404,7 @@ pub(crate) fn tough_jerky_template(entry: u32) -> ItemTemplate { buy_price: 10, sell_price: 2, max_stack: 20, - food_type: 1, // Meat + food_type: 1, // Meat spellid_1: 50115, // "Eating" — A_PERIODIC_HEAL food HoT spelltrigger_1: 0, // on-use bonding: crate::items::bonding::NONE, // plain food — unbound/tradeable @@ -1489,7 +1489,7 @@ pub(crate) fn seed_scenario_fixtures(ctx: &ReducerContext) { armor: 0, pickpocket_loot_id: 0, skin_loot_id: 0, - trainer_type: 0, // not a trainer + trainer_type: 0, // not a trainer trainer_class: 0, }); } @@ -1520,7 +1520,7 @@ pub(crate) fn seed_scenario_fixtures(ctx: &ReducerContext) { armor: 0, pickpocket_loot_id: 0, // not imported — a Humanoid questgiver has no pickpocket table skin_loot_id: 0, // not imported — a Humanoid questgiver isn't skinnable anyway - trainer_type: 0, // not a trainer + trainer_type: 0, // not a trainer trainer_class: 0, }); } @@ -1553,7 +1553,7 @@ pub(crate) fn seed_scenario_fixtures(ctx: &ReducerContext) { armor: 0, pickpocket_loot_id: 0, // not imported — a Humanoid vendor has no pickpocket table skin_loot_id: 0, // not imported — a Humanoid vendor isn't skinnable anyway - trainer_type: 0, // not a trainer + trainer_type: 0, // not a trainer trainer_class: 0, }); } @@ -1647,7 +1647,7 @@ pub(crate) fn seed_scenario_fixtures(ctx: &ReducerContext) { armor: 0, pickpocket_loot_id: 0, // not imported — a friendly weapon master has no pickpocket table skin_loot_id: 0, // not imported — a Humanoid trainer isn't skinnable anyway - trainer_type: 2, // TRADESKILLS: the weapon master serves every class, matching the real Woo Ping (entry 11867) in the dump + trainer_type: 2, // TRADESKILLS: the weapon master serves every class, matching the real Woo Ping (entry 11867) in the dump trainer_class: 0, }); } diff --git a/module/src/spell/math.rs b/module/src/spell/math.rs index 9cfd4308b..1abb8f329 100644 --- a/module/src/spell/math.rs +++ b/module/src/spell/math.rs @@ -778,8 +778,7 @@ pub(crate) fn aura_moves_sheet(kind: u8, p0: i32) -> bool { || p0 == STAT_INT as i32 || p0 == STAT_SPI as i32 || p0 == STAT_ALL as i32)) - || (kind == A_MOD_COMBAT - && (p0 == COMBAT_ATTACK_POWER as i32 || p0 == COMBAT_CRIT as i32)) + || (kind == A_MOD_COMBAT && (p0 == COMBAT_ATTACK_POWER as i32 || p0 == COMBAT_CRIT as i32)) } /// One shield's draw against `incoming` damage from a pool of `remaining`: returns diff --git a/module/src/spell/tables.rs b/module/src/spell/tables.rs index 368d5f956..9fc3b4314 100644 --- a/module/src/spell/tables.rs +++ b/module/src/spell/tables.rs @@ -3,9 +3,7 @@ //! re-exports them (`pub use tables::*`) so every `crate::spell::` / `ctx.db.game_
()` //! path resolves. -use spacetimedb::{ - table, Identity, ReducerContext, ScheduleAt, Table, Timestamp, -}; +use spacetimedb::{table, Identity, ReducerContext, ScheduleAt, Table, Timestamp}; // The `scheduled(..)` table macros below reference the reducer callbacks by name; those reducers live in // `scheduler.rs` and are re-exported by `mod.rs`, so pull them into scope for the macro to resolve. diff --git a/module/src/spell/tests.rs b/module/src/spell/tests.rs index a78afb546..a82797d9a 100644 --- a/module/src/spell/tests.rs +++ b/module/src/spell/tests.rs @@ -717,7 +717,10 @@ fn aura_moves_sheet_gate() { // A combat aura on a field the sheet doesn't render (e.g. hit rating) must NOT trip it. assert!(!aura_moves_sheet(A_MOD_COMBAT, COMBAT_HIT as i32)); // A different aura kind with an AP-looking p0 still must NOT trip (the kind gate is first). - assert!(!aura_moves_sheet(A_PERIODIC_DAMAGE, COMBAT_ATTACK_POWER as i32)); + assert!(!aura_moves_sheet( + A_PERIODIC_DAMAGE, + COMBAT_ATTACK_POWER as i32 + )); } /// Cast interrupt-on-damage / CC-break flag decode: `break_auras_on_damage` (which also folds in the diff --git a/module/src/talent.rs b/module/src/talent.rs index 3335bc9b6..80382f8bd 100644 --- a/module/src/talent.rs +++ b/module/src/talent.rs @@ -33,9 +33,7 @@ //! the demo rows (idle, orphaned in tree_id 0-2 alongside real tabs) and the real tree — harmless, since //! nothing gates on "is this the only row in tree_id N". -use spacetimedb::{ - table, Identity, ReducerContext, Table, -}; +use spacetimedb::{table, Identity, ReducerContext, Table}; use crate::{game_character, game_spell, game_spell_effect, game_world_entity, Spell, SpellEffect}; diff --git a/module/src/transfer/harness.rs b/module/src/transfer/harness.rs index 4f0299c35..e5b2ff28c 100644 --- a/module/src/transfer/harness.rs +++ b/module/src/transfer/harness.rs @@ -475,7 +475,10 @@ fn active_taxi_refuses_transfer_before_any_source_mutation() { assert_eq!(error, "PLAYER_IN_TAXI_FLIGHT"); assert_eq!(*src.live.borrow(), before_live); - assert_eq!(src.chars.borrow().keys().copied().collect::>(), before_chars); + assert_eq!( + src.chars.borrow().keys().copied().collect::>(), + before_chars + ); assert_eq!(src.froze.get(), 0); assert!(src.out_rows.borrow().is_empty()); assert!(!src.reaper_armed.get()); diff --git a/module/src/tripwires.rs b/module/src/tripwires.rs index 41356a057..bdc807c71 100644 --- a/module/src/tripwires.rs +++ b/module/src/tripwires.rs @@ -906,12 +906,9 @@ pub(crate) mod grid_cell_tripwire { // `helpers.rs` reads the grid off rows (`grid_of`, `entity_addr`) and its ONE fixture writer // is covered; the exemptions below are files where `grid_x` appears only in a signature, // a tuple destructure or a comment. - [ - "module/src/tripwires.rs", - "module/src/region.rs", - ] - .iter() - .any(|p| path.ends_with(p) || path.replace('\\', "/").ends_with(p)) + ["module/src/tripwires.rs", "module/src/region.rs"] + .iter() + .any(|p| path.ends_with(p) || path.replace('\\', "/").ends_with(p)) } /// The AOI-scoped tables' own `#[table]` definitions declare `pub grid_x: i32` next to a `cell` @@ -919,7 +916,10 @@ pub(crate) mod grid_cell_tripwire { /// match that is a struct FIELD DECLARATION rather than an initializer or assignment. fn is_field_declaration(code: &str, idx: usize) -> bool { let line_start = code[..idx].rfind('\n').map(|i| i + 1).unwrap_or(0); - let line_end = code[idx..].find('\n').map(|i| idx + i).unwrap_or(code.len()); + let line_end = code[idx..] + .find('\n') + .map(|i| idx + i) + .unwrap_or(code.len()); let line = code[line_start..line_end].trim(); line.starts_with("pub grid_x") || line.starts_with("grid_x: i32") } @@ -1208,7 +1208,8 @@ pub(crate) mod grid_cell_tripwire { // `struct E` declares a `cell` column, so this tripwire is E's business — mirrors a real // cell-bearing table's own file, where the `#[table]` struct and every construction site // live together. - let cell_bearing_struct = "struct E {\n pub grid_x: i32,\n pub grid_y: i32,\n pub cell: i64,\n}\n"; + let cell_bearing_struct = + "struct E {\n pub grid_x: i32,\n pub grid_y: i32,\n pub cell: i64,\n}\n"; let bad = format!( "{cell_bearing_struct}fn f() -> E {{\n E {{\n grid_x,\n grid_y,\n }}\n}}\n" @@ -1272,7 +1273,8 @@ pub(crate) mod grid_cell_tripwire { /// was skipped outright — violation or not. Confirms a broken compact literal is still CAUGHT. #[test] fn the_scan_catches_a_broken_compact_one_line_shorthand() { - let cell_bearing_struct = "struct E {\n pub grid_x: i32,\n pub grid_y: i32,\n pub cell: i64,\n}\n"; + let cell_bearing_struct = + "struct E {\n pub grid_x: i32,\n pub grid_y: i32,\n pub cell: i64,\n}\n"; let bad = format!("{cell_bearing_struct}fn f() -> E {{ E {{ grid_x, grid_y }} }}\n"); assert_eq!( diff --git a/module/src/xp.rs b/module/src/xp.rs index ad54d9ae7..8351ea213 100644 --- a/module/src/xp.rs +++ b/module/src/xp.rs @@ -4,9 +4,7 @@ //! the gateway turns into `SMSG_LEVELUP_INFO`. The XP-bar number syncs at create-object (login); //! the popup + the heal carry the live feedback (see docs/roadmap). -use spacetimedb::{ - log, table, Identity, ReducerContext, Table, Timestamp, -}; +use spacetimedb::{log, table, Identity, ReducerContext, Table, Timestamp}; use crate::game_character; use crate::game_config; diff --git a/module/tests/typed_taxi_contracts.rs b/module/tests/typed_taxi_contracts.rs index e4550d2fb..40bbfe74b 100644 --- a/module/tests/typed_taxi_contracts.rs +++ b/module/tests/typed_taxi_contracts.rs @@ -51,7 +51,8 @@ fn gateway_taxi_gates_keep_refusals_typed_and_invariants_fatal() { standalone.assert_call("realm_group_op", &["0", "3", "5", "0", "0"]); standalone.assert_sql("DELETE FROM game_group WHERE leader_guid = 3"); - let valid_group_before = standalone.query_rows("SELECT * FROM game_group WHERE leader_guid = 1"); + let valid_group_before = + standalone.query_rows("SELECT * FROM game_group WHERE leader_guid = 1"); for args in [ &["0", "1", "3", "0", "0"][..], &["1", "5", "0", "0", "0"][..], @@ -223,7 +224,10 @@ fn build_module_bytes() -> Vec { .filter_map(|message| message["filenames"].as_array().cloned()) .flatten() .filter_map(|filename| filename.as_str().map(PathBuf::from)) - .find(|path| path.extension().is_some_and(|extension| extension == "wasm")) + .find(|path| { + path.extension() + .is_some_and(|extension| extension == "wasm") + }) .expect("Cargo did not report the built Module Wasm artifact"); let bytes = std::fs::read(&wasm).expect("the Wasm preflight output is missing"); assert!(bytes.starts_with(b"\0asm"), "the built module is not Wasm"); From b5facd66c4d8965f67fd7be369770bf1b22d4bac Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Thu, 3 Sep 2026 22:48:46 +0200 Subject: [PATCH 2/7] fix: clear clippy findings across the workspace --- gateway/src/codec/gameobject.rs | 7 +- gateway/src/main.rs | 2 +- gateway/src/stdb/mod.rs | 3 + gateway/src/stdb/reducers.rs | 2 + gateway/src/stdb/subscriptions.rs | 6 +- gateway/src/stdb/world_view.rs | 4 + gateway/src/world/handlers/mail.rs | 1 + gateway/src/world/handlers/query.rs | 1 + gateway/src/world/handlers/vendor.rs | 5 +- gateway/src/world/mod.rs | 1 + gateway/src/world/tests.rs | 3 - gateway/tests/schema_parity.rs | 3 + importer/src/dbc.rs | 1 + importer/src/eventai.rs | 109 +++++++++++------- importer/src/main.rs | 3 + module/build.rs | 1 + module/src/auction.rs | 82 +++++-------- module/src/breath.rs | 4 +- module/src/creatures/cycle/harness.rs | 1 + .../creatures/cycle/harness/eventai_combat.rs | 16 +-- module/src/creatures/eventai/combat.rs | 10 +- module/src/creatures/eventai/loader.rs | 1 + module/src/creatures/eventai/relay.rs | 1 + module/src/creatures/hunter_pet.rs | 5 +- module/src/creatures/pet_care.rs | 5 +- module/src/debug/instance.rs | 3 + module/src/debug/repair.rs | 1 + module/src/encounter.rs | 5 +- module/src/gw.rs | 2 +- module/src/loot/tag.rs | 1 + module/src/mail_escrow/harness.rs | 10 +- module/src/package_config.rs | 1 + module/src/quest.rs | 31 ++--- module/src/seed.rs | 2 + module/src/seed/fixtures.rs | 1 + module/src/spell/cast/mod.rs | 6 +- module/src/spell/cast/resolve.rs | 1 + module/src/spell/cast/targeting.rs | 1 + module/src/spell/mod.rs | 30 ++--- module/src/spell/scheduler.rs | 1 + module/src/test_scan.rs | 2 +- module/src/vmap.rs | 8 +- module/tests/support/mod.rs | 2 + 43 files changed, 216 insertions(+), 169 deletions(-) diff --git a/gateway/src/codec/gameobject.rs b/gateway/src/codec/gameobject.rs index 4e7df2668..c745041e3 100644 --- a/gateway/src/codec/gameobject.rs +++ b/gateway/src/codec/gameobject.rs @@ -199,6 +199,7 @@ pub fn build_dynamicobject_create_object( #[cfg(test)] mod tests { use super::*; + use std::f32::consts::FRAC_1_SQRT_2; use wow_world_messages::vanilla::opcodes::ServerOpcodeMessage; #[test] @@ -311,14 +312,14 @@ mod tests { display_id: 259, rotation_0: 0.1, rotation_1: 0.2, - rotation_2: 0.70710678, - rotation_3: 0.70710678, + rotation_2: FRAC_1_SQRT_2, + rotation_3: FRAC_1_SQRT_2, size: 0.0, }; let (opcode, body) = build_gameobject_rotation_values(&go); assert_eq!(opcode, 0x00A9, "SMSG_UPDATE_OBJECT opcode"); let floats = decode_rotation_floats(go.guid, &body); - assert_eq!(floats, [0.1, 0.2, 0.70710678, 0.70710678]); + assert_eq!(floats, [0.1, 0.2, FRAC_1_SQRT_2, FRAC_1_SQRT_2]); } #[test] diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 2cd1dec08..2bf123560 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -151,7 +151,7 @@ async fn run() -> Result<()> { tokio::time::sleep(std::time::Duration::from_secs(secs)).await; logon.abort(); world.abort(); - return Ok(()); + Ok(()) } #[cfg(not(feature = "dhat-heap"))] { diff --git a/gateway/src/stdb/mod.rs b/gateway/src/stdb/mod.rs index c88ee20cf..9880af40c 100644 --- a/gateway/src/stdb/mod.rs +++ b/gateway/src/stdb/mod.rs @@ -13,6 +13,9 @@ //! - `subscriptions`: `PlayerSubscriptions`, viewer setup, and shared packet builders. //! - `views`: row→view converters + the thin `RealmRow`/`AccountRow` mirrors. +// The SpacetimeDB codegen writes one function per table subscription; several run past any +// reasonable length ceiling and no edit here survives a regeneration. +#[allow(clippy::too_many_lines)] pub mod bindings; mod account_sessions; // per-account session-epoch + live-socket registry, split out of `connection` diff --git a/gateway/src/stdb/reducers.rs b/gateway/src/stdb/reducers.rs index c5df6892e..1067939a0 100644 --- a/gateway/src/stdb/reducers.rs +++ b/gateway/src/stdb/reducers.rs @@ -1229,6 +1229,8 @@ impl Coordinator { /// Cast a GROUND-TARGETED spell at a clicked world point (`CMSG_CAST_SPELL` with a DEST_LOCATION — /// Flamestrike/Blizzard/Rain of Fire). Same per-account attribution as `cast_spell`; the `(x,y,z)` is /// the ground click so the module anchors the AoE/patch there. + // The ground point rides flat because it mirrors the `gw_cast_spell_at` reducer signature. + #[allow(clippy::too_many_arguments)] pub fn cast_spell_at( &self, _account_id: u64, diff --git a/gateway/src/stdb/subscriptions.rs b/gateway/src/stdb/subscriptions.rs index e07431e43..c94250c96 100644 --- a/gateway/src/stdb/subscriptions.rs +++ b/gateway/src/stdb/subscriptions.rs @@ -4115,7 +4115,7 @@ mod tests { let out = trade_event_outbound(&event(k)); assert_eq!(out.len(), 1, "kind {k} must decode to exactly one packet"); match &out[0] { - Outbound::One(ServerOpcodeMessage::SMSG_TRADE_STATUS(s)) => (**s).clone(), + Outbound::One(ServerOpcodeMessage::SMSG_TRADE_STATUS(s)) => **s, Outbound::One(other) => panic!("kind {k}: expected SMSG_TRADE_STATUS, got {other}"), _ => panic!("kind {k}: expected a single SMSG_TRADE_STATUS packet"), } @@ -4253,9 +4253,7 @@ mod tests { let extended = |k: u8| -> SMSG_TRADE_STATUS_EXTENDED { let out = trade_event_outbound(&event(k, &payload)); match out.first() { - Some(Outbound::One(ServerOpcodeMessage::SMSG_TRADE_STATUS_EXTENDED(m))) => { - (**m).clone() - } + Some(Outbound::One(ServerOpcodeMessage::SMSG_TRADE_STATUS_EXTENDED(m))) => **m, _ => panic!("kind {k}: expected one SMSG_TRADE_STATUS_EXTENDED"), } }; diff --git a/gateway/src/stdb/world_view.rs b/gateway/src/stdb/world_view.rs index 2e4691aec..3d0a45663 100644 --- a/gateway/src/stdb/world_view.rs +++ b/gateway/src/stdb/world_view.rs @@ -515,6 +515,7 @@ pub(crate) fn arm_shard(view: Arc, coord: Coordinator, shard: ShardId }) } +#[allow(clippy::too_many_lines)] // One callback registration per relayed shard table. fn register_shard_callbacks( view: Arc, coord: Coordinator, @@ -2340,6 +2341,9 @@ fn duel_winner_audience(viewer_guid: u64, initiator_guid: u64, challenged_guid: viewer_guid != 0 && viewer_guid != initiator_guid && viewer_guid != challenged_guid } +// The relay-order tests below scan this file's own source, so the production items that follow +// them must stay where they are. +#[allow(clippy::items_after_test_module)] #[cfg(test)] mod cell_audience_tests; diff --git a/gateway/src/world/handlers/mail.rs b/gateway/src/world/handlers/mail.rs index 9f952553f..77f7cbc4a 100644 --- a/gateway/src/world/handlers/mail.rs +++ b/gateway/src/world/handlers/mail.rs @@ -8,6 +8,7 @@ use super::super::*; use wow_world_messages::vanilla::SMSG_SEND_MAIL_RESULT_MailResultTwo; +#[allow(clippy::too_many_lines)] // One arm per mail opcode. pub(crate) fn handle_mail( tx: &SessionTx, store: &St, diff --git a/gateway/src/world/handlers/query.rs b/gateway/src/world/handlers/query.rs index 6717774d6..66dac0ce5 100644 --- a/gateway/src/world/handlers/query.rs +++ b/gateway/src/world/handlers/query.rs @@ -51,6 +51,7 @@ fn filtered_gossip_options( /// Query / social family: name / creature / item lookups + the gossip / npc-text round-trips, plus /// the social tier (say / yell / whisper chat + text emotes) — grouped as the stateless /// request→reply / broadcast opcodes. +#[allow(clippy::too_many_lines)] // One arm per query and social opcode. pub(crate) fn handle_query( tx: &SessionTx, store: &St, diff --git a/gateway/src/world/handlers/vendor.rs b/gateway/src/world/handlers/vendor.rs index 41d4245f2..37b573f4d 100644 --- a/gateway/src/world/handlers/vendor.rs +++ b/gateway/src/world/handlers/vendor.rs @@ -436,11 +436,14 @@ mod tests { CMSG_REPAIR_ITEM, CMSG_SELL_ITEM, }; + /// (account_id, player_guid, vendor_guid, item_entry, count) as the handler passed them on. + type BuyRequest = (u64, u64, u64, u32, u32); + #[derive(Default)] struct InMemoryVendorActions { stock_requests: Mutex>, gate_requests: Mutex>, - buy_requests: Mutex>, + buy_requests: Mutex>, repair_requests: Mutex>, sell_requests: Mutex>, stock: Vec, diff --git a/gateway/src/world/mod.rs b/gateway/src/world/mod.rs index eddf9bf70..cf55a8bf3 100644 --- a/gateway/src/world/mod.rs +++ b/gateway/src/world/mod.rs @@ -1169,6 +1169,7 @@ fn apply_cast_transition(conn: &mut WorldConn, transition: CastTransition) { /// Route one decrypted client message through the per-family handlers. Each stage either consumes /// its opcode or passes it onward, so the disjoint-family chain ends in the movement-relay catch-all. +#[allow(clippy::too_many_lines)] // One stage per opcode family. fn dispatch( tx: &SessionTx, store: &St, diff --git a/gateway/src/world/tests.rs b/gateway/src/world/tests.rs index e260b21c7..c7a183783 100644 --- a/gateway/src/world/tests.rs +++ b/gateway/src/world/tests.rs @@ -128,9 +128,6 @@ mod framing_tests; #[path = "alpha_test_tools_tests.rs"] mod alpha_test_tools_tests; -/// The per-account connection release regressions. A sibling of the modules above for the -/// same reason — it reaches `InMemoryStore` without widening anything. - /// Multi-shard routing — reducer calls and subscriptions never target a shard other than the /// player's home shard. A sibling of the modules above for the same reason. `ShardCallLog` is /// `pub(super)` here and re-exported below because this file's own pre-shard-routing tests (and diff --git a/gateway/tests/schema_parity.rs b/gateway/tests/schema_parity.rs index 1c8bef02b..373e89374 100644 --- a/gateway/tests/schema_parity.rs +++ b/gateway/tests/schema_parity.rs @@ -84,6 +84,9 @@ #[path = "../src/stdb/bindings/mod.rs"] #[allow(dead_code)] +// The SpacetimeDB codegen writes one function per table subscription; several run past any +// reasonable length ceiling and no edit there survives a regeneration. +#[allow(clippy::too_many_lines)] mod bindings; use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9Builder; diff --git a/importer/src/dbc.rs b/importer/src/dbc.rs index 9097b0533..df74131e1 100644 --- a/importer/src/dbc.rs +++ b/importer/src/dbc.rs @@ -868,6 +868,7 @@ struct TaxiCatalogueSql { /// costs/indices must be non-negative, and the operator's data may not enter the reserved fixture /// namespace. A point for an absent path or a path without geometry is omitted with a stable warning. /// Any other malformed client extract fails before `run_sql_statements` can execute the first DELETE. +#[allow(clippy::too_many_lines)] // One validated statement per taxi table. fn taxi_catalogue_sql( nodes: &DbcTaxiNodes, paths: &DbcTaxiPath, diff --git a/importer/src/eventai.rs b/importer/src/eventai.rs index aab610b86..9c5c76bd0 100644 --- a/importer/src/eventai.rs +++ b/importer/src/eventai.rs @@ -1251,6 +1251,7 @@ struct RawRelayStep { } impl RelaySource { + #[allow(clippy::too_many_lines)] // One parse pass per dumped table. fn parse(dump: &str) -> Self { let mut source = Self::default(); source.broadcast_ids.extend( @@ -2164,6 +2165,7 @@ impl RawRelayStep { Ok(()) } + #[allow(clippy::too_many_lines)] // One arm per action kind that can name a dependency. fn resolved_dependencies( &self, source: &RelaySource, @@ -2625,6 +2627,7 @@ impl RawRelayStep { self.encode_instruction() } + #[allow(clippy::too_many_lines)] // One arm per action kind. fn encode_instruction(&self) -> Result { let bool_value = |value: u32| u8::from(value != 0); let forced = |value: i32| match value { @@ -3253,6 +3256,7 @@ fn source_overlap_census(dump: &str, rules: &[RawRule]) -> (u64, u64, u64) { } impl EventAiSource { + #[allow(clippy::too_many_lines)] // One assembly pass per EventAI rule family. pub(crate) fn assemble( &self, imported_entries: &HashSet, @@ -3394,12 +3398,14 @@ impl EventAiSource { rule.id, rule.subject, slot, - broadcasts, - summon_locations, - &self.quest_entries, - importable_templates, - &self.source_maps, - &self.relays, + ActionLookups { + broadcasts, + summon_locations, + quest_entries: &self.quest_entries, + importable_templates, + source_maps: &self.source_maps, + relays: &self.relays, + }, ) { Ok(action) => actions.push(action), Err(mut action_failures) => failures.append(&mut action_failures), @@ -4540,19 +4546,33 @@ fn repeats_on_each_event(event: u32) -> bool { ) } +/// The import-wide lookup tables one action mapping consults. +#[derive(Clone, Copy)] +struct ActionLookups<'a> { + broadcasts: &'a BTreeMap, + summon_locations: &'a BTreeMap, + quest_entries: &'a HashSet, + importable_templates: &'a HashSet, + source_maps: &'a HashMap>, + relays: &'a RelaySource, +} + fn map_action( action: [u32; 4], raw_subject: i32, rule_id: u64, subject: i32, slot: usize, - broadcasts: &BTreeMap, - summon_locations: &BTreeMap, - quest_entries: &HashSet, - importable_templates: &HashSet, - source_maps: &HashMap>, - relays: &RelaySource, + lookups: ActionLookups, ) -> Result> { + let ActionLookups { + broadcasts, + summon_locations, + quest_entries, + importable_templates, + source_maps, + relays, + } = lookups; let kind = action[0]; if let Some(mapped) = eventai_presentation::map_action(action, rule_id, subject, slot, importable_templates) @@ -6386,18 +6406,21 @@ mod tests { let templates = HashSet::new(); let maps = HashMap::new(); let relays = RelaySource::default(); + let lookups = ActionLookups { + broadcasts: &broadcasts, + summon_locations: &summons, + quest_entries: &quest_entries, + importable_templates: &templates, + source_maps: &maps, + relays: &relays, + }; let fail = map_action( [ACTION_SET_INSTANCE_DATA, 1, 2, 0], 4_832, 483_201, 4_832, 0, - &broadcasts, - &summons, - &quest_entries, - &templates, - &maps, - &relays, + lookups, ) .unwrap(); let complete = map_action( @@ -6406,12 +6429,7 @@ mod tests { 483_202, 4_832, 0, - &broadcasts, - &summons, - &quest_entries, - &templates, - &maps, - &relays, + lookups, ) .unwrap(); assert_eq!( @@ -6462,12 +6480,14 @@ mod tests { 10, 4_832, 1, - &BTreeMap::new(), - &BTreeMap::new(), - &HashSet::new(), - &HashSet::new(), - &maps, - &RelaySource::default(), + ActionLookups { + broadcasts: &BTreeMap::new(), + summon_locations: &BTreeMap::new(), + quest_entries: &HashSet::new(), + importable_templates: &HashSet::new(), + source_maps: &maps, + relays: &RelaySource::default(), + }, ); let Err(failures) = result else { panic!("cross-map encounter binding was accepted"); @@ -6970,12 +6990,14 @@ mod tests { 10, 100, slot, - &BTreeMap::new(), - &BTreeMap::new(), - &HashSet::new(), - &templates, - &HashMap::new(), - &relays, + ActionLookups { + broadcasts: &BTreeMap::new(), + summon_locations: &BTreeMap::new(), + quest_entries: &HashSet::new(), + importable_templates: &templates, + source_maps: &HashMap::new(), + relays: &relays, + }, ) .unwrap(); assert_eq!(mapped.encoded, expected); @@ -8180,6 +8202,7 @@ mod tests { #[test] #[ignore = "requires LYRACORE_CLASSIC_DB_SQL pointing at the pinned decompressed SQL or gzip"] + #[allow(clippy::too_many_lines)] // One assertion per censused source family. fn pinned_full_dump_source_census_matches_the_profile() { use std::io::Read; @@ -8398,12 +8421,14 @@ mod tests { rule.id, rule.subject, slot, - &source.broadcasts, - &source.summon_locations, - &source.quest_entries, - &HashSet::new(), - &source.source_maps, - &source.relays, + ActionLookups { + broadcasts: &source.broadcasts, + summon_locations: &source.summon_locations, + quest_entries: &source.quest_entries, + importable_templates: &HashSet::new(), + source_maps: &source.source_maps, + relays: &source.relays, + }, ) .unwrap() .encoded, diff --git a/importer/src/main.rs b/importer/src/main.rs index 95da7b4a5..e5b25f569 100644 --- a/importer/src/main.rs +++ b/importer/src/main.rs @@ -1047,6 +1047,7 @@ fn parse_args() -> Result { parse_args_from(std::env::args().skip(1)) } +#[allow(clippy::too_many_lines)] // One arm per CLI flag. fn parse_args_from(args: I) -> Result where I: IntoIterator, @@ -2754,6 +2755,7 @@ fn vendor_stock_item_set( /// have templates. Auto-inc ids are counter-assigned over deterministic (sorted) orders so clear+reload /// is idempotent. Only KILL objectives (positive `ReqCreatureOrGOId`) + guaranteed reward items /// (`RewItemId`) are mapped; gameobject objectives, choice rewards, item/source objectives are later slices. +#[allow(clippy::too_many_lines)] // Column-by-column mapping of one imported dump family. fn build_quests( dump: &str, local_creatures: &std::collections::HashSet, @@ -3089,6 +3091,7 @@ fn build_quests( /// chance) are skipped until a quest system exists. Loot ids are counter-assigned over a sorted /// creature order so clear+reload stays idempotent. `extra_item_entries` (e.g. quest reward items) are /// force-included in the item-template load so every referenced item gets a template. +#[allow(clippy::too_many_lines)] // Column-by-column mapping of one imported dump family. fn build_items_and_loot( dump: &str, creature_loot_ids: &std::collections::HashMap, diff --git a/module/build.rs b/module/build.rs index aa722f7df..d05c83a12 100644 --- a/module/build.rs +++ b/module/build.rs @@ -282,6 +282,7 @@ const PACKAGE_API_ROOT_ITEMS: &[&str] = &[ /// carry a reason, so an exemption is always readable where it is used and greppable across a tree. const PACKAGE_API_EXEMPT: &str = "// package-api: exempt"; +#[allow(clippy::too_many_lines)] // One scan-and-emit step per generated registry. fn main() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by cargo"); diff --git a/module/src/auction.rs b/module/src/auction.rs index 0922d83e5..143603989 100644 --- a/module/src/auction.rs +++ b/module/src/auction.rs @@ -1850,34 +1850,6 @@ impl ExpirySink for CtxExpiry<'_> { } } -fn listing_request( - operation_id: u64, - seller_guid: u64, - item_guid: u64, - house: u32, - deposit_rate: u32, - consignment_rate: u32, - start_bid: u32, - buyout: u32, - duration_minutes: u32, -) -> ListingRequest { - ListingRequest { - operation_id, - seller_guid, - item_guid, - house: AuctionHousePolicy { - id: house, - deposit_rate, - consignment_rate, - }, - terms: ListingTerms { - start_bid, - buyout, - duration_minutes, - }, - } -} - /// Single-database listing: item, deposit, Auction, receipt, and expiry are one transaction. #[reducer] #[allow(clippy::too_many_arguments)] @@ -1920,17 +1892,17 @@ pub fn gw_auction_list_local( } create_local_listing( &mut CtxSource { ctx }, - listing_request( + ListingRequest { operation_id, seller_guid, item_guid, - house.id, - house.deposit_rate, - house.consignment_rate, - start_bid, - buyout, - duration_minutes, - ), + house, + terms: ListingTerms { + start_bid, + buyout, + duration_minutes, + }, + }, ) .map(|_| ()) .map_err(|refusal| refused(refusal, "listing rejected")) @@ -1986,17 +1958,17 @@ pub fn gw_auction_hold_listing( } fence_listing( &mut CtxSource { ctx }, - listing_request( + ListingRequest { operation_id, seller_guid, item_guid, - house.id, - house.deposit_rate, - house.consignment_rate, - start_bid, - buyout, - duration_minutes, - ), + house, + terms: ListingTerms { + start_bid, + buyout, + duration_minutes, + }, + }, ) .map_err(|refusal| refused(refusal, "listing Hold rejected")) } @@ -2026,17 +1998,21 @@ pub fn realm_auction_commit_listing( ) -> Result<(), String> { crate::helpers::require_operator(ctx)?; let listing = PreparedListing { - request: listing_request( + request: ListingRequest { operation_id, seller_guid, item_guid, - house, - deposit_rate, - consignment_rate, - start_bid, - buyout, - duration_minutes, - ), + house: AuctionHousePolicy { + id: house, + deposit_rate, + consignment_rate, + }, + terms: ListingTerms { + start_bid, + buyout, + duration_minutes, + }, + }, snapshot: crate::items::ItemSnapshot { entry: item_entry, stack_count: item_stack_count, @@ -3130,7 +3106,7 @@ mod tests { } fn commit_refund(&mut self, refund: ListingRefund) { - self.mails.push(refund.mail.clone()); + self.mails.push(refund.mail); self.refund = Some(refund); } } diff --git a/module/src/breath.rs b/module/src/breath.rs index 97bd435f8..306b0475b 100644 --- a/module/src/breath.rs +++ b/module/src/breath.rs @@ -6,8 +6,8 @@ use spacetimedb::{reducer, table, ReducerContext, ScheduleAt, Table, Timestamp}; use crate::{game_world_entity, world::WorldEntity}; use lyracore_shared::env::{advance_breath, drowning_damage, FULL_AIR_MICROS}; -/// Vanilla's `Player::HandleDrowning` gives a player about one minute of air. Surfacing recovers -/// the same bar over ten seconds; the slower recovery prevents brief surface bobs from resetting it. +// Vanilla's `Player::HandleDrowning` gives a player about one minute of air. Surfacing recovers +// the same bar over ten seconds; the slower recovery prevents brief surface bobs from resetting it. /// One active breath timer. This is deliberately non-spatial: the scheduled tick must iterate all /// rows, and spatial iteration would silently omit players outside the caller's subscribed slice. diff --git a/module/src/creatures/cycle/harness.rs b/module/src/creatures/cycle/harness.rs index cbf41bc44..d2a80e2e2 100644 --- a/module/src/creatures/cycle/harness.rs +++ b/module/src/creatures/cycle/harness.rs @@ -5794,6 +5794,7 @@ fn discovery_stays_on_the_narrow_candidate_universes() { /// PLACE, which is what carries the swing clock across a threat switch, and only this assertion /// says so. Re-bless a deliberate change here with the same care. #[test] +#[allow(clippy::too_many_lines)] // One assertion per method the production adapter passes through. fn the_production_adapter_is_the_pass_through_the_harness_assumes() { let src = include_str!("ctx.rs"); for (signature, want) in [ diff --git a/module/src/creatures/cycle/harness/eventai_combat.rs b/module/src/creatures/cycle/harness/eventai_combat.rs index 654627ce5..87eab4fee 100644 --- a/module/src/creatures/cycle/harness/eventai_combat.rs +++ b/module/src/creatures/cycle/harness/eventai_combat.rs @@ -974,12 +974,12 @@ fn nearest_area_picks_the_closest_threat_holder_not_the_top_threat() { assert_eq!(scenario.casts(), vec![(CREATURE, 232, 0)]); assert_eq!( scenario.eventai_spell_starts.borrow().as_slice(), - &[(( + &[( SpellStartMode::Direct, SpellCastTarget::CasterArea, false, SpellCasterAdmission::Living, - ))] + )] ); } @@ -1007,12 +1007,12 @@ fn no_explicit_target_checks_the_caster_without_inventing_a_unit_target() { assert_eq!(ready.casts(), vec![(CREATURE, 521, 0)]); assert_eq!( ready.eventai_spell_starts.borrow().as_slice(), - &[(( + &[( SpellStartMode::Direct, SpellCastTarget::None, false, SpellCasterAdmission::Living, - ))] + )] ); } @@ -1050,12 +1050,12 @@ fn caster_area_requires_an_eligible_candidate_but_keeps_the_area_target() { assert_eq!(ready.casts(), vec![(CREATURE, 522, 0)]); assert_eq!( ready.eventai_spell_starts.borrow().as_slice(), - &[(( + &[( SpellStartMode::Direct, SpellCastTarget::CasterArea, false, SpellCasterAdmission::Living, - ))] + )] ); } @@ -1301,12 +1301,12 @@ fn triggered_cast_starts_during_a_pending_direct_cast() { assert_eq!(scenario.casts(), vec![(CREATURE, 300, TARGET)]); assert_eq!( scenario.eventai_spell_starts.borrow().as_slice(), - &[(( + &[( SpellStartMode::Triggered, SpellCastTarget::Unit(TARGET), false, SpellCasterAdmission::Living, - ))] + )] ); } diff --git a/module/src/creatures/eventai/combat.rs b/module/src/creatures/eventai/combat.rs index 41c51d9cb..538e0acd6 100644 --- a/module/src/creatures/eventai/combat.rs +++ b/module/src/creatures/eventai/combat.rs @@ -63,6 +63,7 @@ fn contexts_for_actors(world: &W, actors: Vec) -> V } /// Checks the rule's event condition and may name the actor selected by that condition. +#[allow(clippy::too_many_lines)] // One arm per EventAI event kind. pub(crate) fn condition( world: &W, context: &EventContext, @@ -520,10 +521,11 @@ pub(super) fn cast( return ActionResult::Refused; } } - if world.eventai_is_casting(caster.guid) { - if !cast.interrupt_previous && cast.start_mode == SpellStartMode::Direct { - return ActionResult::Refused; - } + if world.eventai_is_casting(caster.guid) + && !cast.interrupt_previous + && cast.start_mode == SpellStartMode::Direct + { + return ActionResult::Refused; } if world.eventai_start_spell( &caster, diff --git a/module/src/creatures/eventai/loader.rs b/module/src/creatures/eventai/loader.rs index 25d813505..fb46542d8 100644 --- a/module/src/creatures/eventai/loader.rs +++ b/module/src/creatures/eventai/loader.rs @@ -480,6 +480,7 @@ fn parse_recurrence(encoded: &str) -> Result { } } +#[allow(clippy::too_many_lines)] // One arm per instruction kind. fn parse_instruction(encoded: &str) -> Result { let fields: Vec<&str> = encoded.split(':').collect(); match fields.as_slice() { diff --git a/module/src/creatures/eventai/relay.rs b/module/src/creatures/eventai/relay.rs index af09db83c..095f674cc 100644 --- a/module/src/creatures/eventai/relay.rs +++ b/module/src/creatures/eventai/relay.rs @@ -1382,6 +1382,7 @@ fn apply_leaf( Ok(()) } +#[allow(clippy::too_many_lines)] // One arm per relay leaf kind. fn apply_leaf_for_pair( ctx: &ReducerContext, run: &mut RelayRun, diff --git a/module/src/creatures/hunter_pet.rs b/module/src/creatures/hunter_pet.rs index 85885c346..278b2f3cd 100644 --- a/module/src/creatures/hunter_pet.rs +++ b/module/src/creatures/hunter_pet.rs @@ -384,9 +384,12 @@ mod tests { assert_eq!(eligible().validate(), Ok(())); } + /// One gate broken in an otherwise valid tame. + type BreakGate = Box; + #[test] fn every_tame_gate_rejects_independently() { - let cases: Vec> = vec![ + let cases: Vec = vec![ Box::new(|v| v.caster_class = 9), Box::new(|v| v.caster_is_player = false), Box::new(|v| v.caster_owned = false), diff --git a/module/src/creatures/pet_care.rs b/module/src/creatures/pet_care.rs index 0881f0a5e..ef79d22f6 100644 --- a/module/src/creatures/pet_care.rs +++ b/module/src/creatures/pet_care.rs @@ -349,9 +349,12 @@ mod tests { } } + /// One gate broken in an otherwise valid feed. + type BreakGate = Box; + #[test] fn every_feed_gate_refuses_before_consumption() { - let cases: Vec> = vec![ + let cases: Vec = vec![ Box::new(|v| v.actor_is_player = false), Box::new(|v| v.item_owned = false), Box::new(|v| v.pet_exists = false), diff --git a/module/src/debug/instance.rs b/module/src/debug/instance.rs index c9ff5b5e5..390d15cc5 100644 --- a/module/src/debug/instance.rs +++ b/module/src/debug/instance.rs @@ -251,6 +251,7 @@ pub fn debug_set_nav_coverage_enabled(ctx: &ReducerContext, enabled: bool) -> Re /// hit/miss + the first-hit point for each flavor so a doodad-vs-wall distinction is directly /// observable: a doodad blocks `collision` but leaves `los` clear. #[reducer] +#[allow(clippy::too_many_arguments)] // A reducer's arguments are its call signature. pub fn debug_vmap_ray( ctx: &ReducerContext, map: u32, @@ -950,6 +951,7 @@ pub fn debug_assert_unreachable_goal_stops_at_wall( /// this reducer does no timing itself — it only produces the query volume and logs a checksum /// (`hits`) so two runs can be diffed for correctness, not just speed. #[reducer] +#[allow(clippy::too_many_arguments)] // A reducer's arguments are its call signature. pub fn debug_bench_los( ctx: &ReducerContext, map: u32, @@ -1017,6 +1019,7 @@ pub fn debug_bench_los( /// probes the ray actually truncated (checksum, not just speed, so two runs can be diffed for /// correctness too). #[reducer] +#[allow(clippy::too_many_arguments)] // A reducer's arguments are its call signature. pub fn debug_bench_collision_gate( ctx: &ReducerContext, map: u32, diff --git a/module/src/debug/repair.rs b/module/src/debug/repair.rs index 7907d7512..e08d1f054 100644 --- a/module/src/debug/repair.rs +++ b/module/src/debug/repair.rs @@ -38,6 +38,7 @@ use crate::{ /// provenance) with the total row count across every seeded/rearmed family, rather than the 13 /// separate per-family stamps the old reducers wrote. #[reducer] +#[allow(clippy::too_many_lines)] // One idempotent repair pass per row family. pub fn debug_repair_after_publish(ctx: &ReducerContext) -> Result<(), String> { crate::helpers::require_operator(ctx)?; diff --git a/module/src/encounter.rs b/module/src/encounter.rs index 2a31c3cd7..6c042d20f 100644 --- a/module/src/encounter.rs +++ b/module/src/encounter.rs @@ -962,9 +962,8 @@ mod tests { )); let duplicate = [(binding, handler as EncounterPackageHandler); 2]; - let duplicate = package_handler_for(binding, &duplicate) - .err() - .expect("duplicate authority must refuse"); + let duplicate = + package_handler_for(binding, &duplicate).expect_err("duplicate authority must refuse"); assert!(duplicate.contains("more than one installed package authority")); } diff --git a/module/src/gw.rs b/module/src/gw.rs index f24cce03b..8bb79da0a 100644 --- a/module/src/gw.rs +++ b/module/src/gw.rs @@ -1449,7 +1449,7 @@ mod tests { assert!(!lease_expired(1_000_000, 1_000_000, LEASE_TTL_MICROS)); assert!(!lease_expired(LEASE_TTL_MICROS, 0, LEASE_TTL_MICROS)); assert!(lease_expired(LEASE_TTL_MICROS + 1, 0, LEASE_TTL_MICROS)); - assert!( + const _: () = assert!( LEASE_TTL_MICROS >= 3 * LEASE_REAP_MICROS, "TTL must tolerate at least two missed heartbeats plus a reap interval" ); diff --git a/module/src/loot/tag.rs b/module/src/loot/tag.rs index c851b20a9..fda5f7a58 100644 --- a/module/src/loot/tag.rs +++ b/module/src/loot/tag.rs @@ -340,6 +340,7 @@ const LOOT_TAG_FIXTURE_CHARACTER_F: u64 = 5_093_856; /// Module rows in one isolated standalone transaction. #[cfg(feature = "debug_reducers")] #[spacetimedb::reducer] +#[allow(clippy::too_many_lines)] // One durable step per staged fixture row. pub fn debug_verify_loot_tag_fixture(ctx: &ReducerContext) -> Result<(), String> { let origin = ctx .db diff --git a/module/src/mail_escrow/harness.rs b/module/src/mail_escrow/harness.rs index 03a7c35f6..de0fb2c3d 100644 --- a/module/src/mail_escrow/harness.rs +++ b/module/src/mail_escrow/harness.rs @@ -141,7 +141,7 @@ impl FakeShard { .borrow() .values() .filter(|(o, _)| *o == owner) - .map(|(_, i)| i.clone()) + .map(|(_, i)| *i) .collect(); held.sort_by_key(|i| i.entry); held @@ -243,9 +243,7 @@ impl PayoutSink for FakeShard { } let guid = self.next_item_guid.get() + 1; self.next_item_guid.set(guid); - self.items - .borrow_mut() - .insert(guid, (payee_guid, item.clone())); + self.items.borrow_mut().insert(guid, (payee_guid, *item)); Ok(()) } fn file_receipt(&mut self, row: MailDelivery) { @@ -363,7 +361,7 @@ impl TakeFenceSink for FakeMailPlane { .borrow() .iter() .find(|m| m.id == mail_id) - .map(|m| (m.recipient_guid, m.item.clone())) + .map(|m| (m.recipient_guid, m.item)) } fn clear_mail_item(&mut self, mail_id: u64) { if let Some(m) = self.mails.borrow_mut().iter_mut().find(|m| m.id == mail_id) { @@ -395,7 +393,7 @@ impl DeliverySink for FakeMailPlane { body: letter.body.clone(), money: letter.money, cod: letter.cod, - item: item.clone(), + item: *item, }); id } diff --git a/module/src/package_config.rs b/module/src/package_config.rs index 85ce800ad..678e9e897 100644 --- a/module/src/package_config.rs +++ b/module/src/package_config.rs @@ -143,6 +143,7 @@ pub(crate) fn should_seed_default(row_exists: bool) -> bool { /// when no row exists yet — see [`should_seed_default`] for the decision this carries out. `pub(crate)` /// — a Package's own defaults are seeded from inside the module build, not through a reducer an /// external caller could hit. +#[cfg_attr(not(has_packages), allow(dead_code))] // package-only caller — see `build.rs` pub(crate) fn ensure_package_config_default( ctx: &ReducerContext, package: &str, diff --git a/module/src/quest.rs b/module/src/quest.rs index cb9342403..adb652559 100644 --- a/module/src/quest.rs +++ b/module/src/quest.rs @@ -1118,15 +1118,15 @@ const CHOICE_FIXTURE_GIVER_SOURCE: u64 = (0xF130_u64 << 48) | ((620_u64) << 24) #[cfg(feature = "debug_reducers")] const CHOICE_FIXTURE_QUEST: u32 = 509_033; #[cfg(feature = "debug_reducers")] -const CHOICE_FIXTURE_OBJECTIVE_ITEM: u32 = 509_033_0; +const CHOICE_FIXTURE_OBJECTIVE_ITEM: u32 = 5_090_330; #[cfg(feature = "debug_reducers")] -const CHOICE_FIXTURE_CHOICE_0: u32 = 509_033_1; +const CHOICE_FIXTURE_CHOICE_0: u32 = 5_090_331; #[cfg(feature = "debug_reducers")] -const CHOICE_FIXTURE_CHOICE_1: u32 = 509_033_2; +const CHOICE_FIXTURE_CHOICE_1: u32 = 5_090_332; #[cfg(feature = "debug_reducers")] -const CHOICE_FIXTURE_GUARANTEED: u32 = 509_033_3; +const CHOICE_FIXTURE_GUARANTEED: u32 = 5_090_333; #[cfg(feature = "debug_reducers")] -const CHOICE_FIXTURE_FILLER: u32 = 509_033_4; +const CHOICE_FIXTURE_FILLER: u32 = 5_090_334; /// Stage a quest-33-shaped durable turn-in fixture for the standalone reducer test. #[cfg(feature = "debug_reducers")] @@ -1212,9 +1212,9 @@ pub fn debug_stage_choice_reward_fixture( }); let objectives = ctx.db.game_quest_objective(); - objectives.id().delete(509_033_0); + objectives.id().delete(5_090_330); objectives.insert(QuestObjective { - id: 509_033_0, + id: 5_090_330, quest_entry: CHOICE_FIXTURE_QUEST, obj_index: 0, kind: objective_kind::COLLECT_ITEM, @@ -1222,34 +1222,34 @@ pub fn debug_stage_choice_reward_fixture( required_count: 8, }); let guaranteed = ctx.db.game_quest_reward_item(); - guaranteed.id().delete(509_033_3); + guaranteed.id().delete(5_090_333); guaranteed.insert(QuestRewardItem { - id: 509_033_3, + id: 5_090_333, quest_entry: CHOICE_FIXTURE_QUEST, item_entry: CHOICE_FIXTURE_GUARANTEED, count: 3, }); let choices = ctx.db.game_quest_reward_choice(); - choices.id().delete(509_033_1); - choices.id().delete(509_033_2); + choices.id().delete(5_090_331); + choices.id().delete(5_090_332); choices.insert(QuestRewardChoice { - id: 509_033_1, + id: 5_090_331, quest_entry: CHOICE_FIXTURE_QUEST, choice_index: 0, item_entry: CHOICE_FIXTURE_CHOICE_0, count: 1, }); choices.insert(QuestRewardChoice { - id: 509_033_2, + id: 5_090_332, quest_entry: CHOICE_FIXTURE_QUEST, choice_index: 1, item_entry: CHOICE_FIXTURE_CHOICE_1, count: 2, }); let relations = ctx.db.game_creature_quest(); - relations.id().delete(509_033_4); + relations.id().delete(5_090_334); relations.insert(CreatureQuest { - id: 509_033_4, + id: 5_090_334, creature_entry: CHOICE_FIXTURE_GIVER_ENTRY, quest_entry: CHOICE_FIXTURE_QUEST, role: quest_role::END, @@ -1479,6 +1479,7 @@ fn eventai_credit_fixture_count( /// Exercise EventAI quest authority against durable live rows in one standalone transaction. #[cfg(feature = "debug_reducers")] #[spacetimedb::reducer] +#[allow(clippy::too_many_lines)] // One durable step per staged fixture row. pub fn debug_verify_eventai_quest_credit_fixture(ctx: &ReducerContext) -> Result<(), String> { crate::helpers::require_operator(ctx)?; diff --git a/module/src/seed.rs b/module/src/seed.rs index 65be63f11..cfb73b06f 100644 --- a/module/src/seed.rs +++ b/module/src/seed.rs @@ -243,6 +243,7 @@ fn seed_production_core(ctx: &ReducerContext) { /// `importer --apply` run lands for map 0 (or fenced off entirely for any other continent). This is /// a DIFFERENT fixture family from `seed/fixtures.rs`'s synthetic engine-mechanic fixtures (5xxxx /// ids, no map content) — see that file's header. +#[allow(clippy::too_many_lines)] // A seed data table written as code. fn seed_map0_demo_content(ctx: &ReducerContext) { use constants::start_human_warrior as hw; @@ -803,6 +804,7 @@ fn seed_map0_demo_content(ctx: &ReducerContext) { /// Stratum 3 — the hand-authored spell/item registry: `game_spell`/`game_spell_effect` rows, the /// crafted-consumable on-use spells, 1-10-alpha consumable breadth, the mock-seed fixture kits /// (`seed/fixtures.rs`), enchant/disenchant, talents, and the stacking-group starter set. +#[allow(clippy::too_many_lines)] // A seed data table written as code. fn seed_spell_registry(ctx: &ReducerContext) { // --- Spell registry: hand-authored `game_spell` headers + `game_spell_effect` rows (the // data-driven effect-row engine). Each spell = a header + 1..3 effect rows; effect.id is the diff --git a/module/src/seed/fixtures.rs b/module/src/seed/fixtures.rs index 90eb52067..ad37e2911 100644 --- a/module/src/seed/fixtures.rs +++ b/module/src/seed/fixtures.rs @@ -1341,6 +1341,7 @@ pub(crate) fn seed_fixture_catalogue(ctx: &ReducerContext) { /// unconditionally (contrast `FIXTURE_BLADE`/`FIXTURE_JERKY` above, which stay reachable from `init` /// in every build and so carry no such attribute). #[cfg_attr(not(feature = "debug_reducers"), allow(dead_code))] +#[allow(clippy::too_many_lines)] // A seed data table written as code. pub(crate) fn seed_scenario_fixtures(ctx: &ReducerContext) { use crate::quest::quest_role; diff --git a/module/src/spell/cast/mod.rs b/module/src/spell/cast/mod.rs index d9b1edce4..604f5ed3c 100644 --- a/module/src/spell/cast/mod.rs +++ b/module/src/spell/cast/mod.rs @@ -1,7 +1,7 @@ //! Targeting + effect dispatch + the cast core (381 split of the old flat 2,999-line `cast.rs`): -//! - `targeting` — `select_targets` / `aura_apply` / `apply_effect` and the per-kind effect handlers. -//! - `resolve` — the cast core: `resolve_cast_at` (+ its extracted `check_cast_gates` gate sweep) / -//! `resolve_cast` / `begin_cast` / the passive-apply path. +//! - `targeting` — `select_targets` / `aura_apply` / `apply_effect` and the per-kind effect handlers. +//! - `resolve` — the cast core: `resolve_cast_at` (+ its extracted `check_cast_gates` gate sweep) / +//! `resolve_cast` / `begin_cast` / the passive-apply path. //! //! The cast-bar teardown family (`pushback_cast` / `interrupt_cast` / `break_channel` / //! `interrupt_cast_and_lock`) moved OUT of this module entirely, into `control.rs` — it already called diff --git a/module/src/spell/cast/resolve.rs b/module/src/spell/cast/resolve.rs index 2010fc660..5cf42ae91 100644 --- a/module/src/spell/cast/resolve.rs +++ b/module/src/spell/cast/resolve.rs @@ -760,6 +760,7 @@ fn check_cast_gate_prefix( } #[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_lines)] // One gate per refusal a cast can hit. fn check_cast_gate_suffix( ctx: &ReducerContext, caster: &WorldEntity, diff --git a/module/src/spell/cast/targeting.rs b/module/src/spell/cast/targeting.rs index 80fa6fc77..7f8cddb9b 100644 --- a/module/src/spell/cast/targeting.rs +++ b/module/src/spell/cast/targeting.rs @@ -328,6 +328,7 @@ fn displace_auras( /// re-freezes the magnitude + resets the next tick. The `UNIT_FIELD_AURA` slot is chosen PER-SPELL on a /// fresh apply (`pick_aura_slot` — all of a spell's effects share one slot ⇒ one icon). The cast visual is /// emitted ONCE by `resolve_cast_at`, not here. [entity] +#[allow(clippy::too_many_lines)] // The refresh and insert branches share one frozen magnitude and stay together. pub(crate) fn aura_apply( ctx: &ReducerContext, e: &SpellEffect, diff --git a/module/src/spell/mod.rs b/module/src/spell/mod.rs index 7f71b0fca..b4635c1b7 100644 --- a/module/src/spell/mod.rs +++ b/module/src/spell/mod.rs @@ -12,21 +12,21 @@ //! relay reuses the proven dirty_reset discipline (the gateway reads slot/spell_id/level/flags). [event] //! //! Submodules: -//! - `tables` — the spell/aura/cooldown/cast/schedule table structs (+ their generated accessors). -//! - `taxonomy` — the KIND/MECHANIC/param/target/combat-field/stat/resistance consts + tuning. -//! - `control` — the crowd-control model (the 7 CC predicates + immunity + break-on-damage), the -//! (381) cast-bar teardown family (`pushback_cast` / `interrupt_cast` / -//! `break_channel` / `interrupt_cast_and_lock`), and the death aura cleanup. -//! - `math` — pure helpers + aura-read folds only (388: no ctx-bound orchestration — see `effects`). -//! - `effects` — (388) the ctx-bound orchestration `math` used to carry: `arm_spell_retaliation` / -//! `apply_target_damage` / `dispel_target` / `recompute_vitals` / `absorb_incoming` — -//! every one of which WRITES to a live table, split out so `math`'s own charter is true. -//! - `cast` — a directory module (381 split): `targeting` (`select_targets` / `aura_apply` / -//! `apply_effect` + per-kind handlers) and `resolve` (the cast core — -//! `resolve_cast_at` + its extracted `check_cast_gates` gate sweep / `resolve_cast` / -//! `begin_cast` / the passive-apply path). -//! - `proc` — the Proc engine: the pure proc decision plus the ONE pass, from `apply_hit`. -//! - `scheduler` — the `#[reducer]`s (`tick_auras` / `fire_pending_cast` / `cast_spell`). +//! - `tables` — the spell/aura/cooldown/cast/schedule table structs (+ their generated accessors). +//! - `taxonomy` — the KIND/MECHANIC/param/target/combat-field/stat/resistance consts + tuning. +//! - `control` — the crowd-control model (the 7 CC predicates + immunity + break-on-damage), the +//! (381) cast-bar teardown family (`pushback_cast` / `interrupt_cast` / +//! `break_channel` / `interrupt_cast_and_lock`), and the death aura cleanup. +//! - `math` — pure helpers + aura-read folds only (388: no ctx-bound orchestration — see `effects`). +//! - `effects` — (388) the ctx-bound orchestration `math` used to carry: `arm_spell_retaliation` / +//! `apply_target_damage` / `dispel_target` / `recompute_vitals` / `absorb_incoming` — +//! every one of which WRITES to a live table, split out so `math`'s own charter is true. +//! - `cast` — a directory module (381 split): `targeting` (`select_targets` / `aura_apply` / +//! `apply_effect` + per-kind handlers) and `resolve` (the cast core — +//! `resolve_cast_at` + its extracted `check_cast_gates` gate sweep / `resolve_cast` / +//! `begin_cast` / the passive-apply path). +//! - `proc` — the Proc engine: the pure proc decision plus the ONE pass, from `apply_hit`. +//! - `scheduler` — the `#[reducer]`s (`tick_auras` / `fire_pending_cast` / `cast_spell`). //! //! Everything is re-exported below so every `crate::spell::` path resolves regardless of which //! submodule actually defines it. diff --git a/module/src/spell/scheduler.rs b/module/src/spell/scheduler.rs index dc9284ecd..c901a2641 100644 --- a/module/src/spell/scheduler.rs +++ b/module/src/spell/scheduler.rs @@ -327,6 +327,7 @@ pub(crate) fn is_due_for_expiry(eff_kind: u8, expires_at: Timestamp, now: Timest /// Scheduler-only. Time indexes select due rows across the Shard; each effect operates on its /// own target, so instance count does not change an aura's cadence. #[reducer] +#[allow(clippy::too_many_lines)] // One pass per aura family the tick settles. pub fn tick_auras(ctx: &ReducerContext, _schedule: AuraSchedule) { if ctx.sender() != ctx.database_identity() { return; diff --git a/module/src/test_scan.rs b/module/src/test_scan.rs index 71fe6f9d3..0d35732ce 100644 --- a/module/src/test_scan.rs +++ b/module/src/test_scan.rs @@ -106,7 +106,7 @@ pub(crate) fn on_comment_line(content: &str, byte_idx: usize) -> bool { /// must not treat assertion needles as live table reads. pub(crate) fn in_string_literal(content: &str, byte_idx: usize) -> bool { let line_start = content[..byte_idx].rfind('\n').map(|i| i + 1).unwrap_or(0); - let bytes = content[line_start..byte_idx].as_bytes(); + let bytes = &content.as_bytes()[line_start..byte_idx]; let mut in_string = false; let mut i = 0; while i < bytes.len() { diff --git a/module/src/vmap.rs b/module/src/vmap.rs index 5afaed351..84d41e7a9 100644 --- a/module/src/vmap.rs +++ b/module/src/vmap.rs @@ -208,6 +208,7 @@ fn same_stage_request( /// Start a generation. Repeating the exact request resumes it; a conflicting request is refused. #[reducer] +#[allow(clippy::too_many_arguments)] // A reducer's arguments are its call signature. pub fn stage_vmap_generation( ctx: &ReducerContext, generation_id: u64, @@ -1019,12 +1020,15 @@ mod tests { use super::*; use std::collections::BTreeMap; + /// (base_z, walk, obs) for one coverage cell. + type CoverageCell = (f32, Vec, Vec); + #[derive(Default)] struct LifecycleHarness { generations: BTreeMap, chunks: BTreeMap<(u64, u64, u32), Vec>, /// (generation_id, cell_key) -> (base_z, walk, obs) — mirrors `VmapNavCoverage`. - coverage: BTreeMap<(u64, u64), (f32, Vec, Vec)>, + coverage: BTreeMap<(u64, u64), CoverageCell>, /// generation_id -> (cell_count, digest, complete) — mirrors `VmapNavCoverageManifest`. coverage_manifests: BTreeMap, } @@ -1116,7 +1120,7 @@ mod tests { /// Mirrors `finalize_vmap_nav_coverage`: cell count + digest over the sorted current rows. fn finalize_coverage(&mut self, id: u64) -> Result<(), String> { self.coverage_preparable(id)?; - let mut cells: Vec<(u64, &(f32, Vec, Vec))> = self + let mut cells: Vec<(u64, &CoverageCell)> = self .coverage .iter() .filter(|((generation_id, _), _)| *generation_id == id) diff --git a/module/tests/support/mod.rs b/module/tests/support/mod.rs index e9901dcfe..5fac679ab 100644 --- a/module/tests/support/mod.rs +++ b/module/tests/support/mod.rs @@ -121,6 +121,7 @@ impl Standalone { .expect("private Owner Token is missing") } + #[allow(dead_code)] // Only the integration tests that publish a module use this. pub fn publish_module(&mut self) { let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); let module_dir = workspace.join("module"); @@ -163,6 +164,7 @@ impl Standalone { command.output().expect("failed to call reducer") } + #[allow(dead_code)] // Only the integration tests that call reducers use this. pub fn assert_call(&self, reducer: &str, args: &[&str]) { self.assert_ok(&self.call(reducer, args)); } From a99041b369ec888f8811d60382fe0ce940c18cb0 Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Thu, 3 Sep 2026 23:04:51 +0200 Subject: [PATCH 3/7] test: re-pin the relay-wiring scanners after the reformat rustfmt broke out the calls these scanners read, so the scanner now ignores the space after '(' and the trailing comma before ')', and two needles lose the closure braces rustfmt removed. --- gateway/src/stdb/subscriptions.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gateway/src/stdb/subscriptions.rs b/gateway/src/stdb/subscriptions.rs index c94250c96..d810796bc 100644 --- a/gateway/src/stdb/subscriptions.rs +++ b/gateway/src/stdb/subscriptions.rs @@ -5772,13 +5772,20 @@ mod tests { /// mutation-hardening [`scanned_source`] applies to the whole file (strip a `//` line comment /// FIRST, so a scan can't be satisfied by a comment quoting the pattern; collapse whitespace /// SECOND, so a rustfmt line-split can't fail a scan that pins a whole statement). + /// + /// The last step drops what a broken-out call leaves behind — the space after `(`, the trailing + /// comma before `)` — so a needle reads as one written call whichever way rustfmt wrapped it. fn decommented(text: &str) -> String { let stripped: String = text .lines() .map(|l| l.split("//").next().unwrap_or("")) .collect::>() .join("\n"); - stripped.split_whitespace().collect::>().join(" ") + let one_line = stripped.split_whitespace().collect::>().join(" "); + one_line + .replace("( ", "(") + .replace(", )", ")") + .replace(" )", ")") } /// The call-site tripwire for the shared-view `Viewer`'s construction. From 0bd2ac2a3ebb375d7de53b548789cf7e8590500c Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Thu, 3 Sep 2026 23:04:54 +0200 Subject: [PATCH 4/7] ci: make fmt and clippy blocking --- .github/workflows/rust.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f0df59482..7f921ac4d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,22 +18,17 @@ permissions: jobs: fmt: runs-on: ubuntu-latest - # Non-blocking: 61 files on main already fail this. Reformatting them is a separate change. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check formatting - continue-on-error: true run: cargo fmt --all -- --check clippy: runs-on: ubuntu-latest - # Non-blocking: lyracore-module, -gateway and -importer are red on main today. lyracore-shared - # and -package-delta are clean, so this flips to blocking once the other three are fixed. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - name: Run clippy - continue-on-error: true run: cargo clippy --workspace --all-targets --all-features -- -D warnings test: From 6324f6b81e720001048fa944bfe0994a65694ca5 Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Sat, 5 Sep 2026 08:14:53 +0200 Subject: [PATCH 5/7] fix(gateway): harden quality review checks --- gateway/src/stdb/subscriptions.rs | 101 +++++++++++++++++++++++---- gateway/src/stdb/world_view.rs | 1 + gateway/src/world/handlers/vendor.rs | 32 +++++++-- 3 files changed, 114 insertions(+), 20 deletions(-) diff --git a/gateway/src/stdb/subscriptions.rs b/gateway/src/stdb/subscriptions.rs index d810796bc..118e9fe33 100644 --- a/gateway/src/stdb/subscriptions.rs +++ b/gateway/src/stdb/subscriptions.rs @@ -5712,8 +5712,72 @@ mod tests { assert!(!instance_relay_gate(7, None)); } - /// The non-test half of this file as the source scans below may read it: `//` comments STRIPPED - /// and every whitespace run collapsed to one space. + /// Remove line and nested block comments while preserving ordinary string literals. + /// + /// This is deliberately limited to the Rust source spans scanned in this module. The scans need + /// quoted labels, so comment markers inside a quoted label remain part of the source. + fn without_comments(text: &str) -> String { + let mut output = String::with_capacity(text.len()); + let mut chars = text.char_indices().peekable(); + let (mut in_string, mut in_line_comment, mut block_depth) = (false, false, 0usize); + + while let Some((index, character)) = chars.next() { + if in_line_comment { + if character == '\n' { + in_line_comment = false; + output.push(character); + } + continue; + } + + if block_depth > 0 { + if character == '/' && text[index..].starts_with("/*") { + block_depth += 1; + chars.next(); + } else if character == '*' && text[index..].starts_with("*/") { + block_depth -= 1; + chars.next(); + } else if character == '\n' { + output.push(character); + } + continue; + } + + if in_string { + output.push(character); + if character == '\\' { + if let Some((_, escaped)) = chars.next() { + output.push(escaped); + } + } else if character == '"' { + in_string = false; + } + continue; + } + + match character { + '"' => { + in_string = true; + output.push(character); + } + '/' if text[index..].starts_with("//") => { + in_line_comment = true; + output.push(' '); + } + '/' if text[index..].starts_with("/*") => { + block_depth = 1; + output.push(' '); + chars.next(); + } + _ => output.push(character), + } + } + + output + } + + /// The non-test half of this file as the source scans below may read it: comments stripped and + /// every whitespace run collapsed to one space. /// /// Both steps are mutation findings, not tidiness. Counting matches in the raw text made every /// scan below satisfiable by a COMMENT quoting the pattern while the real call was deleted — @@ -5728,11 +5792,7 @@ mod tests { .split("mod tests {") .next() .expect("the non-test half of this file"); - let decommented: String = body - .lines() - .map(|l| l.split("//").next().unwrap_or("")) - .collect::>() - .join("\n"); + let decommented = without_comments(body); decommented.split_whitespace().collect::>().join(" ") } @@ -5769,18 +5829,14 @@ mod tests { } /// Comment-stripped, whitespace-collapsed form of an arbitrary source span — the same two-step - /// mutation-hardening [`scanned_source`] applies to the whole file (strip a `//` line comment + /// mutation-hardening [`scanned_source`] applies to the whole file (strip comments /// FIRST, so a scan can't be satisfied by a comment quoting the pattern; collapse whitespace /// SECOND, so a rustfmt line-split can't fail a scan that pins a whole statement). /// /// The last step drops what a broken-out call leaves behind — the space after `(`, the trailing /// comma before `)` — so a needle reads as one written call whichever way rustfmt wrapped it. fn decommented(text: &str) -> String { - let stripped: String = text - .lines() - .map(|l| l.split("//").next().unwrap_or("")) - .collect::>() - .join("\n"); + let stripped = without_comments(text); let one_line = stripped.split_whitespace().collect::>().join(" "); one_line .replace("( ", "(") @@ -5788,6 +5844,25 @@ mod tests { .replace(" )", ")") } + #[test] + fn source_scans_ignore_block_commented_code() { + let source = r#" + wire_insert_live(db.live_table(), "live.label", &view, relay); + /* + wire_insert_live(db.decoy_table(), "decoy.label", &view, relay); + /* wire_insert_live(db.nested_decoy(), "nested.label", &view, relay); */ + */ + let label = "/* quoted label */"; + "#; + + let scanned = decommented(source); + + assert!(scanned.contains("wire_insert_live(db.live_table()")); + assert!(!scanned.contains("decoy_table")); + assert!(!scanned.contains("nested_decoy")); + assert!(scanned.contains("\"/* quoted label */\"")); + } + /// The call-site tripwire for the shared-view `Viewer`'s construction. /// /// The session's `Viewer` must hold the same dedup/gate state prepared during world entry. diff --git a/gateway/src/stdb/world_view.rs b/gateway/src/stdb/world_view.rs index 3d0a45663..2b6435d90 100644 --- a/gateway/src/stdb/world_view.rs +++ b/gateway/src/stdb/world_view.rs @@ -2350,6 +2350,7 @@ mod cell_audience_tests; #[cfg(test)] mod relay_bench; +#[allow(clippy::items_after_test_module)] #[cfg(test)] mod family_audience_tests { use super::{ diff --git a/gateway/src/world/handlers/vendor.rs b/gateway/src/world/handlers/vendor.rs index 37b573f4d..57c552668 100644 --- a/gateway/src/world/handlers/vendor.rs +++ b/gateway/src/world/handlers/vendor.rs @@ -436,8 +436,14 @@ mod tests { CMSG_REPAIR_ITEM, CMSG_SELL_ITEM, }; - /// (account_id, player_guid, vendor_guid, item_entry, count) as the handler passed them on. - type BuyRequest = (u64, u64, u64, u32, u32); + #[derive(Debug, Eq, PartialEq)] + struct BuyRequest { + account_id: u64, + actor_guid: u64, + vendor_guid: u64, + item_entry: u32, + count: u32, + } #[derive(Default)] struct InMemoryVendorActions { @@ -487,13 +493,13 @@ mod tests { item_entry: u32, count: u32, ) -> Result<()> { - self.buy_requests.lock().unwrap().push(( + self.buy_requests.lock().unwrap().push(BuyRequest { account_id, - self_guid, + actor_guid: self_guid, vendor_guid, item_entry, count, - )); + }); match &self.buy_error { Some(error) => Err(anyhow::anyhow!("{error}")), None => Ok(()), @@ -760,7 +766,13 @@ mod tests { ); assert_eq!( actions.buy_requests.lock().unwrap().as_slice(), - &[(7, 42, VENDOR, 2589, 3)] + &[BuyRequest { + account_id: 7, + actor_guid: 42, + vendor_guid: VENDOR, + item_entry: 2589, + count: 3, + }] ); } @@ -795,7 +807,13 @@ mod tests { assert_eq!( actions.buy_requests.lock().unwrap().as_slice(), - &[(7, 0, VENDOR, 2589, 1)] + &[BuyRequest { + account_id: 7, + actor_guid: 0, + vendor_guid: VENDOR, + item_entry: 2589, + count: 1, + }] ); } From e58957f493613c558412995ae4d481637dfc9510 Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Sat, 5 Sep 2026 08:38:46 +0200 Subject: [PATCH 6/7] test(gateway): align relay scanner normalization --- gateway/src/stdb/subscriptions.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gateway/src/stdb/subscriptions.rs b/gateway/src/stdb/subscriptions.rs index 118e9fe33..89c70df5c 100644 --- a/gateway/src/stdb/subscriptions.rs +++ b/gateway/src/stdb/subscriptions.rs @@ -5847,7 +5847,12 @@ mod tests { #[test] fn source_scans_ignore_block_commented_code() { let source = r#" - wire_insert_live(db.live_table(), "live.label", &view, relay); + wire_insert_live( + db.live_table(), + "live.label", + &view, + |v, row| relay(v, row), + ); /* wire_insert_live(db.decoy_table(), "decoy.label", &view, relay); /* wire_insert_live(db.nested_decoy(), "nested.label", &view, relay); */ @@ -5856,8 +5861,11 @@ mod tests { "#; let scanned = decommented(source); + let compact: String = scanned.chars().filter(|c| !c.is_whitespace()).collect(); - assert!(scanned.contains("wire_insert_live(db.live_table()")); + assert!(compact.contains( + "wire_insert_live(db.live_table(),\"live.label\",&view,|v,row|relay(v,row));" + )); assert!(!scanned.contains("decoy_table")); assert!(!scanned.contains("nested_decoy")); assert!(scanned.contains("\"/* quoted label */\"")); @@ -5941,11 +5949,11 @@ mod tests { let body = decommented(top_level_fn_body_of("world_view.rs", "arm_realm_private")); let compact: String = body.chars().filter(|c| !c.is_whitespace()).collect(); assert!( - compact.contains("wire_insert_live(db.game_whisper_event(),\"realm.game_whisper_event.insert\",&view,|v,row|whisper_appeared(v,row),);"), + compact.contains("wire_insert_live(db.game_whisper_event(),\"realm.game_whisper_event.insert\",&view,|v,row|whisper_appeared(v,row));"), "arm_realm_private no longer relays realm-core whispers through `whisper_appeared`" ); assert!( - compact.contains("wire_insert_live(db.game_group_event(),\"realm.game_group_event.insert\",&view,move|v,row|group_event_appeared(v,&coord,row),);"), + compact.contains("wire_insert_live(db.game_group_event(),\"realm.game_group_event.insert\",&view,move|v,row|group_event_appeared(v,&coord,row));"), "arm_realm_private no longer relays realm-core group events through \ `group_event_appeared` (which also carries the QUEST_SHARE detail JOIN through a \ WORLD handle — realm-core's cache has no quest catalogue)" From b24a5c2f06c141e31cd6fb7c5d2e2d2072fd19dc Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Sat, 5 Sep 2026 09:07:12 +0200 Subject: [PATCH 7/7] style: format the merged auction changes --- gateway/src/main.rs | 2 +- gateway/src/world/handlers/auction.rs | 5 +++-- gateway/src/world/tests.rs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 2bf123560..8725d0799 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -15,11 +15,11 @@ mod accept; mod codec; mod config; -mod fd_limit; #[cfg(test)] #[allow(dead_code)] // Shared commands are also used by Module integration tests. #[path = "../../module/tests/support/mod.rs"] mod durable_test_support; +mod fd_limit; mod load_sample; mod logon; mod movement_batch_metrics; diff --git a/gateway/src/world/handlers/auction.rs b/gateway/src/world/handlers/auction.rs index 772b457fc..149c779f8 100644 --- a/gateway/src/world/handlers/auction.rs +++ b/gateway/src/world/handlers/auction.rs @@ -462,8 +462,9 @@ pub(crate) fn dispatch_auction_action( other => return Ok(AuctionActionOutcome::PassThrough(other)), }; let auctioneer_guid = auctioneer.guid(); - let Some((player_guid, interaction)) = auction_actor_interaction(store, player, auctioneer_guid)? - .filter(|(_, interaction)| !interaction.refuses_interaction) + let Some((player_guid, interaction)) = + auction_actor_interaction(store, player, auctioneer_guid)? + .filter(|(_, interaction)| !interaction.refuses_interaction) else { return Ok(AuctionActionOutcome::Handled { outbound: Vec::new(), diff --git a/gateway/src/world/tests.rs b/gateway/src/world/tests.rs index c7a183783..e325b0d90 100644 --- a/gateway/src/world/tests.rs +++ b/gateway/src/world/tests.rs @@ -1,6 +1,6 @@ use super::handlers::{ - AuctionActionStore, AuctionInteraction, CastStore, DuelActionStore, - ItemActionStore, LootWindowRefusal, LootWindowRequestStatus, LootWindowStore, MeleeActionStore, + AuctionActionStore, AuctionInteraction, CastStore, DuelActionStore, ItemActionStore, + LootWindowRefusal, LootWindowRequestStatus, LootWindowStore, MeleeActionStore, QuestActionStore, TaxiActionStore, VendorActionStore, WeatherStore, }; use super::party::PartyOutcome;