In 1997, Cavedog’s Total Annihilation set a high-water mark for real-time strategy (RTS) architecture. While its contemporaries operated on flat 2D tilemaps with deterministic grid pathfinding and hard unit caps (often 200 units per player), Total Annihilation introduced true 3D heightfield terrain, physical ballistics, independent turret vector tracking, dynamic height-based visibility, and multi-thousand-unit battles.
However, its original monolithic x86 C++ engine was constrained by single-threaded processing, 32-bit memory address space, strict IEEE-754 floating-point assumptions across homogeneous x86 CPUs, and $O(N^2)$ spatial collision checks.
When re-architecting an engine inspired by Total Annihilation for modern heterogeneous high-concurrency systems (Modern for H—spanning multi-core x86_64, ARM64 Apple Silicon/Graviton, WebAssembly runtime targets, and distributed edge nodes), we face a radically different hardware profile:
- Massive core counts, shallow single-core frequency gains: Computing 100,000 independent physical projectiles and unit path updates per frame requires aggressive task-parallelism and Data-Oriented Design (DOD).
- Extreme memory latency penalties relative to CPU compute: Modern performance is gated by L1/L2/L3 cache misses. Classical Pointer-heavy Object-Oriented Hierarchies (e.g.,
class Unitcontaining pointer trees toWeapon,Physics, andPath) ruin hardware prefetchers. - Cross-Architecture Non-Determinism: Modern execution environments (e.g., SIMD AVX-512 vs. ARM Neon, WASM implementations, GPU compute pipelines) execute floating-point operations with subtle rounding, fused multiply-add (FMA) discrepancies, and x87 legacy register mismatches that cause lockstep desynchronization.
This blueprint details the architectural transition from legacy single-threaded RTS object-graphs to a deterministic, zero-allocation, cache-aligned Entity-Component-System (ECS) engine with fixed-point spatial hashing, tailored for modern high-concurrency workloads.
1. Memory Architecture: Object-Graph to Struct-of-Arrays (SoA) ECS
The original Total Annihilation object model allocated dynamic memory per entity on the heap. Inspecting unit structures meant jumping through pointer chains, incurring continuous L3 cache line invalidations.
To scale unit counts from $10^3$ to $10^5$ active entities (units, debris, dynamic heightfield terraforming vertices, and ballistic projectiles) while executing at a deterministic 60 Hz tick rate (16.66ms per tick execution budget), we migrate to a contiguous Struct-of-Arrays (SoA) memory layout.
Array-of-Structs (AoS) [Legacy - Cache Inefficient]:
[ Pos(x,y,z), Vel(x,y,z), HP, Armor, TargetID ] -> [ Pos(x,y,z), Vel(x,y,z), HP, Armor, TargetID ]
Cache line wasted on non-physics fields during kinematic updates.
Struct-of-Arrays (SoA) [Modern - Prefetcher Optimized]:
PositionsX: [ x0, x1, x2, x3, ... xN ] <-- Continuous 64-byte aligned vector
PositionsY: [ y0, y1, y2, y3, ... xN ]
VelocitiesX:[ vx0, vx1, vx2, ... vxN ]
VelocitiesY:[ vy0, vy1, vy2, ... vyN ]
By storing component data in contiguous arrays aligned to 64-byte boundaries (matching standard CPU cache line sizes), hardware instruction prefetchers stream data into vector registers (AVX2/AVX-512 or ARM Neon) with zero execution pipeline stalls.
Production Implementation: High-Performance Spatial ECS in Rust
The following Rust module defines a deterministic, lockstep-safe physics and dynamic spatial hash register designed for zero heap allocation during execution frames:
use std::simd::f32x8; // SIMD processing vector
pub const MAX_ENTITIES: usize = 131_072; // 128k Concurrent Dynamic Entities
pub const FIXED_POINT_SHIFT: i64 = 16;
pub const GRID_CELL_SIZE: i32 = 128; // Fixed-size spatial binning grid
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct FixedVec3 {
pub x: i64, // Q32.32 Fixed Point representation
pub y: i64,
pub z: i64,
}
impl FixedVec3 {
#[inline(always)]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self {
x: (x * ((1 << FIXED_POINT_SHIFT) as f64)) as i64,
y: (y * ((1 << FIXED_POINT_SHIFT) as f64)) as i64,
z: (z * ((1 << FIXED_POINT_SHIFT) as f64)) as i64,
}
}
}
/// Contiguous Struct-of-Arrays (SoA) Storage for Mass Ballistics & Units
pub struct BallisticRegistry {
pub pos_x: Vec<i64>,
pub pos_y: Vec<i64>,
pub pos_z: Vec<i64>,
pub vel_x: Vec<i64>,
pub vel_y: Vec<i64>,
pub vel_z: Vec<i64>,
pub active_flags: Vec<u8>, // Bitmask flags: 0x01 = active, 0x02 = terrain collision
pub entity_ids: Vec<u32>,
pub len: usize,
}
impl BallisticRegistry {
pub fn with_capacity(capacity: usize) -> Self {
Self {
pos_x: vec![0; capacity],
pos_y: vec![0; capacity],
pos_z: vec![0; capacity],
vel_x: vec![0; capacity],
vel_y: vec![0; capacity],
vel_z: vec![0; capacity],
active_flags: vec![0; capacity],
entity_ids: vec![0; capacity],
len: 0,
}
}
/// Ticks kinematic updates using fixed-point arithmetic without dynamic heap allocation
#[inline(always)]
pub fn tick_kinematics(&mut self, gravity_q16: i64) {
let count = self.len;
for i in 0..count {
if (self.active_flags[i] & 0x01) == 0 {
continue;
}
// Apply Velocity to Position
self.pos_x[i] += self.vel_x[i];
self.pos_y[i] += self.vel_y[i];
self.pos_z[i] += self.vel_z[i];
// Apply Deterministic Gravity Vector
self.vel_y[i] -= gravity_q16;
// Heightfield Floor Collision Check (Base zero plane)
if self.pos_y[i] <= 0 {
self.pos_y[i] = 0;
self.active_flags[i] |= 0x02; // Flag impact
}
}
}
}
2. Cross-Platform Deterministic State Synchronization
Traditional network engines stream client state via full authorative entity updates. However, for a high-concurrency engine supporting up to 100,000 units on screen, sending state updates for every entity over the wire requires multi-gigabit bandwidth.
The solution pioneered by Total Annihilation—and modernized here—is Lockstep State Machine Replication.
Client Input Vectors (Tick N) ---> Consensus Server Engine
|
Broadcast Order Array
|
+------------------+------------------+
| |
x86_64 Native Instance ARM64 WASM Edge Node
[Executes Tick N with Q32.32] [Executes Tick N with Q32.32]
| |
State Hash: 0xA4F9C211 State Hash: 0xA4F9C211
+------------------+------------------+
|
Verification Matched (0 Desync)
The Floating-Point Fallacy
Standard IEEE-754 dynamic floating-point math (f32/f64) cannot be guaranteed cross-platform.
- x86_64 SSE/AVX uses target-specific rounding modes and extended internal precision registers (80-bit x87 hardware legacy fallback).
- ARM64 Neon handles denormalized numbers differently by default (flush-to-zero vs. subnormal arithmetic).
- Wasm runtimes lack native 80-bit float guarantees and vary by browser host engine (V8, JavaScriptCore, SpiderMonkey).
Fixed-Point Math Engine Architecture
To eliminate non-determinism, all kinematic calculations, trajectory vectors, pathfinding cost calculations, and collision geometry MUST run on custom software Fixed-Point Arithmetic Engine ($Q32.32$).
/**
* Q32.32 Deterministic Fixed-Point Math Engine for Heterogeneous Multi-Platform Runtimes
* Precision: 1 / 4,294,967,296 (~2.32e-10)
*/
export class Fix64 {
public static readonly SHIFT = 32n;
public static readonly << Fix64.SHIFT;
/**
* Converts native IEEE float to deterministic 64-bit Fixed Point integer representation
*/
public static fromNumber(val: number): bigint {
return BigInt(Math.round(val * 4294967296));
}
/**
* Converts 64-bit Fixed Point integer back to native IEEE float for Render Thread Interpolation ONLY
*/
public static toNumber(val: bigint): number {
return Number(val) / 4294967296;
}
/**
* Fixed-Point Multiplication with Bit Shift Normalization
*/
#[inline]
public static multiply(a: bigint, b: bigint): bigint {
return (a * b) >> Fix64.SHIFT;
}
/**
* Fixed-Point Division with Shifted Precision
*/
#[inline]
public static divide(a: bigint, b: bigint): bigint {
if (b === 0n) throw new Error("Fix64: Division by Zero");
return (a << Fix64.SHIFT) / b;
}
}
/**
* Deterministic State Checksum Generator
* Executes every N ticks to verify sync parity across x86/ARM/Wasm targets
*/
export class DeterministicStateHasher {
private hash: bigint = 0xcbf29ce484222325n; // FNV-1a offset basis
private readonly FNV_PRIME = 0x100000001b3n;
public updateFix64(value: bigint): void {
// Break 64-bit integer into byte-wise updates
for (let i = 0; i < 8; i++) {
const byte = Number((value >> BigInt(i * 8)) & 0xffn);
this.hash ^= BigInt(byte);
this.hash = BigInt.asUintN(64, this.hash * this.FNV_PRIME);
}
}
public getHashHex(): string {
return this.hash.toString(16).padStart(16, "0");
}
}
3. Structural Comparison: Legacy vs Modern High-Concurrency Architecture
| Architectural Layer | Original Total Annihilation (1997) | Modern for H Architecture (2025) |
|---|---|---|
| Execution Paradigm | Single-Threaded Monolithic Engine Loop | Task-Based Multi-Threaded Worker Pipeline + WASM Edge |
| Memory Structure | Array-of-Structs (AoS) Heap Objects (new CUnit()) |
Struct-of-Arrays (SoA) SIMD-Aligned Contiguous Arrays |
| Spatial Indexing | Coarse 2D Uniform Tile Grid ($O(N^2)$ Local Queries) | Hierarchical Dynamic Bounding Volume Hierarchy (BVH) + Sparse Spatial Hashing |
| Arithmetic Model | Native x86 Floating-Point Hardware Unit (float) |
Software $Q32.32$ / $Q16.16$ Fixed-Point Engine |
| Network Engine | Monolithic Lockstep over DirectPlay / IPX | Deterministic Lockstep + Frame Rollback with State Vector Hash Hashing |
| Unit Scalability | Hard capped at ~200 - 5,000 global entities | Scalable to 100,000+ physical entities at 60 Hz tick |
| Terrain System | Static 2D Heightmap with basic slope arrays | Real-time Deformable 3D Voxels / Dynamic Heightfield Displacement |
4. Scaling Spatial Partitioning: Sparse Spatial Hashing
A naive spatial collision check executes in $O(N^2)$ time complexity. For 100,000 entities, this requires 10 billion comparisons per tick—completely unfeasible within a 16.6ms window. Modern spatial partitioning uses Sparse Spatial Hashing using continuous bitwise hashing, reducing space-query lookup complexity to $O(1)$ average time.
Spatial Hash Cell Mapping:
[World Space (X, Z)]
│
▼ Fixed Bit Shift (Spatial Resolution = 128 units)
[Cell X = PosX >> 7, Cell Z = PosZ >> 7]
│
▼ Spatial Hash Key Generation
Key = (CellX * 73856093) ^ (CellZ * 19349663)
│
▼ Direct Flat Index Lookup into Non-Allocating Bucket Memory
Bucket Index = Key % TotalBuckets
Spatial Grid Hashing Algorithm
Algorithm: Deterministic Zero-Allocation Spatial Partitioning
Input: Array of Positions (x: Fix64, z: Fix64), Active Entity Array, Cell Size S
Output: Dense Spatial Key Lookup Buffer
1. FOREACH Entity i in Active Entities DO:
2. cell_x = PosX[i] >> SHIFT_CELL_BITS
3. cell_z = PosZ[i] >> SHIFT_CELL_BITS
4. hash_key = (cell_x * 73856093) XOR (cell_z * 19349663) MASK (BUCKET_COUNT - 1)
5.
6. // Atomic or Lock-free append to Spatial Slot Array
7. slot_index = FetchAndIncrement(BucketHeadCounts[hash_key])
8. IF slot_index < MAX_PER_BUCKET THEN:
9. BucketEntities[hash_key][slot_index] = EntityID[i]
10. END IF
11. END FOREACH
This algorithm yields predictable, zero-heap-allocation spatial partitioning across arbitrary-sized map dimensions, handling massive dynamic projectile swarms without incurring memory fragmentation.
How BrickTry Helps You Build and Scale This
Rebuilding a complex, deterministic real-time engine requires rigorous memory profiling, low-level architecture verification, and precise state synchronization logic across web and edge environments. BrickTry accelerates the entire lifecycle of high-performance engineering projects like this through:
1. Interactive Zero-Setup Sandbox (/lab)
Test low-level WebAssembly engines, SharedArrayBuffer performance, and fixed-point state vector execution directly inside BrickTry's /lab virtual runtime. Prototype high-concurrency Rust/C++ implementations compiled to WASM alongside real-time canvas renderers without wasting hours on local toolchain setups.
2. Autonomous AI Dev Pairing Agents
BrickTry’s AI agents understand performance-critical systems. Automatically scaffold lockstep state hashing algorithms, translate floating-point math models into bit-exact $Q32.32$ software engines, and generate unit tests for cross-platform bitwise parity.
3. Senior Platform Engineering Pods
Low-level determinism bugs, SIMD instruction misalignments, and L3 cache thrashing require senior architectural oversight. BrickTry pairs your team with veteran principal systems engineers who perform in-depth code audits, memory layout analyses, and low-latency network architecture design.
4. Direct Repo Integration with 100% IP Ownership
All code scaffolded and refined within BrickTry commits directly to your private GitHub/GitLab repositories. You retain 100% intellectual property ownership with absolute zero platform lock-in—deploy your engine to raw metal, cloud instances, or WebAssembly edge nodes seamlessly.
Frequently Asked Questions
FAQ 1: Why use fixed-point arithmetic instead of native float types with strict compiler flags (e.g., -ffast-math disabled)?
Even with strict IEEE-754 compliance settings enabled in compilers like GCC or Clang, native hardware floating-point instructions behave differently across CPU architectures. For instance, x86_64 CPUs executing SSE2 instructions handle denormalized floating-point numbers differently than ARM Neon registers or WebAssembly runtimes. Floating-point operations can also yield subtle bit-level differences during dynamic intermediate calculations due to extended internal hardware registers (e.g., 80-bit x87 registers) or Fused Multiply-Add (FMA) optimizations. Software fixed-point math ($Q32.32$) guarantees identical bitwise results across every x86, ARM, and WebAssembly target.
FAQ 2: How does modern frame interpolation work when visual output requires floating-point coordinates for smooth rendering?
The simulation tick thread and render frame thread run completely decoupled. The simulation executes at a fixed, deterministic rate (e.g., 60 Hz) using $Q32.32$ fixed-point arithmetic. The render thread executes at the display's variable refresh rate (e.g., 144 Hz or 240 Hz). On every render frame, the client calculates an alpha blending interpolation factor:
$$\alpha = \frac{\text{Current Render Time} - \text{Last Tick Time}}{\text{Tick Duration}}$$
The visual renderer converts the previous and current fixed-point positions to native floating-point numbers ONLY inside the render loop, linearly interpolating ($\text{Position}{\text{render}} = \text{Pos}{\text{prev}} + \alpha \cdot (\text{Pos}{\text{curr}} - \text{Pos}{\text{prev}})$) for display purposes. The core simulation state remains purely integer-based and unpolluted.
FAQ 3: How does spatial hashing perform when thousands of units collapse into a single grid cell?
This is known as the "dense cell hotspot" edge case. When unit density within a single spatial cell exceeds safe thresholds, naive array buffers risk bucket overflow. To mitigate this without dynamic memory allocation, the engine utilizes a Two-Tier Hybrid Partitioning System:
- Global spatial queries use fixed-size, array-backed spatial hashes for single-lookup neighbor filtering.
- Cells that exceed a density threshold (e.g., >256 entities per bin) automatically register their local subgroup into a secondary Dynamic Bounding Volume Hierarchy (BVH) Tree that evaluates tight spatial bounds in $O(N \log N)$ time. This hybrid approach maintains bounded execution speed while preventing memory exhaustion.
Build, Test, and Scale This on BrickTry
BrickTry pairs you with autonomous AI scaffolding supervised by dedicated senior full-stack software engineers in an interactive in-browser development sandbox (/lab). Test, build, and deploy production-grade software with 100% source code ownership and zero vendor lock-in.