Skip to main content

Combat System

Hyperscape implements a tick-based combat system inspired by Old School RuneScape. Combat operates on discrete 600ms ticks, with authentic damage formulas, accuracy rolls, and attack styles.
Combat code lives in packages/shared/src/systems/shared/combat/ and uses constants from packages/shared/src/constants/CombatConstants.ts.

Core Constants

From CombatConstants.ts:

Mob Combat

Mobs can use any of the three attack types: melee, ranged, or magic. This is configured in the NPC manifest and fully integrated with the combat system.

Mob Attack Types

Configure mob attack types via NPC manifest JSON:
Attack Type Configuration:
  • attackType: "melee" (default), "ranged", or "magic"
  • spellId: Required for magic mobs (e.g., "wind_strike", "fire_bolt")
  • arrowId: Required for ranged mobs (e.g., "bronze_arrow", "iron_arrow")
  • heldWeaponModel: Optional visual weapon GLB (bow, staff, etc.)
  • combatRange: Attack range in tiles (1 for melee, 7-10 for ranged/magic)
  • attackSpeedTicks: Ticks between attacks (4-5 typical)

Mob Combat Mechanics

Mobs use the same combat handlers as players but with simplified resource management: Mob Magic Attacks:
  • Use mob’s magic stat for damage calculation
  • No rune consumption (infinite resources)
  • Emit spell projectiles with correct visual effects (element-based colors)
  • Play SPELL_CAST animation
  • Spell launch delay: 600ms (allows cast animation wind-up)
  • Hit delay formula: 1 + floor((1 + distance) / 3) ticks
Mob Ranged Attacks:
  • Use mob’s ranged stat for damage calculation
  • No arrow consumption (infinite resources)
  • Emit arrow projectiles with correct visuals (metal-tipped arrows)
  • Play RANGE animation
  • Arrow launch delay: 400ms (allows draw animation wind-up)
  • Hit delay formula: 1 + floor((3 + distance) / 6) ticks
Mob Melee Attacks:
  • Use mob’s attack and strength stats
  • Standard melee range (1 tile) or custom combatRange
  • Play COMBAT or SWORD_SWING animation
  • Immediate hit (0 tick delay)

Held Weapon Visuals

Mobs can display held weapons (bows, staves) using the same attachment system as player equipment:
Weapon Cache System:
  • Static _weaponCache shares loaded GLB scenes across mob instances
  • _pendingLoads deduplicates concurrent fetches for the same URL
  • clone(true) creates per-mob instances with shared geometry/materials
  • Cache cleared on world teardown via MobNPCSpawnerSystem.destroy()
  • Prevents duplicate network requests and GPU memory waste
  • Proper cleanup: weapons removed from parent on mob destroy (geometry shared, not disposed)
Attachment Metadata: Weapons use Asset Forge export metadata for bone attachment:
  • vrmBoneName: Target bone (default: "rightHand")
  • version: Metadata format version (1 or 2)
  • relativeMatrix: Pre-baked 4×4 transform matrix (V2 format)
Supported Formats:
  • V1: Direct attachment to bone (simple position/rotation)
  • V2: Pre-baked matrix with EquipmentWrapper group (advanced positioning)

Mob Damage Calculation

Mobs use simplified damage formulas without equipment bonuses:
Key Differences from Player Combat:
  • Mobs have zero equipment bonuses (no attack/strength/defense from gear)
  • Mobs have infinite resources (no rune/arrow consumption)
  • Mobs use stats from NPC manifest (not dynamic equipment)
  • Players can still use prayer bonuses to defend against mob attacks
  • Damage formulas are otherwise identical to player formulas (OSRS-accurate)

Projectile Emission

Both magic and ranged attacks emit projectile events for client-side visuals:
Projectile Timing:
  • delayMs: Time after attack start before projectile appears (animation wind-up)
  • travelDurationMs: How long the projectile flies (derived from hit delay formula)
  • Visual arrival coincides with server-side damage splat
Launch Delays:
  • Spell projectiles: 600ms delay (allows staff raise and cast gesture)
  • Arrow projectiles: 400ms delay (allows bow draw animation)

Mob Retaliation

When a player attacks a ranged or magic mob, the mob retaliates with the correct attack type:

Attack Handler Routing

