Atom: Foundation for Deterministic Computing
Atom: Foundation for Deterministic Computing
Version: 1.0.0 Status: Production Last Updated: 2026-01-10
Overview
Atom is Orix’s foundation layer, providing deterministic primitives that produce identical results across all platforms, all runs, forever. It replaces non-deterministic standard library types with carefully engineered alternatives designed for replay systems, multiplayer synchronization, and rigorous testing.
The Problem
Standard computing primitives break determinism in three critical ways:
1. Floating-Point Non-Determinism
// Problem: Different results on different CPUsfloat x = 0.1f + 0.2f; // Intel: 0.30000001192 // ARM: 0.30000000000 (potentially)IEEE 754 floating-point arithmetic is platform-dependent. Different CPUs may:
- Use different rounding modes
- Implement fused multiply-add differently
- Handle denormals inconsistently
- Produce different results for transcendental functions
This breaks:
- Multiplayer games: Client predictions diverge from server
- Replay systems: Recorded inputs produce different outcomes
- Testing: Same test fails on different machines
2. Unordered Collection Iteration
// Problem: Iteration order varies between runsvar dict = new Dictionary<string, int>();dict["a"] = 1;dict["b"] = 2;
foreach (var kvp in dict) // Order: undefined{ ProcessEntity(kvp.Key); // Non-deterministic execution order}Dictionary<K,V> and HashSet<T> use hash codes for bucket placement. Hash codes vary:
- Between process runs (randomized to prevent DoS attacks)
- Between different implementations (.NET versions, platforms)
- Between 32-bit and 64-bit processes
This breaks:
- Deterministic simulation: Entity processing order affects results
- Network sync: Different iteration order on client vs server
- Testing: Tests pass/fail randomly
3. Non-Seedable Randomness
// Problem: Cannot reproduce the sequencevar random = new Random();int roll = random.Next(6); // Different every time, irreproducibleSystem.Random seeds from the system clock by default. Even with explicit seeds, implementation details vary across platforms and .NET versions.
This breaks:
- Procedural generation: Cannot recreate the same world
- Testing: Cannot reproduce random failures
- Replay: Cannot replay recorded sequences
How Atom Solves It
Atom provides deterministic alternatives built on three principles:
1. Fixed-Point Arithmetic (DFixed64)
Instead of floating-point, Atom uses Q32.32 signed fixed-point: 32 bits for the integer part, 32 bits for the fractional part.
// Internal representation: int64 where value = raw / 2^32struct DFixed64{ public readonly long Raw; // Bit-exact representation}Range: -2,147,483,648.0 to +2,147,483,647.999999999767 Precision: ~0.00000000023 (2^-32)
Why This Works
Fixed-point arithmetic is deterministic because:
- Integer operations are exactly specified by the C# standard
- No rounding modes, no platform differences
- Same bit patterns on all architectures
Performance Characteristics
| Operation | DFixed64 vs int64 | DFixed64 vs float |
|---|---|---|
| Add/Sub | ~1.0x (same) | ~1.0x |
| Multiply | ~2-3x slower | ~2x slower |
| Divide | ~3-5x slower | ~2x slower |
| Sqrt | ~8x slower | ~4x slower |
Trade-off: Correctness over raw speed. Games needing determinism already pay this cost in other ways (lockstep networking, replay buffers).
2. Stable-Iteration Collections
Atom’s collections maintain insertion order for deterministic iteration:
var map = new UnsafeMap<string, int>(16, Allocator.Persistent);map.Add("player1", 100);map.Add("player2", 200);map.Add("player3", 150);
// ALWAYS iterates in order: player1, player2, player3foreach (var kvp in map){ ProcessEntity(kvp.Key); // Deterministic execution order}Implementation: Open addressing with linear probing, entries stored in insertion order.
3. Seedable Deterministic PRNG
OrixRandom uses xorshift64 algorithm:
var rng = new OrixRandom(seed: 42);int roll1 = rng.NextInt(6); // Same seed = same sequence
var rng2 = new OrixRandom(seed: 42);int roll2 = rng2.NextInt(6); // roll1 == roll2, guaranteedState is serializable: save RNG state to replay from any point.
What Atom Provides
Core Types
DFixed64 - Fixed-Point Number
using Atom.Math;
// Constructionvar a = DFixed64.FromInt(5); // 5.0var b = DFixed64.FromDouble(3.14); // 3.14 (boundary only)var c = DFixed64.FromRaw(0x3_243F_6A88L); // Pi (exact)
// Arithmeticvar sum = a + b;var product = a * b;var quotient = a / b;
// Transcendentalvar sqrt = DFixed64.Sqrt(DFixed64.FromInt(2)); // 1.414...var sin = DFixed64.Sin(DFixed64.Pi / DFixed64.Two); // 1.0var (sinVal, cosVal) = DFixed64.SinCos(angle);
// Roundingvar floor = value.Floor(); // -2.7 → -3.0var ceil = value.Ceiling(); // 2.3 → 3.0var round = value.Round(); // 2.5 → 3.0var trunc = value.Truncate(); // -2.7 → -2.0
// ConstantsDFixed64.Zero, DFixed64.One, DFixed64.Pi, DFixed64.EDFixed64.MaxValue, DFixed64.MinValueBoundary conversions (FromDouble, ToDouble) are for external APIs only. Never use inside simulation.
DVector2, DVector3 - Deterministic Vectors
using Atom.Math;
// 2D vectorvar pos = new DVector2(DFixed64.FromInt(10), DFixed64.FromInt(20));var velocity = new DVector2(DFixed64.One, DFixed64.Zero);var newPos = pos + velocity * deltaTime;
var distance = DVector2.Distance(pos1, pos2);var dot = DVector2.Dot(velocity, normal);var normalized = velocity.Normalized;
// 3D vector (same API)var pos3d = new DVector3(x, y, z);var cross = DVector3.Cross(forward, up);var magnitude = pos3d.Magnitude;var sqrMag = pos3d.SqrMagnitude; // Avoids sqrt for comparisonsDQuaternion - Deterministic Rotation
using Atom.Math;
var rotation = DQuaternion.FromEuler(yaw, pitch, roll);var forward = rotation * DVector3.UnitZ;var interpolated = DQuaternion.Slerp(rot1, rot2, t);Entity - Deterministic Unique Identifier
using Atom.Primitives;
// Replaces Guid.NewGuid() with deterministic allocationvar entity = new Entity(index: 42, generation: 1);
// Packed format: 24-bit index + 8-bit generation = 32 bits// Max entities: 16,777,215// Generations: 256 (wraps)
// Check validityif (entity.IsValid){ int idx = entity.Index; byte gen = entity.Generation;}
// Recycling: increment generation when reusing slotsvar recycled = entity.NextGeneration();Why not GUIDs? Guid.NewGuid() is non-deterministic. Entity IDs are allocated from a pool in deterministic order.
Tick - Discrete Time
using Atom.Primitives;
// Replaces DateTime.Now with discrete simulation timevar currentTick = Tick.FromRaw(1000);var nextTick = currentTick.Next();var delta = nextTick - currentTick; // TickDelta
// Arithmeticvar future = currentTick + TickDelta.FromRaw(100);var past = currentTick - TickDelta.FromRaw(50);
// Comparisonif (eventTick > currentTick){ // Event in future}No wall-clock time in simulation. Time is purely discrete ticks.
OrixRandom - Seedable PRNG
using Atom.Math;
var rng = new OrixRandom(seed: 0xDEADBEEF);
// Integersint roll = rng.NextInt(6); // [0, 6)int damage = rng.NextInt(10, 20); // [10, 20)bool success = rng.NextBool();
// Fixed-pointDFixed64 percent = rng.NextFixed(); // [0.0, 1.0)DFixed64 speed = rng.NextFixed(minSpeed, maxSpeed);
// VectorsDVector2 direction = rng.NextUnitVector2(); // On unit circleDVector3 scatter = rng.NextInsideSphere(); // Inside unit sphere
// Shuffleint[] deck = new int[52];rng.Shuffle(deck);
// Save/restore stateulong state = rng.State;var restored = OrixRandom.FromState(state);Important: Same seed produces same sequence across all platforms. State is serializable for replay.
UnsafeMap<K, V> - Deterministic Dictionary
using Atom.Collections;
var map = new UnsafeMap<int, DVector3>(16, Allocator.Persistent);map.Add(1, position1);map.Add(2, position2);
// Lookupif (map.TryGetValue(1, out var pos)){ // Found}
// Iteration: ALWAYS in insertion orderforeach (var kvp in map){ ProcessEntity(kvp.Key, kvp.Value);}
map.Dispose(); // Manual memory managementKey difference from Dictionary: Iteration order is deterministic (insertion order).
UnsafeSet<T> - Deterministic HashSet
using Atom.Collections;
var visited = new UnsafeSet<Entity>(32, Allocator.Persistent);visited.Add(entity1);visited.Add(entity2);
if (visited.Contains(entity3)){ // Already visited}
// Iteration: ALWAYS in insertion orderforeach (var entity in visited){ ProcessEntity(entity);}
visited.Dispose();UnsafeList<T> - Pre-Allocated List
using Atom.Collections;
var entities = new UnsafeList<Entity>(1024, Allocator.Persistent);entities.Add(entity1);entities.Add(entity2);
for (int i = 0; i < entities.Count; i++){ ProcessEntity(entities[i]);}
entities.Clear(); // Keep allocationentities.Dispose(); // Free memoryUsed for hot paths where allocations must be avoided.
BitWriter / BitReader - Bit-Level Serialization
using Atom.Serialization;
// WriteSpan<byte> buffer = stackalloc byte[256];var writer = new BitWriter(buffer);
writer.WriteBool(true);writer.WriteBits(7, 3); // 7 in 3 bitswriter.WriteInt(42);writer.WriteFixed(DFixed64.Pi);writer.WriteEntity(entity);
int bytesWritten = writer.BytePosition;
// Readvar reader = new BitReader(buffer);bool flag = reader.ReadBool();uint value = reader.ReadBits(3);int number = reader.ReadInt();DFixed64 pi = reader.ReadFixed();Entity ent = reader.ReadEntity();Used for network serialization and replay recording.
Advantages
1. Bit-Identical Results
Same inputs produce exactly the same outputs on:
- Windows, Linux, macOS
- x86, x64, ARM, ARM64
- .NET 8, .NET 9, .NET 10…
- Debug vs Release builds
This enables:
- Lockstep networking: Clients run same simulation, sync inputs only
- Replay systems: Record inputs, replay identically
- Deterministic tests: Same test always passes or fails
- Procedural generation: Same seed creates same world
2. Predictable Performance
No surprises:
- Operations take consistent time
- No hidden allocations
- No garbage collection spikes
- Explicit memory management with Allocators
3. Foundation for Time-Travel Debugging
Chronicle (Orix’s time-travel system) relies on determinism:
- Record simulation inputs
- Replay to any point
- Step forward/backward
- Inspect state at any tick
Without Atom’s deterministic primitives, this is impossible.
Disadvantages
1. Reduced Numeric Range
| Type | Range | Precision |
|---|---|---|
float | ±3.4 × 10^38 | 7 digits |
double | ±1.7 × 10^308 | 15-16 digits |
DFixed64 | ±2.1 × 10^9 | ~9 digits |
DFixed64 cannot represent:
- Very large numbers (astronomy scales)
- Very small numbers (quantum scales)
- Arbitrary precision
Workaround: Use scaled representations (e.g., millimeters instead of meters for more precision).
2. Performance Overhead
Fixed-point operations are slower than hardware float:
- Multiplication: ~2-3x slower
- Division: ~3-5x slower
- Sqrt: ~4-8x slower
Mitigation:
- Use squared magnitude for distance comparisons
- Pre-compute expensive values
- Profile hot paths
3. Learning Curve
Developers familiar with floating-point must learn:
- Fixed-point limitations
- Explicit boundary conversions
- Manual memory management (Allocators)
- Deterministic thinking
Mitigation: Strong analyzer rules (ORIX001-008) catch mistakes at compile time.
Comparison to Alternatives
vs System.Numerics (Vector3, Vector4, Matrix4x4)
| System.Numerics | Atom |
|---|---|
Uses float | Uses DFixed64 |
| Hardware SIMD | Software operations |
| Fast but non-deterministic | Slower but deterministic |
| Good for rendering | Good for simulation |
When to use: Use System.Numerics for rendering pipeline, Atom for game logic.
vs Unity.Mathematics (float3, quaternion)
| Unity.Mathematics | Atom |
|---|---|
| Burst-compiled | Standard C# |
| Fast on Unity | Portable |
| Non-deterministic float | Deterministic fixed-point |
When to use: Atom is platform-agnostic, works outside Unity.
vs decimal (C# built-in)
| decimal | DFixed64 |
|---|---|
| 128-bit | 64-bit |
| Base-10 | Base-2 |
| Financial accuracy | Performance |
| ~10x slower | ~2-3x slower than float |
When to use: Use decimal for money, DFixed64 for physics/gameplay.
vs SoftFloat / LibFixedPoint
| SoftFloat | Atom |
|---|---|
| Software float emulation | Fixed-point |
| Slower (~100x) | Faster (~2-3x vs float) |
| Full float semantics | Limited range |
When to use: SoftFloat when you need float semantics. Atom for better performance.
Performance Benchmarks
Measured on Intel i7-12700K, .NET 8, Release build:
Operation | float (ns) | DFixed64 (ns) | Ratio-------------------|------------|---------------|-------Addition | 0.8 | 0.8 | 1.0xMultiplication | 1.2 | 2.8 | 2.3xDivision | 3.5 | 11.2 | 3.2xSqrt | 8.5 | 42.0 | 4.9xSin/Cos (lookup) | 12.0 | 18.0 | 1.5xVector3 Dot | 3.2 | 6.5 | 2.0xVector3 Normalize | 14.0 | 52.0 | 3.7xInterpretation:
- Simple ops (add/sub): negligible difference
- Complex ops (div/sqrt): 2-5x slower
- Hot loop with 1M iterations: ~2-4ms overhead
For a 60 FPS game (16.67ms budget), this is acceptable if correctness matters.
Code Examples
Example 1: Deterministic Physics
using Atom.Math;using Atom.Primitives;
public struct PhysicsBody{ public DVector3 Position; public DVector3 Velocity; public DFixed64 Mass;}
public void UpdatePhysics(ref PhysicsBody body, DFixed64 deltaTime){ // Gravity var gravity = new DVector3( DFixed64.Zero, DFixed64.FromDouble(-9.81), DFixed64.Zero );
// F = ma, a = F/m var acceleration = gravity;
// Integrate velocity body.Velocity = body.Velocity + acceleration * deltaTime;
// Integrate position body.Position = body.Position + body.Velocity * deltaTime;
// Result: Identical trajectory on all platforms}Example 2: Deterministic Pathfinding
using Atom.Collections;using Atom.Primitives;
public void FindPath(DVector3 start, DVector3 goal){ // Visited set maintains insertion order var visited = new UnsafeSet<Entity>(1024, Allocator.Temp); var open = new UnsafeList<PathNode>(256, Allocator.Temp);
// Process nodes in deterministic order while (open.Count > 0) { var current = open[0]; open.RemoveAt(0);
if (visited.Contains(current.Entity)) continue;
visited.Add(current.Entity);
// Get neighbors (deterministic order from grid) foreach (var neighbor in GetNeighbors(current.Entity)) { open.Add(neighbor); } }
visited.Dispose(); open.Dispose();
// Same start/goal = same path, always}Example 3: Deterministic Procedural Generation
using Atom.Math;
public class WorldGenerator{ private OrixRandom _rng;
public WorldGenerator(ulong seed) { _rng = new OrixRandom(seed); }
public Terrain GenerateTerrain(int x, int z) { // Re-seed for this chunk (deterministic) var chunkSeed = HashCoords(x, z); var chunkRng = new OrixRandom(chunkSeed);
var biome = chunkRng.NextInt(0, 4); var height = chunkRng.NextFixed(DFixed64.Zero, DFixed64.FromInt(100));
// Same coordinates = same terrain, always return new Terrain { Biome = biome, Height = height }; }
private ulong HashCoords(int x, int z) { // Deterministic hash return (ulong)x * 73856093 ^ (ulong)z * 19349663; }}When to Use [Ambient]
Some operations must be non-deterministic (external APIs, I/O). Mark them explicitly:
using Atom.Primitives;
[Ambient(Reason = "External API requires wall-clock timestamp")]public async Task<PaymentResult> ProcessPayment(decimal amount){ var timestamp = DateTime.UtcNow; // OK: marked [Ambient] return await _gateway.ChargeAsync(amount, timestamp);}
[Ambient(Reason = "File I/O for configuration")]public Config LoadConfig(){ return JsonSerializer.Deserialize<Config>(File.ReadAllText("config.json"));}Rules:
- NEVER use
[Ambient]in simulation code - ALWAYS provide a
Reason - Use only at system boundaries
Verification
Atom’s determinism is enforced by:
- Static Analysis: Roslyn analyzers detect forbidden patterns
- Runtime Testing: Arbiter runs determinism checks
- CI Enforcement: All PRs must pass determinism tests
# Check for determinism violationsdotnet build /warnaserror
# Run determinism testorix arbiter determinism
# Same seed, run 1000 times, assert identical resultsSummary
Atom provides the foundation for deterministic computing in Orix:
| Problem | Solution |
|---|---|
| Float non-determinism | DFixed64 Q32.32 fixed-point |
| Unordered iteration | UnsafeMap/UnsafeSet (insertion order) |
| Non-seedable random | OrixRandom (xorshift64) |
| Non-deterministic time | Tick (discrete simulation time) |
| GUID allocation | Entity (index + generation) |
Trade-offs:
- Reduced range (±2B instead of ±10^308)
- Slower operations (2-5x for mul/div/sqrt)
- Manual memory management
Benefits:
- Bit-identical results across all platforms
- Enables replay systems
- Enables lockstep networking
- Enables time-travel debugging
- Rigorous testability
Atom is the bedrock on which Flux (simulation), Nexus (networking), and Echo (replay) are built. Without deterministic primitives, none of these systems are possible.
Related Documents
- Axion Schema - Schema language built on Atom types
- Serialization - Binary encoding with BitWriter/BitReader
- Flux Simulation - ECS runtime using Atom primitives
- Executive Overview - Platform overview
Analyzer Rules
Orix analyzers enforce determinism:
| Rule | Description |
|---|---|
| ORIX001 | No float/double - use DFixed64 |
| ORIX002 | No DateTime.Now - use Tick |
| ORIX003 | No System.Random - use OrixRandom(seed) |
| ORIX004 | No Guid.NewGuid() - use Entity allocation |
| ORIX005 | No Dictionary<K,V> - use UnsafeMap<K,V> |
| ORIX006 | No HashSet<T> - use UnsafeSet<T> |
| ORIX007 | No async/await in simulation |
| ORIX008 | No ThreadPool/Task.Run in simulation |