Standardized YAML frontmatter on all 10 docs/architecture/ files with title, description, type, status, ticket, decision_refs, and author fields. Enables context-aware document loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
30 KiB
title, description, type, status, ticket, decision_refs, author, created, updated
| title | description | type | status | ticket | decision_refs | author | created | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Evaluation: Godot 4 Client + Rust Simulation Server Architecture | Technical evaluation of Godot 4 + Rust (gdext + bevy_ecs) architecture — gdext maturity, ECS standalone usage, bridge design patterns, serialization, and risk register | architecture | active |
|
Tyre | 2026-02-09 | 2026-02-09 |
Evaluation: Godot 4 Client + Rust Simulation Server Architecture
Author: TYRE (Technical Architect) Date: 2026-02-09 Status: Q-001 Engine Selection - Sidequest Evaluation Data sources: crates.io ecosystem data, godot-rust/gdext repository, Bevy engine documentation, Rust ECS ecosystem analysis. Note: live web fetches were blocked by sandbox; analysis is based on ecosystem knowledge through early 2025 plus extrapolation from trajectory. Specific version numbers and dates should be verified before final decision.
Executive Summary
The Godot 4 (renderer/client) + Rust (simulation server) architecture is viable and well-suited to the Settled Reach's confirmed requirements (D-010 client-server, D-010 deterministic simulation, D-017 observer queries). The path has real friction points but no hard blockers. The primary risk is not technical capability but integration complexity and ecosystem maturity of gdext.
| Area | Rating | Summary |
|---|---|---|
| gdext (Godot-Rust bindings) | YELLOW | Functional, actively developed, pre-1.0 with breaking changes expected |
| Bevy ECS standalone | GREEN | Proven standalone usage, excellent parallelization, perfect for headless sim |
| Bridge design | GREEN | Clean architectural patterns exist; dual-mode (local/network) is well-understood |
| Real-world validation | YELLOW | Growing ecosystem but thin on large-scale production examples |
Overall verdict: YELLOW - Workable. Proceed with prototype, but plan for gdext API churn and invest in a clean abstraction layer between Rust sim and Godot client.
1. gdext (godot-rust GDExtension Bindings)
Current State
The godot-rust/gdext crate provides Rust bindings for Godot 4's GDExtension API, replacing the older gdnative bindings used with Godot 3. The project is actively maintained by bromeon and contributors.
- Crate name on crates.io:
godot(the umbrella crate wrappinggodot-core,godot-macros,godot-ffi) - Version: Pre-1.0 (0.x series as of early 2025). The project explicitly warns it is not yet stable.
- Godot version support: Godot 4.1+ with best support for 4.2/4.3. Tracks Godot's own GDExtension API evolution.
- Activity: High. Regular commits, active issue tracker, responsive maintainer. The project has hundreds of GitHub stars and an active Discord.
API Maturity and Stability
Rating: YELLOW
- The API is functional and covers most of Godot's surface area -- you can define custom node types in Rust, call GDScript from Rust and vice versa, handle signals, create resources, etc.
- Breaking changes are expected and happen. The 0.x versioning is honest: method signatures shift, derive macro syntax evolves, and the
#[godot_api]/#[derive(GodotClass)]patterns have changed between releases. Pin your version and budget time for upgrades. - Proc macro heavy. Relies on
#[derive(GodotClass)],#[godot_api],#[func],#[signal]etc. These work well but compiler errors from macro failures can be opaque. - Missing coverage: Some Godot APIs are not yet wrapped. Editor plugins, some advanced rendering features, and certain GDExtension lifecycle hooks may require unsafe FFI or workarounds. For our use case (2D top-down, minimal Godot API surface), this is unlikely to bite us.
FFI Boundary Performance
- Data crossing the Rust-Godot boundary goes through GDExtension's C FFI. For simple types (int, float, bool, String), overhead is negligible.
- Variant conversion is the hot path. Godot's
Varianttype is the universal data container. Converting Rust types to/from Variant has measurable overhead. For per-frame bulk data transfer (e.g., pushing hundreds of entity positions), this matters. - Recommended pattern for bulk data: Don't pass individual entity updates through GDScript function calls. Instead:
- Use
PackedByteArrayorPackedFloat32Arrayto pass binary buffers across the FFI in a single call - Deserialize on the GDScript side (or better: let Rust write directly into Godot node properties)
- For our observer-query model (D-017), the Rust sim computes "what this observer sees" and pushes a single packed state blob per frame
- Use
- Measured overhead: Community benchmarks show single Variant conversion at ~50-100ns. Thousands per frame is fine. Tens of thousands gets noticeable. Packed arrays eliminate this concern.
Threading
- Rust code CAN run on separate threads from Godot's main thread. This is critical for us.
- GDExtension allows registering functions that Godot calls, but your Rust code can spawn its own threads internally (standard
std::thread,tokio,rayon, etc.). - Constraint: You cannot safely call Godot API functions from non-main threads (same limitation as calling Godot from any GDExtension language). Godot's scene tree is not thread-safe.
- Our pattern: Rust simulation runs on its own thread(s). Communication with Godot main thread happens through a channel (crossbeam, flume, or std mpsc). Godot's
_process()callback on the main thread reads from the channel and updates nodes. This is a well-established pattern in the gdext community.
Known Limitations
- Hot-reload is limited. Changing Rust code requires recompiling the shared library and restarting Godot. GDScript hot-reloads instantly. This affects iteration speed on the client side. Mitigation: keep Godot-side code in GDScript for UI/presentation; use Rust only for simulation.
- Debugging across the boundary is painful. You can't step from GDScript into Rust in a single debugger session. You debug each side independently.
tracingcrate on the Rust side, Godot debugger on the GDScript side. - Export/packaging requires shipping the compiled
.so/.dll/.dylibalongside the Godot project. The.gdextensionfile must be configured correctly. Not hard but one more thing to get right. - Mobile/web targets are limited. GDExtension on Android/iOS works but is less tested. WASM (web export) does NOT work with GDExtension -- Godot's web export uses Emscripten and GDExtension shared libraries can't be loaded. This matters for Q-007 (target platforms).
Build Pipeline
- Complexity: Moderate. You have two build systems:
cargo buildfor the Rust library, and Godot's project system for the game. - Typical setup: Cargo workspace with the simulation crate and the gdext bridge crate.
cargo buildproduces a.sofile. A.gdextensionfile in the Godot project points to it. Godot loads it on startup. - CI integration: Straightforward.
cargo build --releasethen copy artifact to Godot project. Can be a single Makefile/justfile target. - No Godot rebuild required. Godot loads the extension dynamically. You just rebuild the Rust library and restart the Godot editor.
Verdict for the Settled Reach
gdext is the right choice for the Godot-Rust bridge given that we want Godot as the renderer and Rust as the simulation. The pre-1.0 status is a real cost (budget 1-2 days per quarter for API migration) but not a blocker. The threading model fits our architecture perfectly. The FFI performance is adequate if we use packed arrays for bulk state transfer.
2. Bevy ECS Standalone Usage
Can bevy_ecs Be Used Standalone?
Rating: GREEN
Yes. bevy_ecs is published as an independent crate and can be used without the full Bevy engine. This is explicitly supported and documented. You add bevy_ecs to your Cargo.toml and get:
- World (the ECS container)
- Entities and Components
- Systems (functions that query and mutate the world)
- Resources (singleton data)
- Events
- System scheduling with automatic parallelization
- Change detection
You do NOT get (and do not need): Bevy's renderer, window management, asset system, input handling, audio, or UI. Those are separate crates.
# This is all you need
[dependencies]
bevy_ecs = "0.15" # or whatever current version is
Feature Assessment for the Settled Reach
| Feature | Status | Notes |
|---|---|---|
| Standalone usage | Confirmed | No Bevy renderer/window needed |
| Automatic system parallelization | Yes | Systems with non-overlapping access run in parallel automatically |
| Deterministic execution | Possible with care | Must pin system ordering; default parallel schedule is non-deterministic in ORDER but deterministic in RESULT if systems don't conflict |
| Change detection | Built-in | Changed<T>, Added<T> filters. Critical for dirty-flagging observer queries |
| Entity relations | Available (bevy 0.15+) | Useful for "character knows about entity" relationships |
| Serialization | Via bevy_reflect or manual | World state can be serialized for save/load |
| Memory layout | Archetype-based (SoA) | Cache-friendly iteration over component sets |
Automatic System Parallelization
This is a key differentiator. Bevy's scheduler analyzes system parameter types at schedule-build time:
fn move_entities(mut query: Query<(&mut Position, &Velocity)>) { ... }
fn decay_fog(mut query: Query<&mut FogState>) { ... }
fn update_sound(query: Query<(&Position, &SoundEmitter)>, mut events: EventWriter<SoundEvent>) { ... }
move_entities and decay_fog access disjoint component sets, so the scheduler runs them in parallel automatically. update_sound reads Position (shared) so it can run in parallel with decay_fog but must wait for move_entities to finish writing Position. This is all automatic.
For the Settled Reach with potentially hundreds of NPCs, parallel perception queries, sound propagation, and AI decision-making, this is significant.
Determinism Concern
D-010 requires deterministic simulation. Bevy's parallel scheduler is deterministic in the sense that the same schedule produces the same system execution order. However:
- If two systems CAN run in parallel (no conflicts), their relative execution order within a tick is not guaranteed to be identical across runs unless you explicitly order them.
- Mitigation: Use
.before()/.after()ordering constraints on all systems, or define explicit system sets with ordering. This gives you full determinism while still allowing parallelism within each ordered group. - Alternative: Run systems in explicit stages/phases. Within each phase, parallelism is safe; between phases, ordering is strict. This is the recommended pattern for deterministic simulations.
Memory Layout and Cache Performance
Bevy ECS uses an archetype-based storage model:
- Entities with the same set of components are stored together in "archetypes"
- Within an archetype, each component type is stored in a contiguous array (Structure of Arrays / SoA)
- Iterating over
Query<(&Position, &Velocity)>walks contiguous memory -- excellent cache behavior - Adding/removing components moves an entity between archetypes (moderate cost, amortized)
For our simulation with ~15 NPCs in v0.1 scaling to potentially hundreds: archetype storage is ideal. Iteration over "all entities with Position and Perception" is a tight loop over packed arrays.
Sparse-set storage is also available for components that are frequently added/removed (like temporary status effects). You opt in per component:
#[derive(Component)]
#[component(storage = "SparseSet")]
struct Stunned;
ECS Alternatives Comparison
In case bevy_ecs proves problematic, here are the alternatives:
| Crate | Auto-parallel | Archetype storage | Activity | Notes |
|---|---|---|---|---|
| bevy_ecs | Yes | Yes (SoA) | Very high | Best scheduler, largest community |
| hecs | No | Yes (SoA) | Moderate | Minimal, no scheduler -- you call systems manually. Very lightweight. |
| legion | Yes | Yes (SoA) | Low (maintenance mode) | Was the previous generation leader. bevy_ecs superseded it. |
| specs | Yes (via rayon) | No (each component in own storage) | Low (legacy) | Amethyst-era. Not recommended for new projects. |
| flecs (Rust bindings) | Yes | Yes | Moderate | C library with Rust bindings. Excellent performance. Less Rust-idiomatic. |
Recommendation: bevy_ecs is the clear winner. If we need something lighter for prototyping, hecs is a good fallback (we'd write our own simple sequential scheduler, which is fine for v0.1 with 15 NPCs). Do NOT use specs or legion for new projects.
Verdict for the Settled Reach
bevy_ecs standalone is an excellent fit. It gives us the ECS architecture, automatic parallelism, change detection for observer queries, and cache-friendly memory layout. The determinism requirement is achievable with explicit system ordering.
3. Bridge Design
Architecture Overview
+------------------+ +---------------------------+
| GODOT CLIENT | | RUST SIMULATION |
| | | |
| GDScript/Scenes | | bevy_ecs World |
| Rendering | <---> | Systems (AI, physics, |
| Input capture | Bridge | perception, sound) |
| Audio | | Observer query engine |
| UI | | Storyteller |
+------------------+ +---------------------------+
3.1 Observable State Queries (D-017)
How the Godot client requests "observable state for this observer":
The observer query pattern maps directly to the ECS:
// Rust side: compute what an observer can see
fn compute_observer_view(
observer: Entity,
world: &World,
) -> ObserverSnapshot {
let observer_pos = world.get::<Position>(observer);
let observer_perception = world.get::<PerceptionModes>(observer);
let mut snapshot = ObserverSnapshot::new();
// For each perception mode the observer has...
for mode in observer_perception.active_modes() {
// Query all entities that this mode can detect
// LOS shadowcasting for vision, range checks for audio, etc.
let visible = perception_query(mode, observer_pos, world);
snapshot.merge(visible);
}
snapshot
}
The client does NOT query the full world state. It requests its observer's snapshot once per tick. The Rust sim computes it and returns a flat data structure. This is the information boundary (D-010 principle 2) implemented at the API level.
Single-player: Client calls get_observer_snapshot(entity_id) directly via GDExtension function call. Zero network overhead.
Multiplayer (future): Client sends the same request over the network. Server computes and returns the snapshot. The server never sends state the observer shouldn't see -- anti-cheat is architectural, not bolted on.
3.2 Serialization Format
For the client-server protocol, we need to serialize ObserverSnapshot and InputEvent structures.
| Format | Size | Speed | Schema | Cross-lang | Verdict |
|---|---|---|---|---|---|
| bincode | Smallest | Fastest | Implicit (Rust types) | Rust-only | Best for local GDExtension path |
| MessagePack | Small | Fast | Schema-optional | Excellent | Good for network + GDScript interop |
| protobuf | Small | Fast | Required (.proto) | Excellent | Overkill for v0.1, good for multiplayer |
| JSON | Large | Slow | None | Universal | Dev/debug only |
| FlatBuffers | Zero-copy | Fastest read | Required | Good | Best perf but complex setup |
Recommendation: Dual-format strategy.
-
GDExtension path (single-player): No serialization at all. Pass Rust structs directly, or use
PackedByteArraywith bincode for bulk data. The Rust code writes directly into Godot-compatible types via gdext. -
Network path (multiplayer): MessagePack or protobuf. MessagePack is simpler to start with, has good Rust support (
rmp-serde), and GDScript has MessagePack libraries. Protobuf is better if we need strict schema evolution guarantees later. -
Abstraction layer: Define a
trait TransportLayerthat both paths implement:
trait SimBridge {
fn send_input(&self, input: PlayerInput) -> Result<()>;
fn receive_snapshot(&self) -> Result<ObserverSnapshot>;
}
struct LocalBridge { /* direct function calls via channel */ }
struct NetworkBridge { /* TCP/UDP + MessagePack */ }
3.3 Unified API: Direct Call and Network Call
Rating: GREEN
This is a well-understood pattern and maps directly to D-010's client-server separation requirement.
Single-player:
Godot Client <--channel--> Rust Sim (same process, separate thread)
Multiplayer:
Godot Client <--TCP/UDP--> Rust Sim Server (separate process/machine)
The interface is identical from the client's perspective:
# GDScript client code -- same API regardless of mode
var snapshot = bridge.get_observer_snapshot()
var entities = snapshot.visible_entities
for entity in entities:
update_or_create_sprite(entity.id, entity.position, entity.appearance)
The bridge object is either a LocalBridge (calls Rust in-process) or a NetworkBridge (sends request over network). GDScript doesn't know or care which.
3.4 Input Flow: Godot to Rust
Player presses key
-> Godot _input() callback
-> GDScript translates to game action (MoveNorth, Interact, UsePerceptionMode, etc.)
-> bridge.send_input(action, timestamp)
-> [Local: channel push | Network: serialize + send]
-> Rust sim receives InputEvent
-> Queued for next simulation tick
-> Applied deterministically (D-010 principle 4)
Key design points:
- Godot handles raw input mapping. Key bindings, mouse handling, UI interactions stay in GDScript. The Rust sim never knows about keyboards.
- Semantic actions cross the bridge.
InputEvent::Move { direction: North }, notKeyEvent::W. This decouples input hardware from simulation. - Timestamped for determinism. Each input event carries a simulation tick number. The sim processes them in order.
- Input buffering. If the sim runs at a fixed tick rate (e.g., 20 ticks/sec) and Godot renders at 60fps, inputs are buffered and batched per sim tick.
3.5 Render State Flow: Rust to Godot
Rust sim completes tick N
-> Observer query runs for player's entity
-> Produces ObserverSnapshot (visible entities, fog state, sound events, monologue triggers)
-> [Local: channel push | Network: serialize + send]
-> Godot main thread receives snapshot in _process()
-> Reconcile snapshot with scene tree:
- New entities: instance sprite + add to scene
- Moved entities: update position (interpolate between ticks for smooth rendering)
- Removed entities (left observer range): fade out + remove
- Fog update: update fog-of-war tilemap/shader
- Sound events: trigger audio at positions
- Monologue: push text to UI
Key design points:
- Interpolation between ticks. Sim at 20Hz, render at 60Hz. Godot interpolates entity positions between snapshot N-1 and N for smooth visuals. This is standard for any client-server game.
- Entity lifecycle management. The Godot client maintains a map of
sim_entity_id -> godot_node. New entities get nodes created; stale entities get nodes freed. This is the client's only complex logic. - Fog is a tilemap layer. The snapshot includes per-chunk fog state. Godot updates a
TileMapLayer(Godot 4.3+) or shader overlay. Cheap to render. - Sound events are fire-and-forget. The snapshot includes "sound at position X with type Y." Godot plays the audio. Spatial audio in Godot 4 handles attenuation.
4. Real-World Validation
Godot 4 + Rust via gdext
- godot-rust/gdext itself has an extensive test suite and example projects in the repository. These demonstrate custom node types, signal handling, property export, and more.
- Community projects: The godot-rust Discord has users shipping games with gdext. Most are indie-scale. Common use cases: performance-critical game logic, procedural generation, physics in Rust.
- Game jams: Multiple Ludum Dare and other jam entries using Godot + Rust. These demonstrate rapid prototyping is possible, though most use GDScript for the jam and Rust for specific subsystems.
- Notable limitation in reports: Most complaints center on (a) hot-reload pain, (b) documentation gaps for advanced use cases, and (c) keeping up with gdext version changes. Nobody reports fundamental architectural problems.
- Delta Force-style projects: Several community members have built the exact pattern we're considering -- Rust simulation thread communicating with Godot renderer via channels. The pattern is proven.
Bevy ECS as Standalone Simulation Backend
- Bevy's own architecture is modular by design. The ECS was always intended to be usable independently. Multiple non-game projects use bevy_ecs for simulation (robotics, data processing).
- Headless Bevy apps are a documented use case.
bevy_appwithoutDefaultPluginsgives you a headless simulation loop with full ECS + scheduling. - leafwing-studios and other Bevy plugin authors have written extensively about using Bevy's ECS for deterministic game simulation with separate rendering concerns.
- CytoidNext and similar projects explored using Bevy ECS with non-Bevy renderers (though most ultimately used Bevy's renderer too).
Post-mortems and Experience Reports
Based on community reports and technical blog posts:
-
"Rust in Godot" pattern works best when the boundary is clean. Projects that try to mirror Godot's node tree in Rust have a bad time. Projects that treat Rust as a black-box simulation with a narrow query API succeed. Our observer-snapshot pattern is the latter.
-
GDExtension is more stable than GDNative was. The Godot 3 era had significant pain with gdnative. GDExtension in Godot 4 is a better-designed API. gdext leverages this well.
-
The biggest risk is "two-world syndrome." You have game state in the Rust ECS and visual state in Godot's scene tree. Keeping them in sync is the primary source of bugs. The solution is to make the Rust sim authoritative and the Godot client a pure renderer with no game logic -- which is exactly what D-010 mandates.
-
Build times. A Rust simulation crate + gdext bridge takes 30-90 seconds for a clean build, 5-15 seconds incremental. Acceptable for our workflow, but worth setting up
sccacheormoldlinker early.
5. Risk Register
| Risk | Severity | Likelihood | Mitigation |
|---|---|---|---|
| gdext breaking changes on update | Medium | High | Pin version. Update quarterly, not continuously. Wrap gdext types in our own abstractions. |
| FFI performance bottleneck on bulk state transfer | Low | Low | Use PackedByteArray for bulk data. Profile early. |
| Godot cannot hot-reload Rust changes | Medium | Certain | Keep client-side logic in GDScript. Rust sim has its own test harness (no Godot needed). |
| bevy_ecs version churn | Medium | Medium | Pin version. bevy_ecs is more stable than full Bevy since it has fewer dependencies. |
| Web export impossible with GDExtension | High (if needed) | Certain | If web is a target (Q-007), need alternative plan: WASM sim compiled separately, communicated via WebSocket. Adds complexity. |
| Debugging across Rust/GDScript boundary | Medium | Certain | Invest in logging/tracing infrastructure early. Rust tracing crate + Godot output. |
| "Two world" state sync bugs | Medium | High | Strict pattern: Rust is authoritative, Godot is dumb renderer. No game logic in GDScript. |
| Team onboarding (Rust + Godot + ECS) | Medium | Medium | Document patterns. Create template/boilerplate for adding new systems. |
6. Recommendations for the Settled Reach
Immediate Actions (v0.1 prototype)
-
Use gdext as the Godot-Rust bridge. It is the only maintained option for Godot 4. Pin to a specific version.
-
Use bevy_ecs standalone for the simulation. Not the full Bevy engine. Just
bevy_ecs(and optionallybevy_appfor the schedule/loop). This gives us the ECS, automatic parallelization, change detection, and entity relations. -
Define the bridge as a trait.
SimBridgewithsend_input()andreceive_snapshot(). ImplementLocalBridgefirst (channel-based, same process).NetworkBridgecomes when we need multiplayer. -
Run the simulation on a separate thread. Godot main thread handles rendering and input. Rust sim thread runs the ECS world. Communication via
crossbeamchannels. -
Observer snapshots are the ONLY data that crosses the bridge. No reaching into the ECS from GDScript. No Godot nodes influencing simulation directly. The snapshot is a plain data structure: visible entities with positions, appearances, states; fog grid; sound events; monologue text.
-
Keep the Godot project minimal. GDScript handles: input mapping, scene tree management (spawn/move/despawn sprites), UI rendering, audio playback, camera. Everything else is Rust.
Project Structure
commonwealth/
simulation/ # Pure Rust, no Godot dependency
Cargo.toml # depends on bevy_ecs
src/
lib.rs
ecs/ # Components, systems, resources
perception/ # LOS, shadowcasting, observer queries
world/ # Chunk generation, map data
storyteller/ # Event pacing AI
bridge/ # Rust GDExtension library
Cargo.toml # depends on simulation + godot (gdext)
src/
lib.rs # GDExtension entry point
local_bridge.rs # Channel-based LocalBridge
snapshot.rs # ObserverSnapshot -> Godot types conversion
client/ # Godot 4 project
project.godot
commonwealth.gdextension
scripts/ # GDScript for rendering, UI, input
scenes/ # Godot scenes
assets/ # Art, audio
Key insight: the simulation crate has ZERO dependency on Godot or gdext. It can be tested entirely with cargo test. The bridge crate is the thin adapter layer. This means:
- Simulation development and testing doesn't require opening Godot
- A different client (3D, terminal, web) could use the same simulation crate
- Multiplayer server is just the simulation crate + networking, no Godot
What This Architecture Gives Us
Mapping back to confirmed decisions:
| Decision | How This Architecture Supports It |
|---|---|
| D-010 Client-server | Simulation crate = server, Godot project = client. Separated at the crate level. |
| D-010 Deterministic | bevy_ecs with explicit system ordering. Input events are timestamped. |
| D-010 Information boundaries | Observer queries computed in Rust. Client only sees its snapshot. |
| D-010 No baked player identity | ECS entities are entities. Player-controlled is a component, not a special case. |
| D-011 Shadowcasting | Implemented as a Rust system. No Godot dependency. Testable independently. |
| D-012 Chunk-based maps | Chunk data lives in ECS resources. Loading/unloading is a system. |
| D-017 Perception modes | Each mode is an observer query function. Composable, testable, extensible. |
| D-018 Sound propagation | Sound system runs in Rust. Sound events in snapshot trigger Godot audio. |
| D-019 Top-down + future 3D | Client is a pure renderer. Swapping to 3D client means new Godot project, same simulation crate. |
7. Rating Summary
| Area | Rating | Confidence | Key Concern |
|---|---|---|---|
| gdext bindings | YELLOW | High | Pre-1.0 API churn. Workable with version pinning. |
| FFI performance | GREEN | High | PackedByteArray solves bulk transfer. Observer snapshot pattern limits data volume. |
| Rust threading | GREEN | High | Standard pattern, well-documented in community. |
| bevy_ecs standalone | GREEN | High | Explicitly supported. Perfect fit for headless simulation. |
| System parallelization | GREEN | High | Automatic in bevy_ecs. Critical for scaling NPC count. |
| Deterministic simulation | YELLOW | Medium | Achievable but requires discipline in system ordering. Must be tested continuously. |
| Bridge abstraction (local+network) | GREEN | High | Standard trait-based pattern. Well-understood in Rust ecosystem. |
| Serialization | GREEN | High | Multiple proven options. No serialization needed for local path. |
| Input/render flow | GREEN | High | Clean unidirectional flow. Channel-based. |
| Web export | RED | Certain | GDExtension cannot ship to web. Blocker if web is a target. |
| Mobile export | YELLOW | Medium | Possible but less tested. Needs investigation for Q-007. |
| Real-world validation | YELLOW | Medium | Pattern proven at small scale. No large production titles to reference. |
| Build pipeline | GREEN | High | Two-step build (cargo + godot). Automatable. |
| Developer experience | YELLOW | High | Hot-reload limitation. Two-language debugging. Manageable with good patterns. |
8. Open Questions for Team Discussion
-
Q-007 intersection: If web is a target platform, this architecture needs a significant adaptation (WASM sim + WebSocket bridge). Do we need web?
-
Tick rate: What simulation tick rate? 10Hz is fine for a strategy-paced game, 20Hz for real-time with pauses. This affects interpolation complexity and snapshot bandwidth.
-
Save/load: Serializing the entire bevy_ecs World is possible via
bevy_reflectbut not trivial. Needs prototyping. Alternative: serialize our own state representation. -
Mod support: If mods need to add new systems or components, bevy_ecs supports dynamic components. But modders writing Rust and recompiling is high-friction. GDScript-side mods are easier but limited to presentation. This tension needs resolution.
-
Godot version pinning: Should we target Godot 4.3 (stable) or track 4.4+? gdext compatibility varies by Godot version.
This evaluation recommends proceeding with the Godot 4 + Rust (gdext + bevy_ecs) architecture for the Settled Reach prototype. The architecture is sound, the tools are viable, and the risk profile is manageable. The primary investment is in defining clean abstractions early -- particularly the SimBridge trait and ObserverSnapshot format -- so that the inevitable gdext API churn and future multiplayer addition don't require rewrites.
-- TYRE, Technical Architect