The combat system routes mob attacks through specialized handlers:
Dual Routing Paths:
  1. Initial attack (first hit when entering combat):
    • MobEntity.performAttackAction() emits COMBAT_MOB_NPC_ATTACK event
    • Event carries attackType, spellId, arrowId from mob config
    • CombatSystem.handleMobAttack() routes to appropriate handler
    • Handler validates, calculates damage, emits projectile
    • Calls enterCombat() with weaponType to store attack type
  2. Auto-attack ticks (subsequent attacks while in combat):
    • CombatTickProcessor.processAutoAttackOnTick() reads combatState.weaponType
    • Routes through CombatSystem.handleAttack() to appropriate handler
    • Handler resolves spellId/arrowId from NPC data via getNPCById()
    • No event data needed—attack type persisted in combat state
Both paths converge on the same handlers (MagicAttackHandler, RangedAttackHandler, MeleeAttackHandler), ensuring consistent behavior.

Shared Attack Preparation

The prepareMobAttack() utility in AttackContext.ts handles common validation for mob projectile attacks:
Validation Steps:
  1. Entity Resolution: Resolve attacker and target entities (or use preResolved to avoid double lookup)
  2. Alive Check: Verify both entities are alive
  3. NPC Data Lookup: Get mob configuration from getNPCById(mobData.type)
  4. Range Validation: Check distance using checkProjectileRange() with mob’s combatRange
  5. Position Validation: Get entity positions via getEntityPosition()
  6. Cooldown Check: Verify attack is not on cooldown
  7. Cooldown Claim: Set nextAttackTicks to prevent rapid-fire attacks
  8. Face Target: Rotate mob to face target via rotationManager
  9. Play Animation: Trigger attack animation via animationManager
Return Value:
  • Returns MobAttackContext with all validated state if all checks pass
  • Returns null if any check fails (attack aborted, no error thrown)
Performance Optimization: The preResolved parameter allows handlers to pass already-resolved mob entity and NPC data, avoiding redundant entityResolver.resolve() and getNPCById() calls when the handler needs this data for spell/arrow validation before calling prepareMobAttack().

Session Interruption

Combat Closes Bank/Store/Dialogue

When a player is attacked, all interaction sessions are automatically closed (OSRS-accurate behavior):
Session Close Reasons:
  • user_action — Player explicitly closed UI
  • distance — Player moved too far from target
  • disconnect — Player disconnected
  • new_session — Replaced by new session
  • target_gone — Target entity no longer exists
  • combat — Player was attacked (OSRS-style)
OSRS-Accurate: Even a splash attack (0 damage) closes the bank/store/dialogue. Being in combat matters, not just taking damage.

Food Consumption & Combat

Eat Delay Mechanics

Food consumption integrates with the combat system using OSRS-accurate timing:
OSRS Rules:
  • 3-tick delay between eating (1.8 seconds)
  • Food consumed even at full health
  • Attack delay only added if already on cooldown
  • If weapon is ready to attack, eating does NOT add delay

Attack Delay Integration

When eating during combat, the system checks if the player is on attack cooldown:
Example Scenario:
  1. Player attacks with longsword (4-tick weapon)
  2. Attack lands at tick 100, next attack at tick 104
  3. Player eats at tick 102 (while on cooldown)
  4. Eat delay adds 3 ticks: next attack now at tick 107
  5. If player eats at tick 104+ (weapon ready), no delay added

Healing Formula

Healing is capped and validated server-side:

Combat Styles

Combat styles determine which skill gains XP and provide stat bonuses.

Melee Styles

Ranged Styles

Magic Styles

OSRS-Accurate: Staves and wands have both melee and magic combat styles. When used without a spell selected, they function as crush weapons and grant melee XP.
Staff/Wand Melee Styles (no spell selected): Staff/Wand Magic Styles (with spell selected):
XP Grant Logic:
Autocast Panel Behavior: When selecting the “autocast” style, the Spells panel automatically opens for spell selection:

Damage Calculation

Damage uses the authentic OSRS formula from the wiki.

Per-Style Combat Bonuses

The armor system implements OSRS-accurate per-style attack and defense bonuses: Melee Attack Styles:
  • Stab: Daggers, spears (piercing attacks)
  • Slash: Swords, scimitars, axes (slicing attacks)
  • Crush: Maces, unarmed (blunt attacks)
