6.7 KiB
6.7 KiB
Nexus.Simulator — Standalone Game World
Purpose
Test bot systems (combat, navigation, threat assessment) without the real game. Replaces the memory-reading pipeline with a procedural game world. Bot systems run unmodified — they see identical GameState objects and emit actions to IInputController.
Architecture
SimWorld (game tick loop)
│
├── SimPoller (60Hz background thread)
│ ├── FlushToWorld() → transfer input to SimWorld
│ ├── Tick(dt) → advance simulation
│ ├── SimStateBuilder.Build() → SimWorld → GameState
│ └── Push to GameDataCache
│
├── SimInputController (captures bot actions)
│ ├── WASD → MoveDirection vector (45° isometric conversion)
│ ├── Skills → QueueSkill(scanCode, targetWorldPos)
│ ├── Mouse → track position, screen↔world conversion
│ └── Flash timers for input visualization
│
├── Bot Logic Thread (60Hz)
│ ├── GameStateEnricher.Enrich(state)
│ ├── All 6 systems: Threat, Movement, Navigation, Combat, Resource, Loot
│ ├── NavigationController.Update()
│ └── ExecuteActions() → SimInputController
│
└── Render Thread (ImGui + Veldrid)
├── TerrainRenderer (diamond cells, isometric)
├── EntityRenderer (player, enemies, health bars)
├── EffectRenderer (melee cones, AOE circles, projectile lines)
├── PathRenderer (A* waypoints)
├── InputOverlayRenderer (keyboard + mouse state)
└── DebugPanel (system toggles, stats, spawn controls)
SimWorld — Game Loop
Tick (dt-based, 60Hz)
1. CheckAndExpandTerrain() → expand when player within 50 cells of edge
2. MovePlayer(dt) → WASD direction × speed × dt, collision with terrain
3. ProcessSkills() → dequeue skill casts, dispatch by scan code
4. UpdateProjectiles(dt) → move, check terrain/enemy collisions
5. UpdateEffects(dt) → decay visual effects (0.3s duration)
6. UpdateEnemies(dt) → AI state machine per enemy
7. UpdateRespawns(dt) → cull far enemies, spawn new groups
Terrain
- Procedural: all walkable with scattered obstacles (rock clusters, wall segments, pillars)
- 500×500 initial grid,
WorldToGrid = 23/250 - Infinite expansion: Expands 250 cells per side when player within 50 cells of edge
- Preserves existing data via array copy with offset adjustment
Player
- Position (Vector2), Health/Mana with regen (5 HP/s, 10 MP/s)
- Move speed: 400 world units/s
- Collision: slide-along-X / slide-along-Y fallback if direct move blocked
Skills
| Scan Code | Type | Behavior |
|---|---|---|
| Q (0x10), R (0x13) | AOE | Damage all enemies within 250u of target position |
| E (0x12), T (0x14) | Projectile | Spawn projectile, 1200 speed, 800 range, 80u hit radius |
| LMB, RMB | Melee | 150u cone, 120° angle from player toward target |
Base damage: 200 per hit. Configurable via SimConfig.
Enemy AI
State machine per SimEnemy:
Idle → wander randomly within 200u of spawn, new target every 2-5s
│ player within 600u (aggro range)
▼
Chasing → move toward player at 75% player speed
│ player within 100u (attack range)
▼
Attacking → stand still, deal 30 damage every 1.5s
│ player escapes attack range
▼ back to Chasing
│ health ≤ 0
▼
Dead → visible for 2s → queue for respawn (5s delay)
Enemy Spawning
- Groups: 3-7 enemies per spawn, leader keeps rolled rarity, rest are Normal
- Rarity distribution: 70% Normal, 20% Magic, 8% Rare, 2% Unique
- HP multipliers: Magic=1.5×, Rare=3×, Unique=5× base (200)
- Spawn ring: 800-2000 world units from player
- Direction bias: ±90° cone ahead of player's movement direction
- Culling: Remove enemies > 3000u from player
- Population: Maintain 25 enemies, spawn new groups as needed
Bridge Layer
SimPoller
Replaces MemoryPoller. Background thread at 60Hz:
FlushToWorld()— transfer accumulated inputworld.Tick(dt)— advance simulation (dt clamped to 0.1s max)SimStateBuilder.Build()— convert to GameState- Push to GameDataCache (same fields as production)
SimStateBuilder
Converts SimWorld state → GameState:
- Each SimEnemy → EntitySnapshot (with rarity, threat level, AI state, HP)
- SimPlayer → PlayerState (position, vitals, skills)
- Camera matrix: orthographic projection (12800×7200 world units → 2560×1440 screen)
SimInputController
Implements IInputController, captures actions instead of sending Win32 input:
- WASD → direction vector (with 45° isometric inversion)
- Skills →
SimWorld.QueueSkill(scanCode, worldPos) - Mouse → screen position tracking, inverse camera transform for world coords
- Input visualization: flash timers for keyboard/mouse overlay
Rendering
ViewTransform (Isometric Camera)
45° counter-clockwise rotation matching the game's camera:
World → Grid: gx = worldX × WorldToGrid
Grid → Screen: rx = (gx - gy) × cos(45°)
ry = -(gx + gy) × cos(45°)
Screen = canvasOrigin + viewOffset + (rx, ry) × zoom
Renderers
| Renderer | Draws |
|---|---|
| TerrainRenderer | Diamond cells (rotated grid), explored overlay, minimap |
| EntityRenderer | Player (green circle), enemies (colored by rarity), health/mana bars |
| EffectRenderer | Melee cones (red triangle fan), AOE circles (blue), projectile lines (cyan) |
| PathRenderer | Cyan waypoint lines and dots from A* path |
| InputOverlayRenderer | Keyboard (3 rows: 1-5, QWERT, ASDF) + mouse (L/R/M buttons) |
| DebugPanel | Pause/speed, player stats, enemy counts, system toggles, threat info |
VeldridImGuiRenderer
Custom ImGui backend for Veldrid 4.9.0 + D3D11:
- HLSL shaders compiled at runtime via D3DCompiler P/Invoke
- Dynamic vertex/index buffers, font texture from ImGui atlas
- Alpha blending pipeline with scissor rect support
SimConfig
Terrain: 500×500, WorldToGrid=23/250, ExpandThreshold=50, ExpandAmount=250
Player: Speed=400, HP=1000, MP=500, HPRegen=5/s, MPRegen=10/s
Enemies: Count=25, Aggro=600u, Attack=100u, Speed=75%, HP=200, Damage=30
Spawning: Ring=800-2000u, Groups=3-7, Cull=3000u
Skills: Melee=150u/120°, AOE=250u, Projectile=1200speed/800range, Damage=200
Rarity: Normal=70%, Magic=20%, Rare=8%, Unique=2%
Simulation: SpeedMultiplier=1×, Pauseable
Running
dotnet run --project src/Nexus.Simulator
Dependencies: Core, Data, Systems, Pathfinding, ImGui.NET, Veldrid, Veldrid.StartupUtilities Does NOT depend on: Memory, Input, Screen, Game, Bot, Ui, Trade