Mapwright v0.5.0 — procedural battle map generator for Foundry VTT

Buildings (modern/fantasy, multi-floor, footprint shapes), caves, outdoor
biomes, and town/village + city-block settlements. Auto-places Foundry walls,
doors, windows, lighting; furniture as locked tiles; live preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-06-17 15:05:25 +01:00
co-authored by Claude Opus 4.8
commit 100e514c4d
28 changed files with 3771 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
/**
* Seeded pseudo-random number generation.
*
* Pure, dependency-free, and Foundry-agnostic so it can be unit-tested in Node.
* Same seed string always produces the same map.
*/
/** Hash an arbitrary string into a 32-bit unsigned integer seed (xfnv1a). */
export function hashSeed(input) {
const str = String(input ?? "");
let h = 2166136261 >>> 0;
for (let i = 0; i < str.length; i += 1) {
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619);
}
// Final avalanche so similar strings diverge.
h += h << 13; h ^= h >>> 7;
h += h << 3; h ^= h >>> 17;
h += h << 5;
return h >>> 0;
}
/** mulberry32 — small, fast, good-enough PRNG. Returns a function -> float in [0,1). */
export function mulberry32(seed) {
let a = seed >>> 0;
return function next() {
a |= 0;
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/**
* Convenience wrapper around a raw float generator with the helpers a
* generator actually needs.
*/
export class Rng {
constructor(seed) {
this._next = mulberry32(typeof seed === "number" ? seed : hashSeed(seed));
}
/** Float in [0, 1). */
float() {
return this._next();
}
/** Float in [min, max). */
range(min, max) {
return min + this._next() * (max - min);
}
/** Integer in [min, max] inclusive. */
int(min, max) {
return Math.floor(min + this._next() * (max - min + 1));
}
/** True with probability p (0..1). */
chance(p) {
return this._next() < p;
}
/** Pick a uniformly random element from a non-empty array. */
pick(arr) {
return arr[Math.floor(this._next() * arr.length)];
}
/** Pick by weight. items: [{weight, ...}] -> returns one item. */
weighted(items) {
const total = items.reduce((sum, it) => sum + (it.weight ?? 1), 0);
let roll = this._next() * total;
for (const it of items) {
roll -= it.weight ?? 1;
if (roll < 0) return it;
}
return items[items.length - 1];
}
/** In-place Fisher–Yates shuffle, returns the same array. */
shuffle(arr) {
for (let i = arr.length - 1; i > 0; i -= 1) {
const j = Math.floor(this._next() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
}