Skip to content

Recipes

This page shows practical, production-style patterns built on the current public API.

Recipe: Stable Chunk RNG from World Seed

#include <grimoire-pcg/random.h>

static hash_t hash_chunk_seed(hash_t worldSeed, int32_t chunkX, int32_t chunkY)
{
    /* Simple deterministic combiner for example purposes. */
    uint32_t h = (uint32_t)worldSeed;
    h ^= (uint32_t)chunkX * 0x9E3779B9u;
    h ^= (uint32_t)chunkY * 0x85EBCA6Bu;
    h ^= h >> 16;
    return (hash_t)h;
}

int generate_chunk_loot(hash_t worldSeed, int32_t chunkX, int32_t chunkY)
{
    hash_t chunkSeed = hash_chunk_seed(worldSeed, chunkX, chunkY);
    GrimoireRandom rng = GrimoireRandom_CreateSeed(chunkSeed);
    if (!rng) return -1;

    int lootTier = GrimoireRandom_NextRange(rng, 0, 5);
    GrimoireRandom_Destroy(rng);
    return lootTier;
}

Use this when each spatial chunk should be reproducible independently.

Recipe: Save/Load RNG Stream Position with Metadata

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <grimoire-pcg/random.h>

enum { RNG_STATE_SIZE = 228 };
enum { RNG_CHECKPOINT_VERSION = 1 };

typedef struct RngCheckpoint
{
    uint32_t version;
    uint32_t size;
    uint8_t state[RNG_STATE_SIZE];
} RngCheckpoint;

bool save_rng(GrimoireRandom rng, RngCheckpoint* outCp)
{
    if (!rng || !outCp) return false;

    outCp->version = RNG_CHECKPOINT_VERSION;
    outCp->size = RNG_STATE_SIZE;
    GrimoireRandom_Serialize(rng, outCp->state);
    return true;
}

GrimoireRandom load_rng(const RngCheckpoint* inCp)
{
    if (!inCp) return NULL;
    if (inCp->version != RNG_CHECKPOINT_VERSION) return NULL;
    if (inCp->size != RNG_STATE_SIZE) return NULL;

    return GrimoireRandom_Deserialize(inCp->state);
}

This lets save files fail safely if the checkpoint shape is not what you expect.

Recipe: 2D Height + Moisture Field for Biome Selection

#include <grimoire-pcg/noise.h>

typedef struct BiomeSample
{
    float height;
    float moisture;
} BiomeSample;

BiomeSample sample_biome_fields(float x, float y, hash_t worldSeed)
{
    GrimoireFractalSettings heightSet;
    heightSet.frequency = 0.008f;
    heightSet.octaves = 6;
    heightSet.lacunarity = 2.0f;
    heightSet.persistence = 0.5f;
    heightSet.staticSeed = true;

    GrimoireFractalSettings moistSet;
    moistSet.frequency = 0.014f;
    moistSet.octaves = 4;
    moistSet.lacunarity = 2.0f;
    moistSet.persistence = 0.55f;
    moistSet.staticSeed = true;

    BiomeSample out;
    out.height = Grimoire_Fbm(Grimoire_Perlin2D, x, y, 0.0f, worldSeed, &heightSet);
    out.moisture = Grimoire_Fbm(Grimoire_ValueSmooth2D, x, y, 0.0f, worldSeed + 101, &moistSet);
    return out;
}

Use separate frequencies and seeds to avoid strongly correlated biome axes.

Recipe: Animated Cloud Mask (Time as Z)

#include <grimoire-pcg/noise.h>

float cloud_mask(float x, float y, float tSeconds, hash_t seed)
{
    GrimoireFractalSettings s;
    s.frequency = 0.015f;
    s.octaves = 5;
    s.lacunarity = 2.0f;
    s.persistence = 0.5f;
    s.staticSeed = true;

    float z = tSeconds * 0.08f;
    return Grimoire_Billow(Grimoire_Perlin3D, x, y, z, seed, &s);
}

Sampling 3D noise with time in z gives smooth animation without frame-to-frame pops.

Recipe: Deterministic Byte Buffer Fill

#include <stdint.h>
#include <grimoire-pcg/random.h>

void fill_bytes(hash_t seed, uint8_t* buffer, size_t length)
{
    GrimoireRandom rng = GrimoireRandom_CreateSeed(seed);
    if (!rng) return;

    GrimoireRandom_NextBytes(rng, buffer, length);
    GrimoireRandom_Destroy(rng);
}

Use this for procedural data blobs, not security-sensitive entropy.

Recipe: Replay-Safe RNG Branching

#include <grimoire-pcg/random.h>

void generate_encounter_and_loot(hash_t seed)
{
    GrimoireRandom master = GrimoireRandom_CreateSeed(seed);
    if (!master) return;

    GrimoireRandom encounterRng = GrimoireRandom_Clone(master);
    GrimoireRandom lootRng = GrimoireRandom_Clone(master);

    if (encounterRng) {
        int enemyCount = GrimoireRandom_NextRange(encounterRng, 2, 8);
        (void)enemyCount;
    }

    if (lootRng) {
        int rarity = GrimoireRandom_NextRange(lootRng, 0, 1000);
        (void)rarity;
    }

    GrimoireRandom_Destroy(lootRng);
    GrimoireRandom_Destroy(encounterRng);
    GrimoireRandom_Destroy(master);
}

Branching from a shared snapshot keeps subsystems deterministic while allowing independent call counts.