PSF-BotServer/bot-docs/TOWER_DEMO.md
Claude 95a579a3e6
docs: Update tracer fix status and select Hossin for tower demo
- Document tracer direction fix root cause and solution in TOWER_DEMO.md
- Select Hossin as the continent for tower arena demo
- Update PROJECT.md with tracer direction fix completion
- Update status checklists
2025-11-23 08:35:12 +00:00

188 lines
5.3 KiB
Markdown

# Tower Attack Demo
## Overview
A controlled demonstration of the bot combat system at a single tower location. By manually capturing coordinates, we create a bounded "arena" where bots behave correctly without needing full world geometry.
## Why This Approach
- **Wall shooting problem**: Bots currently shoot through walls (no server-side collision)
- **Full solution is massive**: UBR mesh extraction + navmesh generation = huge project
- **POC needs to be demonstrable**: Bots shooting through 4 concrete walls isn't a demo, it's a bug showcase
- **Scrappy but effective**: Manual coordinate capture gives us a working showcase
---
## Implementation Plan
### Step 1: Choose a Tower
Pick a tower with good sight lines and clear boundaries:
- [x] Select continent: **Hossin** (jungle continent with interesting terrain)
- [ ] Select specific tower location (user to provide)
- [ ] Capture tower coordinates via `!locrec <note>` command in-game
### Step 2: Define Combat Arena
Capture boundary coordinates by walking the perimeter:
```
/loc at each corner point:
- Northwest corner
- Northeast corner
- Southeast corner
- Southwest corner
- Height bounds (ground level, top platform)
```
### Step 3: Implement Arena System
```scala
// Combat arena definition
case class CombatArena(
minX: Float, maxX: Float,
minY: Float, maxY: Float,
minZ: Float, maxZ: Float,
name: String = "unnamed"
)
// Check if position is within arena bounds
def isInArena(pos: Vector3, arena: CombatArena): Boolean = {
pos.x >= arena.minX && pos.x <= arena.maxX &&
pos.y >= arena.minY && pos.y <= arena.maxY &&
pos.z >= arena.minZ && pos.z <= arena.maxZ
}
// Only engage if BOTH bot AND target are in the arena
// This prevents shooting through walls into/out of the arena
def findTarget(botState: BotState): Option[Player] = {
val arena = currentArena // loaded from config
zone.LivePlayers.filter { p =>
isInArena(botState.player.Position, arena) &&
isInArena(p.Position, arena) &&
p.isAlive &&
p.Faction != botFaction &&
// ... other existing checks
}
}
```
### Step 4: Optional - Interior Exclusion Zones
Define boxes for building interiors where bots won't engage:
```scala
case class ExclusionZone(
minX: Float, maxX: Float,
minY: Float, maxY: Float,
minZ: Float, maxZ: Float,
name: String = "interior"
)
// Skip targets in exclusion zones
def isInExclusionZone(pos: Vector3, zones: Seq[ExclusionZone]): Boolean = {
zones.exists { zone =>
pos.x >= zone.minX && pos.x <= zone.maxX &&
pos.y >= zone.minY && pos.y <= zone.maxY &&
pos.z >= zone.minZ && pos.z <= zone.maxZ
}
}
```
---
## Demo Scenario
### Setup
1. Spawn 3-5 NC bots around tower perimeter
2. Player approaches as TR/VS
3. Bots engage when player enters arena bounds
4. Combat feels natural - no wall shooting because everyone is in open area
### What to Show
- Bots spot player and react (2-second delay)
- Bots track and engage target
- Accuracy/recoil system visible in hit patterns
- Bots die when killed, respawn after delay
- Weapon drawn, muzzle flash, sound effects
### Known Limitations for Demo
- [x] ~~No tracers~~ **RESOLVED** - tracers working! (see notes below)
- [ ] Bots don't navigate around obstacles (random wandering only)
- [ ] Single hardcoded arena location
---
## Future: Scaling Beyond One Tower
Once the single-tower demo works, the approach can scale:
1. **Multiple arenas**: Define several towers/bases as combat zones
2. **Config file**: Load arena definitions from JSON/YAML
3. **Auto-generation**: Eventually parse map data for structure bounds
4. **Full solution**: UBR mesh extraction for complete world collision
---
## Coordinate Capture Workflow
When ready to capture tower coordinates:
```
1. Log into game, go to chosen tower
2. Walk to each boundary corner, type /loc
3. Record coordinates in format:
Tower: [Name]
Continent: [home1/home2/etc]
Boundary Points:
- NW: (x, y, z)
- NE: (x, y, z)
- SE: (x, y, z)
- SW: (x, y, z)
Height Bounds:
- Ground: z = [value]
- Top: z = [value]
Optional Interior Exclusions:
- Room 1: (minX, minY, minZ) to (maxX, maxY, maxZ)
```
---
## Tracer Investigation Notes
**Status: RESOLVED**
### Root Cause
The client caches player orientation when `ChangeFireStateMessage_Start` is received
and uses that cached orientation for tracer direction rendering. Our original code
sent the fire state message BEFORE updating/broadcasting the bot's orientation.
### Solution
In `startFiring()`:
1. Calculate yaw to face target
2. Update `player.Orientation` to face target
3. Broadcast `PlayerState` with correct orientation
4. **THEN** send `ChangeFireStateMessage_Start`
Also fixed `facingYawUpper` in PlayerState to be 0 (relative to body) instead of
the absolute yaw value.
### Key Learnings
- Tracers use LocalEvents like turrets (`LocalAction.SendResponse(ChangeFireStateMessage_Start)`)
- The firing flag (`ChangeFireStateMessage_Start`) MUST be sent for tracers to render
- Orientation MUST be correct BEFORE fire state is sent - client caches it
- `facingYawUpper` is relative to `facingYaw`, not absolute (0 = looking straight ahead)
---
## Status
- [x] Tracers working correctly
- [x] Continent selected: Hossin
- [ ] Tower location selected
- [ ] Coordinates captured via !locrec
- [ ] Arena bounds implemented
- [ ] Exclusion zones defined (optional)
- [ ] Demo tested and working
- [ ] Video/screenshots captured