Linked repository source
examples/rewind_lab/subsystem/rewind.odin
This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.
Download the original file · Return to the guide
// Package rewind owns a Box2D simulation with its own fixed tick, input log,
// bounded history, and exact restore. The tick and the state belong to this
// package, never to the engine: Godot later drives ticks and reads staged
// transforms, but it never owns simulation time.
//
// Imports are vendor:box2d and Odin core only — no Godot, Gododin, or
// Raylib, per the suite's pure-subsystem convention. One owned allocation
// per world (the World value itself, which embeds every ring and log it
// needs); Box2D's internal storage belongs to the C library and never
// flows through Odin allocators.
//
// Determinism discipline: fixed 60 Hz tick, f32 throughout, array-only
// iteration (no map iteration anywhere in the tick), one seeded RNG stream
// per world, and single-threaded solving — WorldDef's task callbacks stay
// unused, warm starting and sleeping stay disabled (see rebuild).
//
// Restore exactness is path-based, and that is a measured engine fact, not
// an assumption: Box2D's broadphase tree, pair ordering, and manifold
// history evolve as a world ages, so a world rebuilt and teleported into
// mid-history state diverges measurably from one that stepped there — the
// step is not a pure function of (transforms, velocities, types). What IS
// a pure function is the path from a rebuild: a world rebuilt from the
// spec and replayed to tick T is bitwise identical to any other world that
// reached T the same way. Restore therefore means: rebuild from the epoch
// spec and replay every tick from the epoch start. Cost is one rebuild
// plus (target - epoch) steps; the receipt records what that costs, and
// the design doc's anchor-spacing/cache follow-ups are what re-enters if
// scrubbing cost ever demands them.
//
// Body identity is ours, not Box2D's: dynamic bodies occupy slots
// 0..dynamic_count-1 (spec dynamics first in spec order, then spawned balls
// in spawn order). Box2D BodyId handles are per-world-build and are
// remapped on every rebuild, so commands always speak slot indices.
package rewind
import b2 "../box2d"
import "core:math"
import "core:mem"
// Fixed-tick constants. One tick is one b2.World_Step call at 60 Hz.
TICK_RATE :: 60
TICK_DT :: f32(1) / f32(TICK_RATE)
DEFAULT_SUB_STEPS :: i32(4)
MAX_SUB_STEPS :: i32(8)
// History bounds. The ring is the retained history window of per-tick
// digests; comparisons and the HUD's playhead read it.
DEFAULT_WINDOW_TICKS :: 3600
MIN_WINDOW_TICKS :: 60
MAX_WINDOW_TICKS :: 7200
// Roster bounds. Preset dynamics and spawned balls share the same slots,
// so the cap is total dynamic bodies, in the boid-admission style.
MAX_DYNAMIC_SLOTS :: 64
DEFAULT_BODY_CAP :: 48
MAX_PRESET_BODIES :: 48
MAX_PRESET_JOINTS :: 24
// Log bounds.
MAX_COMMANDS :: 16384
// Numeric admission bounds, boid style: they prevent finite-but-overflowing
// arithmetic, nothing more.
COORD_LIMIT :: f32(2048)
IMPULSE_LIMIT :: f32(10000)
DEFAULT_RESTITUTION_SCALE :: f32(1)
// The solver's 2D vector type, aliased for spec readability.
Vec2 :: b2.Vec2
// A contraption preset identifier. Presets are pure values of Spec_Preset,
// built identically on every call — they are the rebuild authority.
Preset_Id :: enum {
pyramid_stack,
newtons_cradle,
chain_bridge,
domino_spiral,
seesaw,
pendulum_chain,
}
PRESET_COUNT :: len(Preset_Id)
Spec_Shape_Kind :: enum {
box,
circle,
}
Spec_Body :: struct {
kind: Spec_Shape_Kind,
is_dynamic: bool,
hx, hy: f32, // box half extents
radius: f32, // circle radius
x, y: f32,
angle: f32,
density: f32,
friction: f32,
restitution: f32,
}
Spec_Joint_Kind :: enum {
revolute,
weld,
}
Spec_Joint :: struct {
kind: Spec_Joint_Kind,
a, b: i32, // indices into the preset's body list
anchor_a, anchor_b: Vec2,
enable_limit: bool,
lower_angle, upper_angle: f32,
}
// Spec_Preset is the construction record: bodies, then joints referencing
// body indices. The ground slab leads every preset by convention.
Spec_Preset :: struct {
id: Preset_Id,
gravity: Vec2,
body_count: i32,
dynamic_count: i32,
joint_count: i32,
bodies: [MAX_PRESET_BODIES]Spec_Body,
joints: [MAX_PRESET_JOINTS]Spec_Joint,
}
// Authored configuration, mirroring the Time_Lab properties one for one.
// Scalars only, with admission bounds in valid_config.
Config :: struct {
preset: Preset_Id,
seed: u32,
gravity: Vec2,
restitution_scale: f32,
sub_steps: i32,
window_ticks: i64,
body_cap: i32,
}
default_config :: proc() -> Config {
return {
preset = .pyramid_stack,
seed = u32(20260916),
gravity = {0, -10},
restitution_scale = DEFAULT_RESTITUTION_SCALE,
sub_steps = DEFAULT_SUB_STEPS,
window_ticks = DEFAULT_WINDOW_TICKS,
body_cap = DEFAULT_BODY_CAP,
}
}
valid_config :: proc(c: Config) -> bool {
if math.is_nan(c.gravity[0]) || math.is_nan(c.gravity[1]) || math.is_inf(c.gravity[0]) || math.is_inf(c.gravity[1]) {
return false
}
if math.abs(c.gravity[0]) > 1000 || math.abs(c.gravity[1]) > 1000 {
return false
}
return c.restitution_scale >= 0 && c.restitution_scale <= 1 &&
c.sub_steps >= 1 && c.sub_steps <= MAX_SUB_STEPS &&
c.window_ticks >= MIN_WINDOW_TICKS && c.window_ticks <= MAX_WINDOW_TICKS &&
c.body_cap >= 1 && c.body_cap <= MAX_DYNAMIC_SLOTS
}
// Commands are per-tick records; a replay is (epoch rebuild + command
// replay). Payloads reuse fields by kind: fling carries (x, y) as the world
// impulse; spawn_ball carries (x, y) as the world point.
Command_Kind :: enum {
fling,
freeze,
unfreeze,
spawn_ball,
switch_preset,
clear_spawned,
}
Command :: struct {
tick: i64,
kind: Command_Kind,
slot: i32,
x, y: f32,
preset: Preset_Id,
}
valid_command :: proc(c: Command) -> bool {
for v in ([2]f32{c.x, c.y}) {
if math.is_nan(v) || math.is_inf(v) || math.abs(v) > COORD_LIMIT {
return false
}
}
if c.kind == .fling {
for v in ([2]f32{c.x, c.y}) {
if math.abs(v) > IMPULSE_LIMIT {
return false
}
}
}
return c.tick >= 0 && int(c.slot) >= 0 && int(c.preset) < PRESET_COUNT
}
// One seeded RNG stream per world, carried across rebuilds verbatim so a
// replay consumes it in exactly the original order. A small mixed LCG;
// nothing here is statistical quality.
RNG :: distinct u64
rng_init :: proc(seed: u32) -> RNG {
return RNG(u64(seed) * 0x9E3779B97F4A7C15 ~ 0xD1B54A32D192ED03 + 1)
}
rng_next :: proc(r: ^RNG) -> u32 {
r^ = r^ * 6364136223846793005 + 1442695040888963407
return u32(r^ >> 33)
}
// Per-tick cheap state: the running digest chain, the spine every
// comparison reads. Parked velocities, cursor, and spawn bookkeeping are
// deliberately not copied per tick — restore reconstructs them by
// re-executing history, so per-tick copies would be dead weight.
Tick_Record :: struct {
digest: u64,
}
World :: struct {
allocator: mem.Allocator,
world_id: b2.WorldId,
config: Config,
spec: Spec_Preset,
rng: RNG,
tick: i64, // ticks executed; the playhead's exclusive end
// The epoch is the last rebuild point: the tick the current preset's
// world was constructed at, its preset, and the stream state at that
// moment. Exact restore replays from exactly here.
epoch_tick: i64,
epoch_preset: Preset_Id,
epoch_rng: RNG,
// Slot roster. Spec dynamics fill 0..spec_dynamic_count-1; spawned
// balls fill spec_dynamic_count..dynamic_count-1.
ids: [MAX_DYNAMIC_SLOTS]b2.BodyId,
dynamic_count: i32,
spec_dynamic_count: i32,
spawned_count: i32,
frozen: u64,
parked_vx: [MAX_DYNAMIC_SLOTS]f32,
parked_vy: [MAX_DYNAMIC_SLOTS]f32,
parked_w: [MAX_DYNAMIC_SLOTS]f32,
// Spawned balls' radii, remembered for the staged render layout and
// cleared on every rebuild; spec slots stay zero.
spawn_radii: [MAX_DYNAMIC_SLOTS]f32,
// Input log, sorted by tick and append-only except for the documented
// branch cut: appending at a tick that already has commands, or at any
// tick below the log's last, replaces that tick and the entire later
// branch (rewind-then-act semantics; see README). A preset switch
// clears the log entirely: the old branch's commands belong to the old
// epoch.
commands: [MAX_COMMANDS]Command,
command_count: int,
cursor: int, // first command not yet consumed by the tick loop
// History ring over the retained window. Record for tick t lives at
// ring[t % MAX_WINDOW_TICKS]; records exist for ticks
// [tick - ring_count, tick).
ring: [MAX_WINDOW_TICKS]Tick_Record,
ring_count: i64,
// Telemetry, owned by this package and reported by the eval/HUD.
rebuild_count: i64,
last_restore_steps: i64,
}
// world_create validates the configuration and builds the initial world at
// tick 0. The World value embeds all its tables, so this is the package's
// one warm allocation.
world_create :: proc(cfg: Config, allocator: mem.Allocator) -> ^World {
if allocator.procedure == nil || !valid_config(cfg) {
return nil
}
w, err := new(World, allocator)
if err != nil {
return nil
}
w^ = World{allocator = allocator, config = cfg}
w.epoch_preset = cfg.preset
w.epoch_rng = rng_init(cfg.seed)
w.rng = w.epoch_rng
if !rebuild(w, cfg.preset, 0) {
if b2.World_IsValid(w.world_id) {
b2.DestroyWorld(w.world_id)
}
mem.free(w, allocator)
return nil
}
return w
}
// world_destroy consumes the owner's pointer, per the suite's explicit
// teardown convention. Repeated cleanup is safe through that owner.
world_destroy :: proc(owner: ^^World) -> bool {
if owner == nil || owner^ == nil {
return true
}
w := owner^
if b2.World_IsValid(w.world_id) {
b2.DestroyWorld(w.world_id)
}
w.world_id = {}
allocator := w.allocator
w^ = World{}
if mem.free(w, allocator) != nil {
return false
}
owner^ = nil
return true
}
// rebuild tears down any live simulation and constructs the named preset
// fresh at the given epoch tick. The RNG stream is whatever the caller
// left in w.epoch_rng; rebuild never touches it. rebuild() is the exactness
// atom: every timeline this package certifies passes through it.
rebuild :: proc(w: ^World, preset: Preset_Id, epoch_tick: i64) -> bool {
if b2.World_IsValid(w.world_id) {
b2.DestroyWorld(w.world_id)
w.world_id = {}
}
spec := preset_build(preset)
def := b2.DefaultWorldDef()
// The authored configuration owns gravity; the spec's own gravity field
// stays a documentation default for preset consumers.
def.gravity = w.config.gravity
// Exact restore additionally requires each step to be a pure function
// of captured state: warm-start contact/joint impulses and per-body
// sleep timers are solver state a rebuild does not reconstruct, so
// both stay off. Cold-start solving and no sleeping cost some stack
// stability and settle speed — the receipt records what that costs —
// and buy bitwise replay.
world_id := b2.CreateWorld(def)
if !b2.World_IsValid(world_id) {
return false
}
b2.World_EnableWarmStarting(world_id, false)
b2.World_EnableSleeping(world_id, false)
w.world_id = world_id
w.spec = spec
w.dynamic_count = 0
w.spec_dynamic_count = spec.dynamic_count
w.spawned_count = 0
w.frozen = 0
for i in 0..<MAX_DYNAMIC_SLOTS {
w.parked_vx[i] = 0
w.parked_vy[i] = 0
w.parked_w[i] = 0
w.spawn_radii[i] = 0
}
w.tick = epoch_tick
w.epoch_tick = epoch_tick
w.epoch_preset = preset
w.rebuild_count += 1
// Bodies, in spec order. Dynamic bodies take slots in the same order.
ids: [MAX_PRESET_BODIES]b2.BodyId
for i in 0..<spec.body_count {
body := &spec.bodies[i]
body_def := b2.DefaultBodyDef()
if body.is_dynamic {
body_def.type = b2.BodyType.dynamicBody
} else {
body_def.type = b2.BodyType.staticBody
}
body_def.position = {body.x, body.y}
body_def.rotation = b2.MakeRot(body.angle)
id := b2.CreateBody(world_id, body_def)
if !b2.Body_IsValid(id) {
return false
}
ids[i] = id
shape_def := b2.DefaultShapeDef()
shape_def.material.friction = body.friction
shape_def.material.restitution = clamp01(body.restitution * w.config.restitution_scale)
shape_def.density = body.density
switch body.kind {
case .box:
polygon := b2.MakeBox(body.hx, body.hy)
if !b2.Shape_IsValid(b2.CreatePolygonShape(id, shape_def, &polygon)) {
return false
}
case .circle:
circle := b2.Circle{radius = body.radius}
if !b2.Shape_IsValid(b2.CreateCircleShape(id, shape_def, &circle)) {
return false
}
}
if body.is_dynamic {
w.ids[w.dynamic_count] = id
w.dynamic_count += 1
}
}
// Joints, resolved against the just-created ids.
for j in 0..<spec.joint_count {
joint := &spec.joints[j]
switch joint.kind {
case .revolute:
rev := b2.DefaultRevoluteJointDef()
rev.bodyIdA = ids[joint.a]
rev.bodyIdB = ids[joint.b]
rev.localAnchorA = joint.anchor_a
rev.localAnchorB = joint.anchor_b
rev.enableLimit = joint.enable_limit
rev.lowerAngle = joint.lower_angle
rev.upperAngle = joint.upper_angle
if !b2.Joint_IsValid(b2.CreateRevoluteJoint(world_id, rev)) {
return false
}
case .weld:
weld := b2.DefaultWeldJointDef()
weld.bodyIdA = ids[joint.a]
weld.bodyIdB = ids[joint.b]
weld.localAnchorA = joint.anchor_a
weld.localAnchorB = joint.anchor_b
if !b2.Joint_IsValid(b2.CreateWeldJoint(world_id, weld)) {
return false
}
}
}
// The cursor lands on the first command at or after the epoch tick.
w.cursor = first_command_at_or_after(w, epoch_tick)
return true
}
clamp01 :: proc(v: f32) -> f32 {
if v < 0 {
return 0
}
if v > 1 {
return 1
}
return v
}
first_command_at_or_after :: proc(w: ^World, tick: i64) -> int {
for i in 0..<w.command_count {
if w.commands[i].tick >= tick {
return i
}
}
return w.command_count
}
// append_command admits a command at the present or future playhead.
// Appending at a tick that already has commands, or below the log's last
// tick, performs the documented branch cut: that tick and every later
// command are replaced, which is rewind-then-act semantics. Editing the
// executed past (below the playhead) is refused.
append_command :: proc(w: ^World, cmd: Command) -> bool {
if w == nil || !valid_command(cmd) || cmd.tick < w.tick {
return false
}
if w.command_count >= MAX_COMMANDS {
return false
}
if w.command_count > 0 && cmd.tick < w.commands[w.command_count - 1].tick {
w.command_count = first_command_at_or_after(w, cmd.tick)
}
w.commands[w.command_count] = cmd
w.command_count += 1
if w.cursor > w.command_count {
w.cursor = w.command_count
}
return true
}
// advance executes one fixed tick: consume this tick's commands, step the
// solver, record the digest. It allocates nothing.
advance :: proc(w: ^World) -> bool {
if w == nil || !b2.World_IsValid(w.world_id) {
return false
}
for w.cursor < w.command_count && w.commands[w.cursor].tick == w.tick {
apply_command(w, &w.commands[w.cursor])
w.cursor += 1
}
b2.World_Step(w.world_id, TICK_DT, w.config.sub_steps)
w.tick += 1
w.ring[w.tick % MAX_WINDOW_TICKS] = Tick_Record{digest = state_digest(w)}
if w.ring_count < w.config.window_ticks {
w.ring_count += 1
}
return true
}
apply_command :: proc(w: ^World, cmd: ^Command) {
switch cmd.kind {
case .fling:
if int(cmd.slot) < int(w.dynamic_count) && b2.Body_IsValid(w.ids[cmd.slot]) {
b2.Body_ApplyLinearImpulseToCenter(w.ids[cmd.slot], {cmd.x, cmd.y}, true)
}
case .freeze:
slot := cmd.slot
mask := u64(1) << uint(slot)
if int(slot) < int(w.dynamic_count) && w.frozen & mask == 0 && b2.Body_IsValid(w.ids[slot]) {
v := b2.Body_GetLinearVelocity(w.ids[slot])
w.parked_vx[slot] = v[0]
w.parked_vy[slot] = v[1]
w.parked_w[slot] = b2.Body_GetAngularVelocity(w.ids[slot])
b2.Body_SetType(w.ids[slot], b2.BodyType.staticBody)
w.frozen |= mask
}
case .unfreeze:
slot := cmd.slot
mask := u64(1) << uint(slot)
if w.frozen & mask != 0 && int(slot) < int(w.dynamic_count) && b2.Body_IsValid(w.ids[slot]) {
b2.Body_SetType(w.ids[slot], b2.BodyType.dynamicBody)
b2.Body_SetLinearVelocity(w.ids[slot], {w.parked_vx[slot], w.parked_vy[slot]})
b2.Body_SetAngularVelocity(w.ids[slot], w.parked_w[slot])
w.frozen &= ~mask
}
case .spawn_ball:
if w.dynamic_count < w.config.body_cap {
radius := 0.25 + 0.15 * f32(rng_next(&w.rng)) / f32(0xFFFFFFFF)
body_def := b2.DefaultBodyDef()
body_def.type = b2.BodyType.dynamicBody
body_def.position = {cmd.x, cmd.y}
id := b2.CreateBody(w.world_id, body_def)
if b2.Body_IsValid(id) {
shape_def := b2.DefaultShapeDef()
shape_def.material.friction = 0.3
shape_def.material.restitution = clamp01(0.3 * w.config.restitution_scale)
shape_def.density = 1
circle := b2.Circle{radius = radius}
if b2.Shape_IsValid(b2.CreateCircleShape(id, shape_def, &circle)) {
w.ids[w.dynamic_count] = id
w.spawn_radii[w.dynamic_count] = radius
w.dynamic_count += 1
w.spawned_count += 1
}
}
}
case .switch_preset:
// A switch starts a new epoch at the current tick: the stream
// continues from where it is, stale history is dropped, and the
// log clears — commands addressed to the old epoch's slots do not
// survive it. Restores before this tick are refused (the old
// epoch is gone by design).
w.epoch_rng = w.rng
w.command_count = 0
w.cursor = 0
w.ring_count = 0
rebuild(w, cmd.preset, w.tick)
case .clear_spawned:
slot := w.dynamic_count - w.spawned_count
for s in slot..<w.dynamic_count {
if b2.Body_IsValid(w.ids[s]) {
b2.DestroyBody(w.ids[s])
}
w.frozen &= ~(u64(1) << uint(s))
}
w.dynamic_count = slot
w.spawned_count = 0
}
}
// state_digest is the running checksum: the identity inputs (tick, roster,
// frozen mask) plus every dynamic slot's full solver state, hashed with
// FNV-1a over raw f32 bits, bunny style.
state_digest :: proc(w: ^World) -> u64 {
hash := u64(0xcbf29ce484222325)
mix :: proc(hash: ^u64, v: f32) {
bits := transmute(u32)v
for shift in 0..<4 {
hash^ = (hash^ ~ u64((bits >> (u32(shift) * 8)) & 0xFF)) * 0x100000001b3
}
}
mix_u64 :: proc(hash: ^u64, v: u64) {
for shift in 0..<8 {
hash^ = (hash^ ~ ((v >> (u64(shift) * 8)) & 0xFF)) * 0x100000001b3
}
}
mix_u64(&hash, u64(w.tick))
mix_u64(&hash, u64(w.dynamic_count))
mix_u64(&hash, w.frozen)
for s in 0..<w.dynamic_count {
id := w.ids[s]
pos := b2.Body_GetPosition(id)
rot := b2.Body_GetRotation(id)
vel := b2.Body_GetLinearVelocity(id)
mix(&hash, pos[0])
mix(&hash, pos[1])
mix(&hash, rot.c)
mix(&hash, rot.s)
mix(&hash, vel[0])
mix(&hash, vel[1])
mix(&hash, b2.Body_GetAngularVelocity(id))
}
return hash
}
// rewind_to restores the world to an exact recorded tick: rebuild from the
// epoch spec and replay every recorded command from the epoch start. The
// restored state is bitwise identical to any world that reached the target
// the same way — including the un-rewound timeline this one came from.
// Cost is one rebuild plus (target - epoch) steps, reported in telemetry.
rewind_to :: proc(w: ^World, target: i64) -> bool {
if w == nil || target < 0 || target > w.tick {
return false
}
base := w.tick - w.ring_count
if target < base || target < w.epoch_tick {
return false
}
// Replay from the epoch. The stream resumes from the epoch's own
// state, and the rebuild wipes solver history the old world carried.
w.rng = w.epoch_rng
if !rebuild(w, w.epoch_preset, w.epoch_tick) {
return false
}
steps := target - w.epoch_tick
for steps > 0 {
if !advance(w) {
return false
}
steps -= 1
}
w.last_restore_steps = target - w.epoch_tick
return true
}
// oracle_restore is the elementwise oracle: recompute the target tick in a
// scratch world by replay from the epoch — the same path rewind_to takes,
// built independently — then compare the live world's roster elementwise,
// bitwise. The scratch is born straight at the epoch (create, then rebuild
// at the epoch tick with the epoch's stream): replaying a switch command
// would not work, because a pre-loaded switch erases the post-switch
// commands the live log carries.
oracle_restore :: proc(w: ^World, target: i64, scratch_allocator: mem.Allocator) -> bool {
if w == nil || target < 0 || target > w.tick || target < w.epoch_tick {
return false
}
scratch := world_create(w.config, scratch_allocator)
if scratch == nil {
return false
}
defer world_destroy(&scratch)
scratch.rng = w.epoch_rng
if !rebuild(scratch, w.epoch_preset, w.epoch_tick) {
return false
}
for i in 0..<w.command_count {
cmd := w.commands[i]
if cmd.tick < w.epoch_tick {
continue
}
if cmd.tick >= target {
break
}
if !append_command(scratch, cmd) {
return false
}
}
for scratch.tick < target {
if !advance(scratch) {
return false
}
}
return state_equal(w, scratch)
}
// state_equal compares two worlds elementwise, bitwise — the suite's
// payload-without-holding pattern applied to solver state.
state_equal :: proc(a, b: ^World) -> bool {
if a == nil || b == nil {
return a == b
}
if a.dynamic_count != b.dynamic_count || a.frozen != b.frozen || a.spawned_count != b.spawned_count {
return false
}
for s in 0..<a.dynamic_count {
pa, pb := b2.Body_GetPosition(a.ids[s]), b2.Body_GetPosition(b.ids[s])
ra, rb := b2.Body_GetRotation(a.ids[s]), b2.Body_GetRotation(b.ids[s])
va, vb := b2.Body_GetLinearVelocity(a.ids[s]), b2.Body_GetLinearVelocity(b.ids[s])
wa, wb := b2.Body_GetAngularVelocity(a.ids[s]), b2.Body_GetAngularVelocity(b.ids[s])
pairs := [8]f32{pa[0], pa[1], ra.c, ra.s, va[0], va[1], wa, 0}
pairb := [8]f32{pb[0], pb[1], rb.c, rb.s, vb[0], vb[1], wb, 0}
for i in 0..<7 {
if transmute(u32)pairs[i] != transmute(u32)pairb[i] {
return false
}
}
}
return true
}
// ring_digest returns the recorded digest for a tick inside the window.
ring_digest :: proc(w: ^World, tick: i64) -> (u64, bool) {
if w == nil {
return 0, false
}
base := w.tick - w.ring_count
if tick < base || tick >= w.tick {
return 0, false
}
return w.ring[tick % MAX_WINDOW_TICKS].digest, true
}
// pick_slot returns the dynamic slot nearest to (x, y) within max_dist, or
// -1 when none qualifies. This is the freeze gun's target query. The scan
// runs in slot order, and an equal distance never displaces an earlier
// slot, so ties resolve to the lowest slot deterministically. A radius
// whose square overflows to +Inf keeps working: every finite distance then
// compares below the bound and the nearest slot still wins.
pick_slot :: proc(w: ^World, x, y, max_dist: f32) -> i32 {
if w == nil || max_dist < 0 {
return -1
}
for v in ([3]f32{x, y, max_dist}) {
if math.is_nan(v) || math.is_inf(v) {
return -1
}
}
best := i32(-1)
best_d2 := max_dist * max_dist
for s in 0..<w.dynamic_count {
p := b2.Body_GetPosition(w.ids[s])
dx, dy := p[0] - x, p[1] - y
d2 := dx*dx + dy*dy
if d2 <= best_d2 && (best < 0 || d2 < best_d2) {
best, best_d2 = s, d2
}
}
return best
}
// slot_state writes one dynamic slot's solver state as seven floats:
// x, y, rot.c, rot.s, vx, vy, w. This is simulation truth for tests and
// oracles; MultiMesh packing is the authored class's earned-bulk job and
// lives there, not here.
slot_state :: proc(w: ^World, slot: i32, sink: []f32) -> bool {
if w == nil || slot < 0 || int(slot) >= int(w.dynamic_count) || len(sink) < 7 {
return false
}
pos := b2.Body_GetPosition(w.ids[slot])
rot := b2.Body_GetRotation(w.ids[slot])
vel := b2.Body_GetLinearVelocity(w.ids[slot])
sink[0], sink[1] = pos[0], pos[1]
sink[2], sink[3] = rot.c, rot.s
sink[4], sink[5] = vel[0], vel[1]
sink[6] = b2.Body_GetAngularVelocity(w.ids[slot])
return true
}