poe2-bot/docs/engine-and-systems.md
2026-03-07 09:53:57 -05:00

6.9 KiB

Nexus.Engine & Nexus.Systems — Bot Brain

BotEngine (Orchestrator)

The main loop. Owns systems, navigation, profiles, and action execution.

Logic Loop (25Hz, background thread)

1. Wait for MemoryPoller to provide latest GameState
2. CheckCharacterProfile() → auto-load profile if character changed
3. GameStateEnricher.Enrich() → compute NearestEnemies, ThreatMap, DangerLevel
4. Clear ActionQueue
5. NavigationController.Update(state) → compute path, set DesiredDirection
6. Run all ISystem implementations in priority order
7. NavigationSystem submits MoveAction if DesiredDirection is set
8. ActionQueue.Resolve() → merge conflicts
9. ExecuteActions() → emit key/mouse/click commands via IInputController

Action Execution

Action Execution
MoveAction Direction → MovementKeyTracker → WASD key state changes (delta-based)
CastAction SmoothMoveTo target + key press. Re-projects moving entities. Adds ±30-50px jitter.
FlaskAction Direct key press
KeyAction Press/Down/Up operations
ClickAction Left/Right/Middle click at screen position

MovementKeyTracker

Converts world-space direction vectors to WASD keys for isometric camera:

1. Rotate direction 45° to align with isometric axes
2. sx = dir.X * cos(45°) - dir.Y * sin(45°)
3. sy = dir.X * sin(45°) + dir.Y * cos(45°)
4. W if sy > 0.3, S if sy < -0.3, D if sx > 0.3, A if sx < -0.3
5. Only emit key changes (delta-based — no redundant KeyDown/KeyUp)

Mouse Drift (Navigation)

During navigation, lazily repositions the mouse toward enemy clusters:

  • Projects enemy centroid ahead of player movement
  • Applies ±25° angular offset for organic appearance
  • Fires every 800-1500ms (randomized)

Safety

  • Releases all held keys when loading screen or escape menu detected
  • CombatSystem's ReleaseAllHeld() called on state transitions

Systems

ThreatSystem (Priority 50)

Emergency threat response. Runs first, only acts on elevated danger.

Danger Response
Safe / Low No action
Medium No action (MovementSystem handles soft avoidance)
High Flee toward safety (priority 50, allows casting)
Critical or point-blank (<150 units) Urgent flee (priority 5) — blocks all casting

Flee direction: Player.Position - ThreatCentroid, validated against terrain via FindWalkableDirection().

MovementSystem (Priority 100)

Continuous soft avoidance via inverse-square repulsion field.

For each hostile monster within SafeDistance (400 units):

force += (playerPos - enemyPos) / distanceSquared * RepulsionWeight

Normalizes sum, validates against terrain, submits as lower-priority MoveAction.

Effect: Player gently drifts away from enemies without hard fleeing.

AreaProgressionSystem (Priority 199)

High-level area traversal. Runs before NavigationSystem to take precedence.

State machine (7 phases):

Exploring → Looting → NavigatingToChest → InteractingChest →
NavigatingToTransition → Interacting → TalkingToNpc

Exploration strategy:

  1. Check for elite enemies (Rare/Unique within 800u) → yield to combat
  2. Check for quest chests → navigate and interact
  3. Check for loot (if danger ≤ Low) → pick up within 600u
  4. Once fully explored → find area transition matching quest target
  5. In towns with active quest → talk to NPC

Quest integration: Queries active quests for target areas. Prioritizes tracked quests, then lowest act, then shortest path. Blacklists failed transitions after 5s timeout.

Navigation delegation: Uses NavigationController.NavigateToEntity() and .Explore(). Sets targets and yields until reached.

NavigationSystem (Priority 200)

Ultra-thin passthrough. If NavigationController.DesiredDirection is set, submits a MoveAction. All actual pathfinding logic lives in NavigationController (see pathfinding.md).

CombatSystem (Priority 300)

Skill rotation and target selection. Hot-swappable via CharacterProfile.

Rotation loop:

1. Check global cooldown (skip if recently cast)
2. For each skill in priority order:
   a. Check per-skill cooldown
   b. Match skill to memory via slot index (fallback to name)
   c. If aura: cast once per zone
   d. If damage: find target → submit CastAction
3. Release held keys for skills without valid targets

Target selection pipeline:

1. Filter by TargetSelection (Nearest, Rarest, MagicPlus, RarePlus, UniqueOnly)
2. Filter by range (SkillProfile.RangeMin/RangeMax)
3. Filter by line-of-sight (terrain query)
4. Check MinMonstersInRange (AOE threshold)
5. Pick best: Rarest mode → prefer higher rarity then nearer; others → nearest
6. Project to screen coordinates

Skill input types:

  • LeftClick/RightClick/MiddleClick: Direct click at target position
  • KeyPress with MaintainPressed: Hold key continuously
  • KeyPress normal: Single tap

Kiting/orbit (during global cooldown):

  • Computes enemy centroid
  • Moves perpendicular to centroid (orbital movement)
  • Applies radial bias to maintain ideal distance
  • Flips orbit direction if terrain blocks path
  • Persists orbit sign across ticks for smooth motion

Cooldown management:

  • Per-skill: max(skill.CooldownMs, globalCd + 50) for rotation
  • MaintainPressed skills: use skill.CooldownMs directly
  • Area reset: clears aura tracking, resets orbit

ResourceSystem (Priority 400)

Flask automation based on life/mana thresholds.

if LifePercent < LifeFlaskThreshold (50%) && cooldown expired → FlaskAction
if ManaPercent < ManaFlaskThreshold (50%) && cooldown expired → FlaskAction

Flask cooldown: 4000ms default. Hot-swappable on character profile change.

LootSystem (Priority 500)

Stub — disabled by default. Item pickup logic handled by AreaProgressionSystem's looting phase.


System Interaction Diagram

GameState (read-only, shared)
    │
    ├─→ ThreatSystem ──→ MoveAction (priority 5 or 50)
    │                     [blocks casting if priority ≤ 10]
    │
    ├─→ MovementSystem ──→ MoveAction (priority 100)
    │                      [soft repulsion, overridable]
    │
    ├─→ AreaProgressionSystem ──→ NavigateTo/Explore commands
    │                             [drives NavigationController]
    │
    ├─→ NavigationSystem ──→ MoveAction (priority 200)
    │                        [passthrough from NavigationController]
    │
    ├─→ CombatSystem ──→ CastAction (priority 300)
    │                    [skill rotation + target selection]
    │
    ├─→ ResourceSystem ──→ FlaskAction (priority 400)
    │                      [always passes through]
    │
    └─→ ActionQueue.Resolve()
         │
         ├── Highest MoveAction wins
         ├── CastAction passes unless blocked by urgent flee
         ├── FlaskAction always passes
         └──→ ExecuteActions() → IInputController