diff --git a/core/include/vf/RandomTick.h b/core/include/vf/RandomTick.h new file mode 100644 index 0000000..8f69c5a --- /dev/null +++ b/core/include/vf/RandomTick.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "Math.h" + +namespace vf { + +// Deterministic random-tick position selector. For a chunk coordinate and an +// absolute world tick it yields a repeatable list of local cell positions, +// seeded purely by (worldSeed, chunkCoord, tick, index). Two selectors built +// with the same seed produce identical streams, so the same simulation replays +// bit for bit. Draws are independent per index and may repeat, matching how a +// real per-chunk random tick samples with replacement. +class RandomTick { +public: + RandomTick(uint32_t worldSeed = 1u, int ticksPerChunk = 3); + + int ticksPerChunk() const { return ticks; } + uint32_t worldSeed() const { return seed; } + + void sampleLocal(const Vec3i& chunkCoord, long long tick, std::vector& out) const; + std::vector sampleLocal(const Vec3i& chunkCoord, long long tick) const; + + void sampleGlobal(const Vec3i& chunkCoord, long long tick, std::vector& out) const; + std::vector sampleGlobal(const Vec3i& chunkCoord, long long tick) const; + + // Raw 64-bit value for (chunkCoord, tick, index) under this seed. Callers can + // roll their own seeded events off the same stream without touching layout. + uint64_t hash(const Vec3i& chunkCoord, long long tick, uint32_t index) const; + +private: + uint32_t seed; + int ticks; +}; + +} // namespace vf diff --git a/core/src/RandomTick.cpp b/core/src/RandomTick.cpp new file mode 100644 index 0000000..da3362b --- /dev/null +++ b/core/src/RandomTick.cpp @@ -0,0 +1,71 @@ +#include "vf/RandomTick.h" +#include "vf/Chunk.h" + +namespace vf { + +namespace { + +uint64_t mix64(uint64_t x) { + x += 0x9e3779b97f4a7c15ull; + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull; + x = (x ^ (x >> 27)) * 0x94d049bb133111ebull; + return x ^ (x >> 31); +} + +uint64_t fold(uint64_t acc, uint64_t value) { + return mix64(acc ^ (mix64(value) + 0x9e3779b97f4a7c15ull + (acc << 6) + (acc >> 2))); +} + +uint64_t asBits(int v) { + return static_cast(static_cast(v)); +} + +} // namespace + +RandomTick::RandomTick(uint32_t worldSeed, int ticksPerChunk) + : seed(worldSeed), ticks(ticksPerChunk < 0 ? 0 : ticksPerChunk) {} + +uint64_t RandomTick::hash(const Vec3i& chunkCoord, long long tick, uint32_t index) const { + uint64_t h = mix64(static_cast(seed) + 0x100000001b3ull); + h = fold(h, asBits(chunkCoord.x)); + h = fold(h, asBits(chunkCoord.y)); + h = fold(h, asBits(chunkCoord.z)); + h = fold(h, static_cast(tick)); + h = fold(h, static_cast(index)); + return h; +} + +void RandomTick::sampleLocal(const Vec3i& chunkCoord, long long tick, std::vector& out) const { + out.clear(); + out.reserve(static_cast(ticks)); + for (int i = 0; i < ticks; ++i) { + uint64_t h = hash(chunkCoord, tick, static_cast(i)); + int x = static_cast((h & 0xffffull) % static_cast(chunkSize)); + int y = static_cast(((h >> 21) & 0xffffull) % static_cast(chunkSize)); + int z = static_cast(((h >> 42) & 0xffffull) % static_cast(chunkSize)); + out.push_back(Vec3i{x, y, z}); + } +} + +std::vector RandomTick::sampleLocal(const Vec3i& chunkCoord, long long tick) const { + std::vector out; + sampleLocal(chunkCoord, tick, out); + return out; +} + +void RandomTick::sampleGlobal(const Vec3i& chunkCoord, long long tick, std::vector& out) const { + sampleLocal(chunkCoord, tick, out); + for (Vec3i& cell : out) { + cell.x += chunkCoord.x * chunkSize; + cell.y += chunkCoord.y * chunkSize; + cell.z += chunkCoord.z * chunkSize; + } +} + +std::vector RandomTick::sampleGlobal(const Vec3i& chunkCoord, long long tick) const { + std::vector out; + sampleGlobal(chunkCoord, tick, out); + return out; +} + +} // namespace vf