Files
tactical-shooter/gdextension/simulation/README.md
T
shawn e9dc05983c Save workspace artifacts: character-controller, gdextension scaffold, network scripts, map-pipeline, server config
Includes code from task workspaces that was never pushed:
- client/characters/ — fps_character_controller.gd (400 lines), fps_camera.gd, input_handler.gd
- gdextension/simulation/ — C++ GDExtension scaffold: entity, movement, hit detection, simulation server, state serializer, bitstream (14 files)
- scripts/network/ — ENet-based network_manager.gd, server/client_main.gd, player.gd
- scripts/map_packaging/ — PCK pipeline: pack_map.gd, map_downloader.gd, map_registry_server.py, README
- scripts/config/ — server_config.gd (480 lines)
- scenes/ — client_main, server_main, player, test_range
- project.godot, export_presets.cfg
2026-07-01 18:30:44 -04:00

160 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tactical Shooter — GDExtension Simulation Core
GDExtension C++ simulation core for a 128Hz tactical FPS. Movement, hit detection, and state serialization run entirely in C++ — the GDScript hot loop handles only network I/O and rendering.
## Architecture
```
┌─────────────────────────────────────────────────────┐
│ GDScript Layer │
│ _process(delta) → can_tick() → tick() → send() │
├─────────────────────────────────────────────────────┤
│ SimulationServer (C++) │
│ ┌──────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │Movement │ │HitDetection │ │StateSerializer│ │
│ │Component │ │(ray/overlap) │ │(delta-compress)│ │
│ └──────────┘ └───────────────┘ └───────────────┘ │
│ ┌──────────────────────────────────────────────────┐│
│ │ Entity[N] (position, velocity, health, input) ││
│ └──────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────┤
│ Network Layer (GDScript → ENet) │
└─────────────────────────────────────────────────────┘
```
### Design Principles
- **Hot path in C++**: All simulation ticks (movement, hit detection, serialization) run in GDExtension. No GDScript VM overhead per tick.
- **Custom replication**: Not using Godot's scene-replication (too expensive at 128Hz). Delta-compressed binary snapshots via `StateSerializer`.
- **Abstracted systems**: Movement parameters are configurable from GDScript. Hit detection uses geometric checks (no PhysicsServer3D dependency in Phase 1).
- **Benchmark-ready**: `SimulationServer::populate_bots()` + `get_stats()` provide the hook for the 128Hz load test (`task t_f671f48a`).
## Prerequisites
- **Godot 4.2+** (recommended: 4.3+)
- **godot-cpp** (v4.3 branch): `git submodule update --init`
- **SCons** 4.0+: `pip install scons`
- **C++17 compiler**: GCC 11+ / Clang 14+ / MSVC 2022
## Setup
```bash
# Clone with submodules
git clone <repo-url> tactical-shooter
cd tactical-shooter
git submodule update --init --recursive
# Build (debug)
scons -j$(nproc)
# Build (release)
scons target=template_release -j$(nproc)
```
The compiled `.so`/`.dll` will be placed in `gdextension/bin/<platform>/`.
## Usage (GDScript)
```gdscript
var server = SimulationServer.new()
server.tick_rate = 128
server.start()
# Spawn some entities
server.spawn_entity(Vector3(0, 0, 0))
server.spawn_entity(Vector3(5, 0, 0))
# Main loop
func _process(delta):
while server.can_tick(delta):
var snapshot = server.tick()
if snapshot.size() > 0:
send_to_clients(snapshot)
# Apply player input
func _input(event):
var input_dict = {
move_direction = input_vector,
look_yaw = camera_yaw,
look_pitch = camera_pitch,
jump = Input.is_action_just_pressed("jump"),
sprint = Input.is_action_pressed("sprint"),
shoot = Input.is_action_just_pressed("shoot"),
input_sequence = input_seq
}
server.apply_input(player_entity_id, input_dict)
```
## Project Structure
```
├── SConstruct # Build system
├── src/
│ ├── register_types.cpp/.h # GDExtension entry point
│ ├── simulation_server.cpp/.h # Main orchestrator (GDScript-facing)
│ ├── entity.cpp/.h # Entity state (GDScript-facing)
│ ├── movement_component.cpp/.h # FPS movement simulation
│ ├── hit_detection.cpp/.h # Ray/sphere hit detection
│ ├── state_serializer.cpp/.h # Delta-compressed snapshot I/O
│ └── bitstream.h # Bit-level packing (header-only)
├── gdextension/
│ ├── simulation.gdextension # Godot extension config
│ └── bin/ # Build output
└── tests/
└── test_simulation.cpp # Unit test harness
```
## Movement Parameters
All tunable from GDScript:
| Parameter | Default | Description |
|----------------|---------|--------------------------------------|
| walk_speed | 4.0 | Ground speed (units/s) |
| sprint_speed | 6.5 | Sprint speed (units/s) |
| crouch_speed | 2.0 | Crouch speed (units/s) |
| acceleration | 20.0 | Ground acceleration (units/s²) |
| air_acceleration| 4.0 | Air acceleration (units/s²) |
| friction | 8.0 | Ground deceleration |
| jump_velocity | 5.0 | Initial upward velocity (units/s) |
| gravity | -20.0 | Gravity (units/s², negative = down) |
## Snapshot Wire Format
```
[uint32 tick] # Server tick number
[uint16 count] # Number of changed entities
[uint16 base_tick] # 0 = full snapshot
--- per entity ---
[uint16 entity_id]
[uint32 change_mask] # Which fields changed
[fields per change_mask] # Only changed fields, packed as:
position: 3 × 16-bit quantized floats (range ±1024)
velocity: 3 × 12-bit quantized floats (range ±32)
rotation: 2 × 11-12 bit quantized floats
health: 7-bit quantized (0-100)
armor: 7-bit quantized (0-100)
weapon_id: uint8
ammo: uint16
flags: uint16
input_seq: uint32
```
Typical sizes at 128Hz:
- **Full snapshot** (10 entities): ~300-400 bytes
- **Delta snapshot** (5 changed): ~150-250 bytes
## Task Dependencies
```
Phase 0 (done) → build:gdextension-simulation-scaffold (this) → bench:128hz-load-test
→ build:fps-character-controller
```
## Known Limitations (Phase 1)
- Hit detection uses bounding sphere geometry (not PhysicsServer3D raycasts)
- Ground detection is stub-only (no scene queries for floor normal)
- No interpolation: clients receive raw tick snapshots
- Single-threaded tick processing