fix: add runId to seed calculations and use seeded random for treasure loot
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
Fixes #299: Seed calculation now includes runId component per spec (seed = floor × 12345 + runId) Fixes #298: Treasure loot now uses seeded random instead of Math.random() Changes: - Added runId field to CombatState type - Generated random runId on spire entry in createEnterSpireMode - Updated getRoomsForFloor, generateSpireRoomType, generateSpireFloorState, generateTreasureLoot to accept and use runId - Updated all call sites in combat-descent-actions.ts and combatStore.ts - Treasure loot item count now uses seeded RNG instead of Math.random()
This commit is contained in:
@@ -39,15 +39,13 @@ function makeSeededRandom(seed: number): () => number {
|
||||
// Deterministic per floor via seed = floor × 12345 + runId.
|
||||
// Guardian floors always return 1.
|
||||
|
||||
export function getRoomsForFloor(floor: number, seed?: number): number {
|
||||
export function getRoomsForFloor(floor: number, seed: number): number {
|
||||
if (isGuardianFloor(floor)) return 1;
|
||||
const base = 5;
|
||||
const floorBonus = Math.min(10, Math.floor(floor / 20));
|
||||
|
||||
// Use seeded random if a seed is provided, otherwise fall back to Math.random
|
||||
const randomVariation = seed !== undefined
|
||||
? Math.floor(makeSeededRandom(seed)() * 3)
|
||||
: Math.floor(Math.random() * 3);
|
||||
// Use seeded random; seed should be floor × 12345 + runId per spec
|
||||
const randomVariation = Math.floor(makeSeededRandom(seed)() * 3);
|
||||
|
||||
return base + floorBonus + randomVariation;
|
||||
}
|
||||
@@ -62,6 +60,7 @@ export function generateSpireRoomType(
|
||||
floor: number,
|
||||
roomIndex: number,
|
||||
totalRooms: number,
|
||||
runId: number = 0,
|
||||
): SpireRoomType {
|
||||
// Last room on guardian floors is always guardian
|
||||
if (isGuardianFloor(floor) && roomIndex === totalRooms - 1) {
|
||||
@@ -70,18 +69,18 @@ export function generateSpireRoomType(
|
||||
|
||||
// Override: every 7th floor, one room (chosen by seed) is always 'puzzle'
|
||||
if (floor % 7 === 0) {
|
||||
const puzzleIndex = Math.floor(makeSeededRandom(floor * 12345 + 7)() * totalRooms);
|
||||
const puzzleIndex = Math.floor(makeSeededRandom(floor * 12345 + runId + 7)() * totalRooms);
|
||||
if (roomIndex === puzzleIndex) {
|
||||
return 'puzzle';
|
||||
}
|
||||
}
|
||||
|
||||
// Base roll
|
||||
const roll = makeSeededRandom(floor * 1000 + roomIndex)();
|
||||
// Base roll — seed includes runId for per-run variety
|
||||
const roll = makeSeededRandom(floor * 1000 + roomIndex + runId)();
|
||||
|
||||
if (roll < 0.10) {
|
||||
// Rare roll — secondary roll determines sub-type
|
||||
const rareRoll = makeSeededRandom(floor * 1000 + roomIndex + 9999)();
|
||||
const rareRoll = makeSeededRandom(floor * 1000 + roomIndex + runId + 9999)();
|
||||
if (rareRoll < 0.40) return 'recovery';
|
||||
if (rareRoll < 0.70) return 'treasure';
|
||||
return 'library';
|
||||
@@ -93,8 +92,8 @@ export function generateSpireRoomType(
|
||||
|
||||
// ─── Floor State Generation ───────────────────────────────────────────────────
|
||||
|
||||
export function generateSpireFloorState(floor: number, roomIndex: number, totalRooms: number): FloorState {
|
||||
const roomType = generateSpireRoomType(floor, roomIndex, totalRooms);
|
||||
export function generateSpireFloorState(floor: number, roomIndex: number, totalRooms: number, runId: number = 0): FloorState {
|
||||
const roomType = generateSpireRoomType(floor, roomIndex, totalRooms, runId);
|
||||
const element = getFloorElement(floor);
|
||||
const baseHP = getFloorMaxHP(floor);
|
||||
|
||||
@@ -131,7 +130,7 @@ export function generateSpireFloorState(floor: number, roomIndex: number, totalR
|
||||
case 'puzzle': {
|
||||
const puzzleKeys = Object.keys(PUZZLE_ROOMS);
|
||||
const puzzleIdx = Math.floor(
|
||||
makeSeededRandom(floor * 1000 + roomIndex + 5000)() * puzzleKeys.length,
|
||||
makeSeededRandom(floor * 1000 + roomIndex + runId + 5000)() * puzzleKeys.length,
|
||||
);
|
||||
const selectedPuzzle = puzzleKeys[puzzleIdx];
|
||||
const puzzle = PUZZLE_ROOMS[selectedPuzzle];
|
||||
@@ -162,7 +161,7 @@ export function generateSpireFloorState(floor: number, roomIndex: number, totalR
|
||||
};
|
||||
|
||||
case 'treasure': {
|
||||
const loot = generateTreasureLoot(floor);
|
||||
const loot = generateTreasureLoot(floor, runId);
|
||||
return {
|
||||
roomType: 'treasure',
|
||||
enemies: [],
|
||||
@@ -279,18 +278,21 @@ export function calcInsight(floor: number, isGuardian: boolean): number {
|
||||
* Generate treasure loot for a treasure room based on floor.
|
||||
* Returns pre-generated loot drops that are progressively revealed.
|
||||
*/
|
||||
export function generateTreasureLoot(floor: number): LootDrop[] {
|
||||
export function generateTreasureLoot(floor: number, runId: number = 0): LootDrop[] {
|
||||
const available = getAvailableDrops(floor, false);
|
||||
const drops: LootDrop[] = [];
|
||||
|
||||
// Use seeded random for deterministic loot
|
||||
const rng = makeSeededRandom(floor * 12345 + runId + 31337);
|
||||
|
||||
// Determine item count based on floor
|
||||
let itemCount: number;
|
||||
if (floor <= 10) {
|
||||
itemCount = 2 + Math.floor(Math.random() * 2); // 2-3
|
||||
itemCount = 2 + Math.floor(rng() * 2); // 2-3
|
||||
} else if (floor <= 50) {
|
||||
itemCount = 4 + Math.floor(Math.random() * 4); // 4-7
|
||||
itemCount = 4 + Math.floor(rng() * 4); // 4-7
|
||||
} else {
|
||||
itemCount = 8 + Math.floor(Math.random() * 8); // 8-15
|
||||
itemCount = 8 + Math.floor(rng() * 8); // 8-15
|
||||
}
|
||||
|
||||
// Roll for each item
|
||||
|
||||
Reference in New Issue
Block a user