Save work: Update game engine, maps, and add vision/pathing utilities. (Tests failing: vision raycast)

This commit is contained in:
Antigravity
2026-02-01 00:48:32 -05:00
parent 071906df59
commit be371e887a
18 changed files with 3270 additions and 303 deletions
+38 -12
View File
@@ -204,27 +204,53 @@ class LLMClient:
## Configuration
### `config/game_settings.yaml`
```yaml
num_impostors: 2
kill_cooldown: 25.0
vision_range: 10.0
impostor_vision_multiplier: 1.5
light_sabotage_vision_multiplier: 0.25
emergencies_per_player: 1
confirm_ejects: true
Use the CLI config manager for easy configuration:
```bash
python scripts/config_manager.py settings list # List all settings
python scripts/config_manager.py settings get kill_cooldown
python scripts/config_manager.py settings set kill_cooldown 30
python scripts/config_manager.py validate # Validate all configs
```
### `config/game_settings.json`
```json
{
"map_name": "skeld",
"num_impostors": 2,
"player_speed": 100.0,
"crewmate_vision": 1.0,
"impostor_vision": 1.5,
"kill_cooldown": 25.0,
"kill_distance": "medium",
"emergencies_per_player": 1,
"discussion_time": 30.0,
"voting_time": 120.0,
"confirm_ejects": true,
"visual_tasks": true,
"taskbar_updates": "always"
}
```
All measurements are in **pixels** (map is 2000x1500). Speed is pixels/second.
### `data/maps/skeld.json`
```json
{
"canvas": {"width": 2000, "height": 1500},
"rooms": [
{"id": "cafeteria", "name": "Cafeteria", "tasks": [...], "vent": null},
{"id": "cafeteria", "name": "Cafeteria", "center": [1000, 350], "bounds": [[850, 200], [1150, 500]]},
...
],
"edges": [
{"id": "cafe_weapons", "room_a": "cafeteria", "room_b": "weapons", "distance": 5.0},
{"id": "cafe_weapons", "room_a": "cafeteria", "room_b": "weapons", "waypoints": [[1150, 350], [1300, 350]]},
...
]
],
"walls": [
{"start": [850, 200], "end": [1150, 200]},
...
],
"spawn_points": {"cafeteria": [1000, 400]}
}
```
+164
View File
@@ -0,0 +1,164 @@
# Ray-Traced FOV & Video Rendering Pipeline
## Current State
- Vision is **room-based** (graph traversal)
- Player sees everyone in same room
- No pixel-level visibility, no wall occlusion
## Target State
- **Ray-traced FOV** matching real Among Us
- Walls block visibility
- Circular vision radius from player
- Light sabotage reduces radius
- Video output at **60fps** for YouTube
---
## Part 1: Ray-Traced FOV
### Map Data
Current `skeld.json` has rooms + edges (corridors). Need to add:
```json
{
"walls": [
{"p1": [100, 50], "p2": [100, 200]},
{"p1": [100, 200], "p2": [250, 200]}
],
"spawn_positions": {"cafeteria": [300, 400]}
}
```
### Visibility Algorithm
1. **Cast rays** from player position in 360° (e.g., 360 rays)
2. **Intersect** each ray with wall segments
3. **Closest intersection** per ray = vision boundary
4. **Vision radius** clamps max distance
5. **Player visible** if: within radius AND not occluded by walls
### Implementation
- `src/engine/vision_raycast.py` — new module
- `RaycastVisionSystem.get_visible_players(observer_pos, all_players, walls)`
- Returns: list of visible player IDs + positions
---
## Part 2: Engine Changes
### Current Position Model (`types.py`)
```python
@dataclass
class Position:
room_id: Optional[str] = None # Discrete room
edge_id: Optional[str] = None # Walking between rooms
progress: float = 0.0 # 0.0-1.0 on edge
```
### New Position Model
```python
@dataclass
class Position:
x: float = 0.0 # Pixel X
y: float = 0.0 # Pixel Y
room_id: Optional[str] = None # Derived from position
def distance_to(self, other: "Position") -> float:
return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
```
### New Map Data (`skeld.json`)
```json
{
"rooms": [...],
"edges": [...],
"walls": [
{"p1": [100, 50], "p2": [100, 200]},
...
],
"room_polygons": {
"cafeteria": [[x1,y1], [x2,y2], ...],
...
},
"spawn_points": {
"cafeteria": [300, 400],
...
}
}
```
### New Module: `vision_raycast.py`
```python
class RaycastVision:
def __init__(self, walls: list[Wall], vision_radius: float):
...
def is_visible(self, from_pos: Position, to_pos: Position) -> bool:
"""True if line-of-sight exists (no wall occlusion)."""
...
def get_visible_players(self, observer: Position,
all_players: list[Player]) -> list[Player]:
"""Returns players within vision radius AND line-of-sight."""
...
def get_vision_polygon(self, observer: Position) -> list[tuple]:
"""For rendering: polygon representing visible area."""
...
```
### Wall Intersection Algorithm
```python
def ray_intersects_wall(ray_start, ray_dir, wall_p1, wall_p2) -> float | None:
"""Returns distance to intersection, or None if no hit."""
# Standard line-segment intersection math
```
---
## Part 3: Rendering Pipeline
### Frame Generation (60fps)
```
replay.json → Renderer → frames/0001.png, 0002.png, ...
```
### Per Frame:
1. Draw map background (Skeld PNG)
2. Apply FOV mask (ray-traced vignette)
3. Draw player sprites at interpolated positions
4. Draw bodies
5. Overlay effects (kill, vent, sabotage)
### Video Assembly
```bash
ffmpeg -framerate 60 -i frames/%04d.png -c:v libx264 output.mp4
```
---
## Part 4: Assets
| Asset | Format | Source |
|-------|--------|--------|
| Skeld map | PNG | Fan art / game extract |
| Crewmate sprites | Spritesheet | Available online |
| Kill animations | Sprite sequence | Extract or recreate |
| Meeting UI | HTML/PNG | Recreate |
---
## Implementation Order
1. **Map upgrade** — Add walls + pixel coords
2. **Raycast vision** — `vision_raycast.py`
3. **Pixel positions** — Upgrade engine to (x,y)
4. **Path interpolation** — Smooth walking
5. **Frame renderer** — Pillow/Pygame
6. **Meeting renderer** — Overlay
7. **FFmpeg integration** — Stitch to video
## Questions
1. **POV style**: Single player POV, or omniscient?
2. **Internal thoughts**: Show as subtitles?
3. **TTS**: Voice for dialogue?