Weapon Default Styles: Armor Defense Bonuses: Each armor piece provides separate defense values for each attack style:
Combat Triangle:
  • Melee Armor: High stab/slash/crush/ranged defense, negative magic bonuses
  • Ranged Armor: Positive ranged/magic defense, lower melee defense
  • Magic Armor: Positive magic attack/defense, minimal physical defense
The combat system automatically selects the appropriate attack/defense bonus based on weapon type. For example, a sword attack uses attackSlash vs. the defender’s defenseSlash.

Maximum Hit Formula

Prayer bonuses are applied before the effective level calculation, matching OSRS mechanics.

Accuracy Formula (Per-Style)

Per-Style Bonus Helpers:
Prayer bonuses apply to both attacker and defender, affecting accuracy and defense rolls. Per-style bonuses fall back to generic bonuses for backward compatibility.

Damage Roll


Ranged Combat

Ranged combat uses bows and arrows with OSRS-accurate mechanics.

Requirements

  • Bow equipped in weapon slot
  • Arrows equipped in ammo slot
  • Sufficient Ranged level for bow

Available Bows (F2P)

Available Arrows (F2P)

Ranged Damage Formula

Projectile System

Ranged attacks create projectiles with OSRS-accurate hit delays:
Projectile Rendering:
  • 3D arrow meshes with metal-colored tips
  • Arc trajectory (straight line, no gravity arc in F2P)
  • Rotates to face travel direction
  • Delayed hit based on distance
Arrow Spawn Position: Arrow projectiles spawn offset from the attacker’s center to the bow position for natural appearance:
Before Fix: Arrows spawned at center of attacker model (appeared to come from inside player’s head) After Fix: Arrows spawn 1.2 units forward and at upper torso level for natural bow-firing appearance

Ammunition Consumption

Arrows are consumed on every shot:
Arrows are NOT recoverable. Each shot consumes 1 arrow permanently.

Ranged XP


Magic Combat

Magic combat allows spellcasting with or without a staff.

Requirements

  • Magic level sufficient for the spell
  • Runes in inventory (or infinite from elemental staff)
  • Spell selected for autocast (optional)
OSRS-Accurate: You can cast spells without a staff. The staff provides magic attack bonus and infinite runes for its element.

Available Spells (F2P)

Strike Tier (Levels 1-13): Bolt Tier (Levels 17-35):

Elemental Staves

Staves provide infinite runes for their element:

Magic Damage Formula

Key Difference: Magic defense for players uses 70% Magic level + 30% Defense level. NPCs only use Magic level.

Rune Consumption

Runes are consumed on each cast:

Autocast

Players can select a spell for autocast:
Autocast Behavior:
  • Selected spell automatically casts when attacking
  • Spell selection persists across sessions (saved in database)
  • Cleared when weapon is changed
  • Shown in Spells panel with checkmark

Spell Projectiles

Magic spells render as multi-layer billboard meshes with WebGPU-compatible DataTextures:
Multi-Layer Projectile Structure: Each spell projectile consists of multiple billboard meshes:
  1. Outer Glow (Layer 1): Soft, semi-transparent, 2.5× size
  2. Core Orb (Layer 2): Bright center, sharp glow
  3. Orbiting Sparks (Layer 3, bolt-tier only): 2 tiny particles that orbit the core
Impact Burst Particles: When a spell hits its target, 4-6 particles burst outward:
WebGPU Compatibility: The projectile system uses DataTextures with color baked directly into pixels (not via material.color tinting) for reliable rendering in WebGPU:
Textures are cached and shared across projectiles for memory efficiency. Hit Delay:

Magic XP


Attack Type Detection

The combat system automatically detects attack type from equipped weapon or selected spell:
Spell Priority: If a spell is selected for autocast, the attack type is MAGIC regardless of equipped weapon. This allows staffless casting (OSRS-accurate).

Attack Range System

Melee Range

OSRS Accuracy: Standard melee (range 1) can only attack in cardinal directions (N/S/E/W). Diagonal attacks require range 2+ weapons like halberds.

Combat Pathfinding

