Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117package sim
// Sim_State is the authoritative gameplay state. If a value affects replay,// combat, collision, death, loot, or echo behavior, it belongs here or below here.Sim_State :: struct { tick: u64, rng: Rng_State, initial: Initial_State_Descriptor, world: World_State,}
// Initialization must be explicit and seed-driven so tests and replays can create// identical worlds without opening a window or touching platform APIs.sim_init :: proc( state: ^Sim_State, seed := DEFAULT_WORLD_SEED, initial := DEFAULT_INITIAL_STATE,) { state^ = Sim_State{ rng = rng_from_seed(seed), initial = initial, world = World_State{ seed = seed, arena = DEFAULT_ARENA, }, } world_spawn_player(&state.world, initial.player_position)}
// sim_tick is the only place gameplay time advances. It intentionally takes an// already-canonical command so hardware, frame time, and replay never enter sim.sim_tick :: proc(state: ^Sim_State, command: Input_Command) { sim_tick_with_archive(state, command, nil)}
sim_tick_with_archive :: proc( state: ^Sim_State, live_command: Input_Command, archive: ^Run_Archive,) { player := world_player(&state.world) if player != nil { apply_actor_command(player, live_command) }
for index in 0..<state.world.actor_count { actor := &state.world.actors[index] if actor.kind != .Echo || actor.echo.mode != .Following { continue }
command_count := run_archive_command_count_for_run(archive, actor.echo.source_run_id) if actor.echo.replay_tick >= command_count { actor.echo.mode = .Timeline_Ended continue }
echo_command, found := run_archive_command_at(archive, actor.echo.source_run_id, actor.echo.replay_tick) if !found { actor.echo.mode = .Timeline_Ended continue }
apply_actor_command(actor, echo_command) actor.echo.replay_tick += 1 if actor.echo.replay_tick >= command_count { actor.echo.mode = .Timeline_Ended } }
state.tick += 1}
apply_actor_command :: proc(actor: ^Actor_State, command: Input_Command) { delta := move_delta(command.move) actor.position.x += delta.x actor.position.y += delta.y}
sim_digest :: proc(state: ^Sim_State) -> u64 { hash := u64(14695981039346656037) hash = digest_mix(hash, u64(SIM_VERSION)) hash = digest_mix(hash, state.tick) hash = digest_mix(hash, state.rng.value) hash = digest_mix(hash, state.world.seed) hash = digest_mix(hash, u64(state.world.actor_count)) hash = digest_mix_fixed(hash, state.world.arena.min.x) hash = digest_mix_fixed(hash, state.world.arena.min.y) hash = digest_mix_fixed(hash, state.world.arena.max.x) hash = digest_mix_fixed(hash, state.world.arena.max.y) hash = digest_mix_fixed(hash, state.initial.player_position.x) hash = digest_mix_fixed(hash, state.initial.player_position.y) hash = digest_mix(hash, u64(state.world.player_id.index)) hash = digest_mix(hash, u64(state.world.player_id.generation)) for index in 0..<state.world.actor_count { actor := state.world.actors[index] hash = digest_mix(hash, u64(actor.id.index)) hash = digest_mix(hash, u64(actor.id.generation)) hash = digest_mix(hash, u64(actor.kind)) hash = digest_mix_fixed(hash, actor.position.x) hash = digest_mix_fixed(hash, actor.position.y) hash = digest_mix(hash, u64(actor.echo.source_run_id)) hash = digest_mix(hash, actor.echo.replay_tick) hash = digest_mix(hash, u64(actor.echo.mode)) } return hash}
digest_mix :: proc(hash, value: u64) -> u64 { return (hash ~ value) * 1099511628211}
digest_mix_fixed :: proc(hash: u64, value: Fixed) -> u64 { bits := u32(i32(value)) return digest_mix(hash, u64(bits))}