···1414- Simulation framework in [sim](/src/sim)
1515- Library glue code in [lib](/src/lib)
16161717+## Performance
1818+1919+Performance is paramount. This project by necessity is going to be very compute heavy, given the way that we're handling echoes and deterministic, replayable gameplay.
2020+2121+For that to work at 60hz, we need to be always keeping in mind performance techniques, both for memory and speed.
2222+2323+We should always be preferring simple, readable code that doesn't deopt. Simple loops. Careful memory management. We should be writing code that will work well with performance awareness:
2424+2525+- SIMD
2626+- CPU cache
2727+- Parallelization
2828+2929+This requires upfront thinking and consideration as we build out, especially for core systems.
3030+1731## Notes
18321919-Use jujutsu (jj) for version control.
3333+- Use jujutsu (jj) for version control
3434+- Prefer literate programming styles (code should be readable and comments help tell the why/how alongside the code)
3535+3636+## External References
3737+3838+[Odin documentation](https://odin-lang.org/docs/overview/)
···11package sim
2233+SIM_VERSION :: u32(1)
44+35// The simulation owns gameplay time. Rendering may run faster, slower, or jitter,
46// but every gameplay decision should eventually reduce to integer ticks at this rate.
57SIM_HZ :: 60
···12141315// Seeds are part of the replay contract: same initial state, same inputs, same result.
1416DEFAULT_WORLD_SEED :: u64(0x7468616E61746F)
1717+1818+// Authoritative spatial state uses fixed-point integers. A power-of-two scale
1919+// keeps future conversion cheap and avoids division in the simulation hot path.
2020+FIXED_SHIFT :: 16
2121+FIXED_ONE :: Fixed(1 << FIXED_SHIFT)
2222+2323+PLAYER_MOVE_CARDINAL :: FIXED_ONE
2424+PLAYER_MOVE_DIAGONAL :: Fixed(46341) // round(FIXED_ONE / sqrt(2))
···11+package sim
22+33+// Fixed is the authoritative numeric type for sim-space positions. Keeping this
44+// distinct from raw integers makes accidental mixing of pixels, ticks, and fixed
55+// simulation units a compile-time error instead of a replay bug.
66+Fixed :: distinct i32
77+88+// Vec2_Fixed stores replay-relevant positions and deltas. It is deliberately
99+// integer-only; rendering can convert to floats later at the sim/render boundary.
1010+Vec2_Fixed :: struct {
1111+ x: Fixed,
1212+ y: Fixed,
1313+}
1414+1515+// Vec2_Q is reserved for quantized command vectors such as aim/controller input.
1616+// It keeps persistent commands compact and canonical without storing floats.
1717+Vec2_Q :: struct {
1818+ x: i16,
1919+ y: i16,
2020+}
2121+2222+// Convert whole simulation units into the fixed-point representation. This is a
2323+// shift because FIXED_ONE is a power of two, avoiding multiplication/division.
2424+fixed_from_whole :: proc(value: i32) -> Fixed {
2525+ return Fixed(value << FIXED_SHIFT)
2626+}
···11+package sim
22+33+// Rng_State is owned by Sim_State so all gameplay randomness is seed-driven and
44+// replayable. No gameplay code should use global/random platform state directly.
55+Rng_State :: struct {
66+ value: u64,
77+}
88+99+// Initialize the deterministic random stream from the run seed. A zero seed is
1010+// remapped so the generator always has a useful non-zero state to advance from.
1111+rng_from_seed :: proc(seed: u64) -> Rng_State {
1212+ state := seed
1313+ if state == 0 {
1414+ state = 0x9E3779B97F4A7C15
1515+ }
1616+ return Rng_State{value = state}
1717+}
1818+1919+// Advance and return the next deterministic random value. SplitMix64 is small,
2020+// fast, and stable enough for this substrate; if later gameplay needs separate
2121+// random domains, those should become explicit named streams in Sim_State.
2222+rng_next_u64 :: proc(rng: ^Rng_State) -> u64 {
2323+ rng.value += 0x9E3779B97F4A7C15
2424+ z := rng.value
2525+ z = (z ~ (z >> 30)) * 0xBF58476D1CE4E5B9
2626+ z = (z ~ (z >> 27)) * 0x94D049BB133111EB
2727+ return z ~ (z >> 31)
2828+}