# 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 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//`. ## 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