Added tests for validating enemy sprite ranges

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-17 17:30:56 +01:00
parent 8cb1ea8d9b
commit 815ca4a13e
4 changed files with 68 additions and 49 deletions

View File

@@ -59,8 +59,11 @@ class SpriteGallery extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Expanded( Expanded(
child: AspectRatio(
aspectRatio: 4 / 3,
child: WolfAssetPainter.sprite(sprites[index]), child: WolfAssetPainter.sprite(sprites[index]),
), ),
),
], ],
), ),
); );

View File

@@ -1,5 +1,4 @@
import 'package:wolf_3d_dart/src/data_types/sprite_frame_range.dart'; import 'package:wolf_3d_dart/src/data_types/sprite_frame_range.dart';
import 'package:wolf_3d_dart/src/entities/entities/enemies/enemy_type.dart';
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead } enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }
@@ -90,33 +89,12 @@ class EnemyAnimationMap {
return false; return false;
} }
void validateEnemyAnimations() { Map<EnemyAnimation, SpriteFrameRange> get allRanges => {
bool hasErrors = false; EnemyAnimation.idle: idle,
EnemyAnimation.walking: walking,
for (final enemy in EnemyType.values) { EnemyAnimation.attacking: attacking,
// 1. Check for internal overlaps (e.g., Guard walking overlaps Guard attacking) EnemyAnimation.pain: pain,
if (enemy.animations.hasInternalOverlaps()) { EnemyAnimation.dying: dying,
print( EnemyAnimation.dead: dead,
'❌ ERROR: ${enemy.name} has overlapping internal animation states!', };
);
hasErrors = true;
}
// 2. Check for external overlaps (e.g., Guard sprites overlap SS sprites)
for (final otherEnemy in EnemyType.values) {
if (enemy != otherEnemy && enemy.sharesFramesWith(otherEnemy)) {
print(
'❌ ERROR: ${enemy.name} shares sprite frames with ${otherEnemy.name}!',
);
hasErrors = true;
}
}
}
if (!hasErrors) {
print(
'✅ All enemy animations validated successfully! No overlaps found.',
);
}
}
} }

View File

@@ -172,22 +172,4 @@ enum EnemyType {
EnemyAnimation.dead => range.start, EnemyAnimation.dead => range.start,
}; };
} }
/// Checks if this enemy shares any sprite frames with [other].
bool sharesFramesWith(EnemyType other) {
return animations.overlapsWith(other.animations);
// * Usage example:
// void checkEnemyOverlaps() {
// for (final enemyA in EnemyType.values) {
// for (final enemyB in EnemyType.values) {
// if (enemyA != enemyB && enemyA.sharesFramesWith(enemyB)) {
// print(
// 'Warning: ${enemyA.name} and ${enemyB.name} have overlapping sprites!',
// );
// }
// }
// }
// }
}
} }

View File

@@ -0,0 +1,56 @@
import 'dart:math' as math;
import 'package:test/test.dart';
import 'package:wolf_3d_dart/src/entities/entities/enemies/enemy_type.dart';
void main() {
group('Enemy Sprite Range Validation', () {
test('No enemy animations should have overlapping sprite indices', () {
final allEnemies = EnemyType.values;
final overlaps = <String>[];
for (int i = 0; i < allEnemies.length; i++) {
final enemyA = allEnemies[i];
final animationsA = enemyA.animations.allRanges;
for (int j = i; j < allEnemies.length; j++) {
final enemyB = allEnemies[j];
final animationsB = enemyB.animations.allRanges;
animationsA.forEach((animA, rangeA) {
animationsB.forEach((animB, rangeB) {
// Skip if we are comparing the exact same animation on the same enemy
if (enemyA == enemyB && animA == animB) return;
if (rangeA.overlaps(rangeB)) {
// Determine the specific frames that are clashing
final start = math.max(rangeA.start, rangeB.start);
final end = math.min(rangeA.end, rangeB.end);
final clashingFrames = <int>[];
for (int f = start; f <= end; f++) {
if (rangeA.contains(f) && rangeB.contains(f)) {
clashingFrames.add(f);
}
}
if (clashingFrames.isNotEmpty) {
overlaps.add(
'${enemyA.name}.${animA.name} overlaps ${enemyB.name}.${animB.name} on frames: $clashingFrames',
);
}
}
});
});
}
}
// Assert that the list of overlaps is empty
expect(
overlaps,
isEmpty,
reason: 'Detected sprite index collisions:\n${overlaps.join('\n')}',
);
});
});
}