Make combat reaction state rollback-safe (#34)
CI / build-and-test (push) Successful in 13m10s

## Summary

- replace the per-fighter received-attack inbox with a typed rollback-registered pending-hit resource
- split combat reaction resolution into its own attack module
- remap entity references during rollback and include pending hits in debug save states

Fixes #21

Reviewed-on: #34
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #34.
This commit is contained in:
2026-09-16 17:50:40 +00:00
committed by EIREXE
parent 352c6fcb6c
commit 43ee6d8153
4 changed files with 195 additions and 163 deletions
+6 -5
View File
@@ -8,8 +8,8 @@ use crate::{
FighterVelocity, Grounded,
animation::FighterAnimationFrame,
attack::{
FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction,
FighterReceivedAttacks, FighterSolvedHurtboxes, FighterStaleMoveQueue,
FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction, FighterPendingHits,
FighterSolvedHurtboxes, FighterStaleMoveQueue,
},
baked_animation::FighterBoneMatrices,
state::FighterState,
@@ -28,6 +28,7 @@ pub(super) struct DebugSaveState {
struct Checkpoint {
fighters: Vec<FighterCheckpoint>,
stage_collision: StageCollision,
pending_hits: FighterPendingHits,
}
#[derive(Clone)]
@@ -50,7 +51,6 @@ struct FighterCheckpoint {
knockback_velocity: FighterKnockbackVelocity,
stale_moves: FighterStaleMoveQueue,
hitboxes: FighterHitboxes,
received_attacks: FighterReceivedAttacks,
solved_hurtboxes: FighterSolvedHurtboxes,
bone_matrices: FighterBoneMatrices,
grounded: bool,
@@ -97,9 +97,11 @@ pub(super) fn save_or_restore(world: &mut World) {
};
checkpoint_fighters.push(checkpoint);
}
let pending_hits = world.resource::<FighterPendingHits>().clone();
world.resource_mut::<DebugSaveState>().checkpoint = Some(Checkpoint {
fighters: checkpoint_fighters,
stage_collision,
pending_hits,
});
info!("Saved debug game state");
return;
@@ -147,7 +149,6 @@ pub(super) fn save_or_restore(world: &mut World) {
entity.insert(saved.knockback_velocity);
entity.insert(saved.stale_moves.clone());
entity.insert(saved.hitboxes.clone());
entity.insert(saved.received_attacks.clone());
entity.insert(saved.solved_hurtboxes.clone());
entity.insert(saved.bone_matrices.clone());
if saved.grounded {
@@ -156,6 +157,7 @@ pub(super) fn save_or_restore(world: &mut World) {
entity.remove::<Grounded>();
}
}
world.insert_resource(checkpoint.pending_hits.clone());
world.insert_resource(checkpoint.stage_collision);
for mut match_camera in world.query::<&mut MatchCamera>().iter_mut(world) {
match_camera.needs_snap = true;
@@ -184,7 +186,6 @@ fn capture_fighter(world: &World, entity: Entity) -> Option<FighterCheckpoint> {
knockback_velocity: *fighter.get::<FighterKnockbackVelocity>()?,
stale_moves: fighter.get::<FighterStaleMoveQueue>()?.clone(),
hitboxes: fighter.get::<FighterHitboxes>()?.clone(),
received_attacks: fighter.get::<FighterReceivedAttacks>()?.clone(),
solved_hurtboxes: fighter.get::<FighterSolvedHurtboxes>()?.clone(),
bone_matrices: fighter.get::<FighterBoneMatrices>()?.clone(),
grounded: fighter.contains::<Grounded>(),