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

197 lines
6.9 KiB
Markdown

# 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](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
```