Fix guard logic

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-17 16:59:02 +01:00
parent 68dfd1a444
commit a2f01da515
8 changed files with 406 additions and 236 deletions

View File

@@ -90,25 +90,25 @@ class WolfEngine {
///
/// Updates all world subsystems based on the [elapsed] time.
/// This should be called once per frame by the host application.
void tick(Duration elapsed) {
void tick(Duration delta) {
if (!isInitialized) return;
_timeAliveMs += elapsed.inMilliseconds;
// Trust the incoming delta time natively
_timeAliveMs += delta.inMilliseconds;
// 1. Process User Input
input.update();
final currentInput = input.currentInput;
final inputResult = _processInputs(elapsed, currentInput);
final inputResult = _processInputs(delta, currentInput);
// 2. Update Environment (Doors & Pushwalls)
doorManager.update(elapsed);
pushwallManager.update(elapsed, currentLevel);
// 2. Update Environment
doorManager.update(delta);
pushwallManager.update(delta, currentLevel);
// 3. Update Physics & Movement
player.tick(elapsed);
player.tick(delta);
player.angle += inputResult.dAngle;
// Normalize angle to [0, 2π]
if (player.angle < 0) player.angle += 2 * math.pi;
if (player.angle >= 2 * math.pi) player.angle -= 2 * math.pi;
@@ -120,8 +120,8 @@ class WolfEngine {
player.x = validatedPos.x;
player.y = validatedPos.y;
// 4. Update Dynamic World (Enemies & Combat)
_updateEntities(elapsed);
// 4. Update Dynamic World
_updateEntities(delta);
player.updateWeapon(
currentTime: _timeAliveMs,
@@ -213,11 +213,15 @@ class WolfEngine {
/// Translates [EngineInput] into movement vectors and rotation.
({Coordinate2D movement, double dAngle}) _processInputs(
Duration elapsed,
Duration delta,
EngineInput input,
) {
const double moveSpeed = 0.14;
const double turnSpeed = 0.10;
// Standardize movement to 60 FPS (16.66ms per frame)
final double timeScale = delta.inMilliseconds / 16.666;
// Apply the timeScale multiplier to ensure consistent speed at any framerate
double moveSpeed = 0.14 * timeScale;
double turnSpeed = 0.10 * timeScale;
Coordinate2D movement = const Coordinate2D(0, 0);
double dAngle = 0.0;
@@ -229,7 +233,6 @@ class WolfEngine {
if (input.isFiring) {
player.fire(_timeAliveMs);
// Throttle the acoustic flood-fill to emit a "wave" every 400ms while firing
if (_timeAliveMs - _lastAcousticAlertTime > 400) {
_propagateGunfire();
_lastAcousticAlertTime = _timeAliveMs;
@@ -248,14 +251,12 @@ class WolfEngine {
if (input.isMovingForward) movement += forwardVec * moveSpeed;
if (input.isMovingBackward) movement -= forwardVec * moveSpeed;
// Handle Wall Interactions (Switches, Doors, Secret Walls)
if (input.isInteracting) {
int targetX = (player.x + math.cos(player.angle)).toInt();
int targetY = (player.y + math.sin(player.angle)).toInt();
if (targetX >= 0 && targetX < 64 && targetY >= 0 && targetY < 64) {
int wallId = currentLevel[targetY][targetX];
// Handle Elevator Switches
if (wallId == MapObject.normalElevatorSwitch) {
_onLevelCompleted(isSecretExit: false);
return (movement: const Coordinate2D(0, 0), dAngle: 0.0);
@@ -334,15 +335,29 @@ class WolfEngine {
// Standard AI Update cycle
final intent = entity.update(
elapsedMs: _timeAliveMs,
elapsedDeltaMs: elapsed.inMilliseconds,
playerPosition: player.position,
isWalkable: isWalkable,
tryOpenDoor: doorManager.tryOpenDoor,
onDamagePlayer: (int damage) => player.takeDamage(damage),
);
// Scale the enemy's movement intent to prevent super-speed on 90Hz/120Hz displays
double timeScale = elapsed.inMilliseconds / 16.666;
Coordinate2D scaledMovement = intent.movement * timeScale;
Coordinate2D safeMovement = _clampMovement(scaledMovement);
entity.angle = intent.newAngle;
entity.x += intent.movement.x;
entity.y += intent.movement.y;
// Final sanity check: only move if the destination is on-map
double nextX = entity.x + safeMovement.x;
double nextY = entity.y + safeMovement.y;
if (nextX >= 0 && nextX < 64 && nextY >= 0 && nextY < 64) {
entity.x = nextX;
entity.y = nextY;
}
// Handle Item Drops
if (entity.state == EntityState.dead &&
@@ -439,11 +454,29 @@ class WolfEngine {
/// Returns true if a tile is empty or contains a door that is sufficiently open.
bool isWalkable(int x, int y) {
// 1. Boundary Guard: Prevent range errors by checking if coordinates are on the map
if (x < 0 || x >= 64 || y < 0 || y >= 64) {
return false; // Out of bounds is never walkable
}
if (currentLevel[y][x] == 0) return true;
if (currentLevel[y][x] >= 90) return doorManager.isDoorOpenEnough(x, y);
return false;
}
/// Clamps movement to a maximum of 0.2 tiles per step to prevent wall-clipping
/// and map-boundary jumps during low framerate/high delta spikes.
Coordinate2D _clampMovement(Coordinate2D intent) {
const double maxStep = 0.2;
double length = math.sqrt(intent.x * intent.x + intent.y * intent.y);
if (length > maxStep) {
double scale = maxStep / length;
return Coordinate2D(intent.x * scale, intent.y * scale);
}
return intent;
}
/// Teleports the player to the nearest empty tile if they spawn inside a wall.
void _bumpPlayerIfStuck() {
int pX = player.x.toInt();