Phase 7: netfox + godot-jolt stack upgrade
Stack installed: - netfox v1.35.3 (core + extras + noray + internals) - godot-jolt v0.16.0-stable Architecture: - Server: ENet transport (works headless, no netfox deps) - Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator) New/modified: - docs/migration-netfox-plan.md — migration architecture - scripts/network/network_manager.gd — netfox-aware ENet fallback - scripts/network/player.gd — clean base player - client/characters/player_netfox.gd — rollback player w/ WeaponManager - client/characters/input/player_net_input.gd — BaseNetInput subclass - client/characters/character/fps_character_controller.gd — netfox input feed - client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager - client/scripts/round_replicator.gd — client-side round state bridge - server/scripts/round_manager.gd — improved state machine - server/scripts/plugin_api/plugin_manager.gd — refined plugin system - config: enemy_tag, ally_tag for meatball targeting Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user