Combat movement uses multi-destination BFS with line-of-sight validation for OSRS-accurate pathfinding:
Key Features:
  • BFS as primary pathfinder: Player movement uses BFS (“smartpathing”) instead of naive diagonal-first
  • Multi-destination search: Generates all valid attack tiles and finds shortest path to any of them
  • Line of sight: Ranged/magic attacks verify LoS via Bresenham line trace against BLOCKS_RANGED collision flags
  • Optimal pathing: Naturally selects the closest reachable attack position
  • Obstacle handling: Automatically routes around walls and blocked tiles
Line of Sight Check:
Collision Masks:
Breaking Change (PR #886): Ranged and magic attacks now require line of sight. You cannot attack through walls even if the target is within Chebyshev range.
Benefits:
  • Eliminates visible diagonal zigzag when walking to attack targets
  • Prevents attacking through walls when in Chebyshev range
  • Finds optimal path to closest reachable attack position
  • Matches OSRS smartpathing behavior for players
NPC Chase Pathfinding: NPCs still use naive diagonal pathing (not BFS) to enable safespotting:
This intentional difference allows players to safespot mobs by positioning themselves where naive pathing cannot reach, matching OSRS mechanics.

Combat Follow System

OSRS-Accurate: Players continuously follow their combat target while in range, not just when out of range. This prevents the stutter pattern where players stand still until the target moves away.
The combat system tracks target movement and maintains pursuit even when in attack range:
Benefits:
  • Smooth pursuit of moving targets
  • Zero-delay response when target leaves range
  • Pre-computed pathfinding for responsive gameplay
  • Matches OSRS behavior where players “stick” to their target
Cleanup: The lastCombatTargetTile map is automatically cleaned up when:
  • Combat ends naturally (timeout)
  • Player disengages from combat
  • Combat is forcibly stopped

Combat Rotation System

Players automatically face their combat target during ranged and magic attacks:
Rotation Clearing: Rotation tracking is cleared when:
  • Combat target dies
  • Player starts moving (movement takes priority)
  • Combat ends or is disengaged
This prevents players from continuing to face dead targets or old combat directions.

Ranged Combat

Ranged attacks use Chebyshev distance and require:
  • A ranged weapon (bow)
  • Ammunition (arrows)
Arrow Visuals: Arrows are rendered as 3D meshes with metal-colored tips and wooden shafts:
Arrow Size Reduction: Arrow projectiles were reduced in size (length: 0.6 → 0.35, width: 0.15 → 0.08) to better match the scale of player models and improve visual clarity during combat. Bow Models: All bow items now display 3D bow models when equipped:
  • Shortbow, Oak Shortbow, Willow Shortbow, Maple Shortbow
  • Models are properly rigged to the player’s hand bones
  • Bows are visible during idle, walking, and combat animations

Attack Speed & Cooldowns

Attacks occur on tick boundaries with weapon-specific speeds.

Weapon Speed Examples


Aggro System

NPCs have configurable aggression behaviors.

Aggro Types

Aggro Constants

Aggro Logic


Death Mechanics

Player Death

When a player dies:
  1. Headstone spawns at death location
  2. Items drop to headstone (kept for 15 minutes)
  3. Player respawns at starter town
  4. 3 most valuable items are kept (Protect Item prayer adds 1)

Duel Death Handling

Duel deaths use individual try/catch blocks to prevent one player’s failure from affecting the other:
Critical Fix (PR #875): Both restorePlayerHealth calls were previously in a single try/catch. If the winner’s restore triggered an exception in any PLAYER_RESPAWNED handler, the loser’s restore was skipped—leaving them with:
  • Frozen physics (isDying=true)
  • No playerRespawned/playerSetDead packets sent
  • Client unable to act on playerTeleport (which still arrived)
  • Player stuck in arena with death animation
Now each restore is individually wrapped, matching the pattern already used for teleports. This ensures both players are properly restored even if one fails.

Mob Death

When a mob dies:
  1. Loot drops based on drop table
  2. XP granted to all attackers
  3. Respawn timer starts (based on mob type)
  4. Entity destroyed after death animation

XP Distribution

XP is granted based on damage dealt and combat style.

Melee XP

Ranged XP

Magic XP

Magic grants XP even on a splash (0 damage). You still get the base spell XP, just no damage bonus.

PvP XP Calculation

In player-versus-player combat (duels), XP is granted based on the actual weapon type used, not the player’s selected attack style:
Fixed in PR #875: Previously, PvP kills always used the player’s melee attack style, causing ranged attacks to incorrectly grant Strength XP. Now the system inspects the actual weapon type, matching the logic used for mob kills.

Food & Combat Interaction

Eating During Combat

When a player eats food while in combat, OSRS-accurate timing rules apply:

Eat Delay Mechanics

Players cannot eat again until the eat delay expires:
OSRS-Accurate: Food is consumed even at full health. The eat delay and attack delay apply regardless of current HP.

Attack Delay API

The CombatSystem provides methods for eat delay integration:

Combat Events

The combat system emits events for UI and logging:

Prayer Bonuses in Combat

Active prayers provide multipliers to combat stats. See Prayer System for complete details. Example Prayer Effects:
  • Clarity of Thought (Level 7): +5% Attack (1.05× multiplier)
  • Burst of Strength (Level 4): +5% Strength (1.05× multiplier)
  • Thick Skin (Level 1): +5% Defense (1.05× multiplier)
Bonus Application:
Multiple prayers of the same type do NOT stack. The system uses the highest multiplier for each stat.

Combat Visual Synchronization

Mob Combat Rotation

Mobs properly clear their combat rotation flag when movement starts to prevent stuck facing:
Before Fix: Mobs kept combat rotation after combat ended, appearing stuck facing old target direction After Fix: Combat rotation cleared on movement start, mobs return to normal AI-driven rotation

Damage Splat Positioning

Damage splats now use entity visual position instead of server position for accurate placement:
This prevents damage splats from appearing at the mob’s server position when the client-side visual is interpolating to a different location.

Movement Packet Optimization

The server sends emote on tileMovementEnd instead of redundant entityModified (PR #884):
Movement Cancellation: cancelMovement now sends tileMovementEnd so client TileInterpolator properly stops interpolating:
Before Fix: entityModified sent on cancel, client kept interpolating old path After Fix: tileMovementEnd sent, client properly stops interpolation

Duel Arena Combat

AI Combat Timing

The DuelCombatAI system manages automated agent duels with simplified attack logic (commit 51453da):
Key Changes (commit 51453da):
  • Removed redundant attack-speed tracking: The combat system’s auto-attack loop already drives attack cadence
  • Simplified AI logic: AI only re-engages when combat drops or target changes
  • Fixed 2H sword attacks: Previously, manual attack-speed tracking competed with the combat system’s auto-attack loop, causing attacks to be silently dropped (especially for slow weapons like 2H swords)
  • Added TWO_HAND_SWORD default style: Missing default attack style for two-handed swords added to WeaponStyleConfig.ts
Why This Works:
  • Combat system’s processAutoAttackOnTick handles all attack timing once combat is established
  • Calling executeAttack on every cooldown cycle creates a redundant second driver
  • Both drivers compete for the same cooldown slot, silently dropping attacks
  • AI now only calls executeAttack when combat needs to be (re-)established
Attack Style Configuration: All weapon types now have complete default attack style mappings in WeaponStyleConfig.ts:
This ensures all weapon types have a valid default style, preventing undefined behavior when AI agents or players use weapons without explicit style selection.

Teleport Suppression

Duel arena teleports can be suppressed to prevent visual effects during fight cleanup:
Use Cases:
  • Fight-start HP sync: restoreHealth() with quiet param skips PLAYER_RESPAWNED/PLAYER_SET_DEAD events
  • Proximity corrections: Teleports to fix position without visual disruption
  • Cleanup teleports: Return players to lobby without teleport animation

Cycle Cleanup Chaining

The duel scheduler chains cleanup → delay → new cycle via .finally() to prevent stale avatars:
Benefits:
  • Cleanup always teleports both agents (even if errors occur)
  • Delay ensures clean state before next cycle
  • Prevents stale avatars from previous fights

Countdown Overlay

The countdown overlay stays mounted 2.5s into FIGHTING phase with fade-out animation:

Arena Visuals

Fence Design:
  • Replaced solid walls with fence posts + rails for better visibility
  • Allows spectators to see into the arena
  • Maintains collision boundaries
Floor Textures:
  • Procedural sandstone tile pattern for OSRS medieval aesthetic
  • Each arena gets unique randomized texture with grout lines, color variation, and speckle noise
  • Canvas-generated at runtime (no texture files needed)
Lighting:
  • Lit torches with fire particles at all 4 corners of each arena
  • PointLights with flicker animation
  • “torch” glow preset (6 riseSpread particles per torch, tight 0.08 spread)

Health Bar Synchronization

Health bars are synchronized inline in handleEntityDamaged before every broadcast:
Benefits:
  • Health bars always reflect current HP
  • No race conditions between damage and HP updates
  • Spectators see accurate health in real-time

AI Agent Trash Talk System

The DuelCombatAI system includes an integrated trash talk feature that allows AI agents to taunt opponents during combat:

Trash Talk Triggers

Health Threshold Taunts: Triggered when HP crosses specific milestones (75%, 50%, 25%, 10%):
Ambient Periodic Taunts: Random taunts every 15-25 ticks to add personality:

LLM-Generated Taunts

When an Eliza runtime is available, trash talk uses the agent’s character personality:
LLM Timeout Handling: 3-second timeout with scripted fallback:

Scripted Fallback Taunts

When no LLM runtime is available or LLM calls fail, the system uses pre-written taunt pools:

Fire-and-Forget Architecture

All trash talk calls are background/non-blocking to prevent combat tick delays:
Key Features:
  • Never blocks combat tick loop
  • 8-second cooldown prevents spam
  • In-flight flag prevents overlapping LLM calls
  • Failures are silent (trash talk is optional flavor)

Combat Role System

The duel system supports three combat roles with automatic gear provisioning and weighted random selection (added in PR #933, Feb 2026): Combat Roles:
  • Melee (50% weight): Bronze weapons (longsword, scimitar, 2h sword)
  • Ranged (25% weight): Shortbow + bronze arrows (500 qty), uses “rapid” attack style
  • Mage (25% weight): Staff of air + wind strike autocast + runes (500 mind, 500 air)
Role Selection:
DuelCombatAI Adaptation:
  • Melee: Uses existing phase-based style switching (aggressive/controlled/defensive)
  • Ranged: Forces “rapid” style for faster attack speed (-1 tick), skips melee style switching
  • Mage: Skips style switching entirely (magic auto-casts via selectedSpell)
Gear Lifecycle:
  1. Pre-duel: Role selected → gear equipped → food filled → health restored
  2. During duel: Combat AI adapts behavior based on assigned role
  3. Post-duel: Gear removed → runes removed → food removed → health restored → teleport back
Gear Provisioning Methods:
  • equipMeleeWeapon(): Random bronze weapon from pool (longsword, scimitar, 2h sword)
  • equipRangedGear(): Shortbow + bronze arrows (500 qty)
  • equipMageGear(): Staff of air + wind strike autocast + runes (500 mind, 500 air)
  • cleanupAgentCombatSetup(): Unequips all combat gear, clears autocast, removes leftover runes
Weapon Pool Filtering: Only weapons with new models in swords/ directory are eligible for duel arenas:

Weapon Type Propagation (PR #934, commit 029456255, Feb 25, 2026)

The combat system now propagates weapon type through DuelOrchestrator into startCombat so correct attack speeds are used:
Critical Fixes (PR #934, commit 029456255, Feb 25, 2026): 1. Keep-Alive Re-Engagement (2H Sword Fix):
  • Problem: Entity data flags (inCombat, combatTarget) can be stale when CombatSystem’s internal state has timed out. Agents would stand idle instead of attacking.
  • Fix: DuelCombatAI now periodically re-engages every 5 ticks (~3s) as a keep-alive, even when entity flags show combat is active
  • Impact: 2H sword attacks now fire reliably, agents no longer idle during combat
2. Rune Inventory Readiness Polling:
  • Problem: Adding runes before inventory loaded from DB caused silent rune loss. getOrCreateInventory returned a disposable placeholder (not stored in Map).
  • Fix: Wait up to 2 seconds for inventorySystem.isInventoryReady(playerId) before adding runes
  • Impact: Mage agents now reliably receive runes, magic attacks work consistently
3. Combat Timeout Refresh:
  • Problem: Ranged/magic attacks didn’t refresh combat timeout, causing combat to expire after 16 ticks even during active fighting
  • Fix: Both CombatSystem and CombatTickProcessor now refresh combatEndTick after ranged/magic attacks
  • Impact: Combat stays active during ranged/magic fights, no premature timeout
4. PvP Zone Bypass for Streaming Duels:
  • Problem: Streaming duel agents couldn’t fight in safe zones due to PvP zone checks
  • Fix: Bypass PvP zone checks when entity.data.inStreamingDuel === true
  • Impact: Streaming duels work in any zone, not just wilderness
5. Safe Zone Aggro Block:
  • Problem: Hostile mobs would aggro and chase players in safe zones
  • Fix: AggroSystem now checks ZoneDetectionSystem.isSafeZone() before aggroing or chasing
  • Impact: Safe zones are truly safe, mobs won’t attack players there

Combat State Starvation Guard (PR #934)

The system now guards against state starvation from repeated startCombat resets on slow weapons:
Why This Matters:
  • createAttackerState replaces the state Map entry which resets nextAttackTick
  • For slow weapons (2H swords, attackSpeed 7), repeated re-engagement keeps pushing nextAttackTick forward
  • Auto-attack loop never reaches nextAttackTick (starvation pattern)
  • Guard prevents replacing valid combat state, allowing auto-attacks to fire

Critical Bug Fixes

Combat State Key Mismatch (Fixed in PR #933):
  • Issue: CombatStateService syncs abbreviated keys (data.c/data.ct) but getGameState() only read full keys (data.inCombat/data.combatTarget)
  • Impact: DuelCombatAI always saw inCombat=false and flooded executeAttack every tick instead of letting auto-attacks drive combat
  • Fix: EmbeddedHyperscapeService now reads both abbreviated and full keys:
  • File: packages/server/src/eliza/EmbeddedHyperscapeService.ts
  • Commit: 82ff784 (Feb 25, 2026)
Magic Attack TOCTOU Race (Fixed in PR #933):
  • Issue: Cooldown was checked early but claimed after async consumeRunesForSpell call. With the combat state bug flooding attacks, two concurrent invocations could both pass the cooldown check before either claimed it
  • Impact: Duplicate magic projectiles, double rune consumption
  • Fix: Moved cooldown claim and enterCombat before async rune consumption to close the race window
  • File: packages/shared/src/systems/shared/combat/handlers/MagicAttackHandler.ts
  • Commit: 82ff784 (Feb 25, 2026)
Duel Arena Terrain Sinking (Fixed in PR #911):
  • Issue: Players/agents were sinking ~0.4m into duel arena floors because flat zones were removed from the terrain system, causing getHeightAt() to return raw procedural terrain height instead of floor-level height
  • Impact: Players appeared to sink through arena floors, grass grew through floor surfaces
  • Fix: DuelArenaVisualsSystem now registers flat zones programmatically for all 8 floor areas (6 arenas + lobby + hospital) so terrain height queries return correct floor-level values
  • File: packages/shared/src/systems/client/DuelArenaVisualsSystem.ts
  • Commit: 7a60135 (Feb 25, 2026)
Duel Arena Click Targeting (Fixed in commit 24354238):
  • Issue: Click targets were going underground in duel arenas due to building footprint validation rejecting arena floor raycast hits
  • Impact: Players couldn’t click to move within duel arenas
  • Fix: RaycastService now skips building footprint validation for arena-floor raycast hits
  • File: packages/shared/src/systems/client/interaction/services/RaycastService.ts
  • Commit: 2435423 (Feb 24, 2026)
Duel Arena Minimap Rendering (Fixed in commit 24354238):
  • Issue: Minimap showed duel arenas as black holes because arena/lobby/hospital floor meshes had layer 0 disabled
  • Impact: Minimap was unusable in duel arena area
  • Fix: Enabled layer 0 on arena/lobby/hospital floor meshes so minimap camera can render them
  • File: packages/shared/src/systems/client/DuelArenaVisualsSystem.ts
  • Commit: 2435423 (Feb 24, 2026)
Wall Sconce Cleanup (commit 24354238):
  • Issue: 96 dead wall sconce meshes across 6 arenas with no lights attached
  • Impact: Unnecessary geometry in scene, no visual benefit
  • Fix: Removed non-functional wall sconce geometry from arena fences
  • File: packages/shared/src/systems/client/DuelArenaVisualsSystem.ts
  • Commit: 2435423 (Feb 24, 2026)

Integration with DuelOrchestrator

The trash talk system is wired into the combat AI via a callback:
Social System Update: CHAT_MESSAGE action now allowed during combat (previously blocked).