{
  "note": "Shared simulation contract for the zoen-sim crate (M0-03, ADR-024 and ADR-029): fixed tick, integer units, determinism rules and the golden-vector files the C# prediction subset or the client plugin (M0-09, M0-11) must match. Movement numbers come from attributes.json and world.json; this file holds only the simulation's own rules.",
  "tickHz": 20,
  "tickSource": "docs/tech/NETCODE_1000.md (20 Hz = 50 ms tick) and glossary.json Tick",
  "units": {
    "position": "integer millimetres (i32), region-local from world.json region.origin; positions stay inside region.sizeM until the walkability grid exists",
    "time": "ticks (u32, wraps after about 6.8 years at 20 Hz)",
    "speed": "millimetres per tick, from the m/s value in data rounded half away from zero",
    "moveInput": "two i16 axes; only the direction counts and the server moves at the authoritative speed"
  },
  "determinism": [
    "Integer fixed-point only inside the step: no f32/f64, no clock, no I/O, no threads and no randomly seeded hashing. The crate is no_std (alloc only), so the compiler keeps them out.",
    "Inputs apply in slice order, actors move in index order, and integer division truncates toward zero (the same in Rust and C#).",
    "No heap allocation per tick: state and event buffers are sized at creation; a full event buffer counts dropped events instead of growing.",
    "The state hash is FNV-1a 64 over the little-endian layout listed in the vectors file, taken after every tick."
  ],
  "inputRules": [
    "Sequence numbers start at 1. An input is accepted only when its sequence number is greater than the actor's last accepted one; duplicates and older (reordered) inputs are rejected with an event.",
    "A move intent persists until the actor's next accepted input."
  ],
  "golden": {
    "vectors": "packages/sim-golden/vectors.json",
    "schema": "packages/sim-golden/vectors.schema.json",
    "write": "pnpm sim:golden",
    "check": "pnpm sim:test"
  }
}
