Refine RNG simulator layout and simulation stats
All checks were successful
Deploy Sokko G / deploy (push) Successful in 18s

This commit is contained in:
Shinuwa 2026-09-05 00:19:08 +02:00
parent 055fb78d60
commit f8231c748f
11 changed files with 825 additions and 394 deletions

View file

@ -12,7 +12,7 @@ import { getMonthOccurrenceSegments, getWeekGridLayout, getWeekMinutePosition, l
import { appendChecklistItems, moveChecklistItem, moveChecklistItemToCategory, renameChecklistSection } from "../website/src/features/toolboxes/modules/checklistUtils.js";
import { formatUpdatedAt, isHtmlEmpty, queryBlockFormat, queryCommandState } from "../website/src/features/toolboxes/modules/notepadUtils.js";
import { createBalancedTeams, drawWeightedEntries, filterExclusionsForPlayers, getDiceShape, rollDice } from "../website/src/features/toolboxes/modules/randomizerUtils.js";
import { calculateAtLeastOneProbability, calculateDrawsForChance, calculateRngSummary, getEffectiveRate, normalizeRateString, simulateRngDraws } from "../website/src/features/toolboxes/modules/rngSimulatorUtils.js";
import { calculateAtLeastOneProbability, calculateDrawsForChance, calculateRngCostSummary, calculateRngSummary, getEffectiveRate, normalizeCostString, normalizeRateString, simulateRngDraws } from "../website/src/features/toolboxes/modules/rngSimulatorUtils.js";
import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js";
import { compactModuleDataForStorage, createToolboxExportPayload, moduleStorageKey, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeMemosData, normalizeModuleOrder, normalizeNotepadData, normalizeRandomizerData, normalizeRngSimulatorData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
import { applyGroupedReorderOperation, completeGroupOrder, getBoundaryItemId, getGroupedEntries, moveGroupOrder, moveGroupOrderToEnd, moveGroupOrderToStart, moveItem, moveItemGroup } from "../website/src/hooks/useGroupedReorder.js";
@ -300,8 +300,8 @@ test("rng simulator preserves rate precision and compacts storage", () => {
const normalized = normalizeRngSimulatorData({
activeRateId: "rate2",
rates: [
{ id: "rate1", label: "Drop rare", rate: "2.500", multiplier: 25, perHour: 10 },
{ id: "rate2", label: "Ultra rare", rate: "0.000004", multiplier: 50, perHour: 30, perDay: 120, perMonth: 3600 },
{ id: "rate1", label: "Drop rare", rate: "2.500", costPerDraw: "", multiplier: 25, occurrenceValue: 10, occurrenceUnit: "perHour" },
{ id: "rate2", label: "Ultra rare", rate: "0.000004", costPerDraw: "12,500", multiplier: 50, occurrenceValue: 120, occurrenceUnit: "perDay" },
{ id: "bad", label: "", rate: "4" }
],
settings: { simulationDraws: 2000 },
@ -312,11 +312,17 @@ test("rng simulator preserves rate precision and compacts storage", () => {
assert.equal(normalized.activeRateId, "rate2");
assert.deepEqual(normalized.rates.map((rate) => [rate.label, rate.rate]), [["Drop rare", "2.500"], ["Ultra rare", "0.000004"]]);
assert.deepEqual(normalized.rates.map((rate) => [rate.multiplier, rate.perHour, rate.perDay, rate.perMonth]), [[25, 10, 0, 0], [50, 30, 120, 3600]]);
assert.deepEqual(normalized.rates.map((rate) => rate.costPerDraw), ["", "12.500"]);
assert.deepEqual(normalized.rates.map((rate) => [rate.multiplier, rate.occurrenceValue, rate.occurrenceUnit]), [[25, 10, "perHour"], [50, 120, "perDay"]]);
assert.equal(normalized.history, undefined);
assert.equal(normalizeCostString("0"), "");
assert.equal(normalizeCostString("0012,500"), "12.500");
assert.equal(compactModuleDataForStorage("rngSimulator", normalizeRngSimulatorData({})), null);
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).rates[0].costPerDraw, undefined);
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).rates[1].costPerDraw, "12.500");
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).rates[1].multiplier, 50);
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).rates[1].perMonth, 3600);
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).rates[1].occurrenceValue, 120);
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).rates[1].occurrenceUnit, "perDay");
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).settings.simulationDraws, 2000);
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).history, undefined);
});
@ -329,13 +335,24 @@ test("rng simulator calculates probability milestones and deterministic simulati
assert.equal(getEffectiveRate("10", -50), 5);
assert.equal(getEffectiveRate("75", 100), 100);
const hourlySummary = calculateRngSummary({ rate: "25", simulationDraws: 8, perHour: 2, perDay: 0, perMonth: 0 });
const hourlySummary = calculateRngSummary({ rate: "25", simulationDraws: 8, occurrenceValue: 2, occurrenceUnit: "perHour" });
assert.equal(hourlySummary.timeByFrequency.hour, 2);
assert.equal(hourlySummary.timeByFrequency.day, 2 / 24);
assert.equal(hourlySummary.timeByFrequency.month, 2 / 24 / 30);
const multipliedSummary = calculateRngSummary({ rate: "10", multiplier: 100, simulationDraws: 1, perHour: 0, perDay: 0, perMonth: 0 });
const dailySummary = calculateRngSummary({ rate: "25", simulationDraws: 8, occurrenceValue: 2, occurrenceUnit: "perDay" });
assert.equal(dailySummary.timeByFrequency.hour, 48);
assert.equal(dailySummary.timeByFrequency.day, 2);
const multipliedSummary = calculateRngSummary({ rate: "10", multiplier: 100, simulationDraws: 1, occurrenceValue: 0, occurrenceUnit: "perHour" });
assert.equal(multipliedSummary.effectiveRate, 20);
assert.equal(multipliedSummary.averageDraws, 5);
const costSummary = calculateRngCostSummary({ costPerDraw: "12,5", averageDraws: 5, drawsForChance: { 50: 4, 90: 12 }, simulationDraws: 8, simulationSuccesses: 2 });
assert.equal(costSummary.costPerDraw, 12.5);
assert.equal(costSummary.averageSuccessCost, 62.5);
assert.equal(costSummary.targetCosts[50], 50);
assert.equal(costSummary.targetCosts[90], 150);
assert.equal(costSummary.simulationCostPerSuccess, 50);
assert.equal(calculateRngCostSummary({ costPerDraw: "12,5", averageDraws: 5, drawsForChance: {}, simulationDraws: 8, simulationSuccesses: 2, firstSuccessDraw: 3 }).simulationFirstSuccessCost, 37.5);
assert.equal(calculateRngCostSummary({ costPerDraw: "0", averageDraws: 5, drawsForChance: {}, simulationDraws: 8, simulationSuccesses: 2 }), null);
const result = simulateRngDraws("25", 8, (() => {
const values = [0.1, 0.9, 0.2, 0.8, 0.7, 0.1, 0.6, 0.5];
@ -348,7 +365,9 @@ test("rng simulator calculates probability milestones and deterministic simulati
successes: 3,
fails: 5,
observedRate: 37.5,
longestFailStreak: 2
longestFailStreak: 2,
longestSuccessStreak: 1,
firstSuccessDraw: 1
});
const multipliedResult = simulateRngDraws("10", 4, (() => {
@ -357,6 +376,8 @@ test("rng simulator calculates probability milestones and deterministic simulati
return () => values[index++];
})(), 100);
assert.equal(multipliedResult.successes, 2);
assert.equal(multipliedResult.longestSuccessStreak, 1);
assert.equal(multipliedResult.firstSuccessDraw, 1);
});
test("rng simulator text exchange round trips rates and settings", () => {
@ -364,8 +385,8 @@ test("rng simulator text exchange round trips rates and settings", () => {
const rngSimulator = normalizeRngSimulatorData({
activeRateId: "rate2",
rates: [
{ id: "rate1", label: "Drop rare", rate: "2.500", multiplier: 25, perHour: 20 },
{ id: "rate2", label: "Ultra rare", rate: "0.000004", multiplier: 50, perHour: 40, perDay: 120, perMonth: 3600 }
{ id: "rate1", label: "Drop rare", rate: "2.500", costPerDraw: "150", multiplier: 25, occurrenceValue: 20, occurrenceUnit: "perHour" },
{ id: "rate2", label: "Ultra rare", rate: "0.000004", costPerDraw: "", multiplier: 50, occurrenceValue: 120, occurrenceUnit: "perDay" }
],
settings: { simulationDraws: 5000 }
});
@ -374,11 +395,13 @@ test("rng simulator text exchange round trips rates and settings", () => {
const imported = importModuleText("rngSimulator", exported, context);
assert.equal(imported.ok, true);
assert.deepEqual(imported.data.rates.map((rate) => [rate.label, rate.rate]), [["Drop rare", "2.500"], ["Ultra rare", "0.000004"]]);
assert.equal(imported.data.rates.find((rate) => rate.label === "Drop rare")?.costPerDraw, "150");
assert.equal(imported.data.rates.find((rate) => rate.label === "Ultra rare")?.costPerDraw, "");
assert.equal(imported.data.rates.find((rate) => rate.id === imported.data.activeRateId)?.label, "Ultra rare");
assert.equal(imported.data.rates.find((rate) => rate.label === "Drop rare")?.multiplier, 25);
assert.equal(imported.data.rates.find((rate) => rate.label === "Ultra rare")?.multiplier, 50);
assert.equal(imported.data.rates.find((rate) => rate.label === "Ultra rare")?.perHour, 40);
assert.equal(imported.data.rates.find((rate) => rate.label === "Ultra rare")?.perMonth, 3600);
assert.equal(imported.data.rates.find((rate) => rate.label === "Ultra rare")?.occurrenceValue, 120);
assert.equal(imported.data.rates.find((rate) => rate.label === "Ultra rare")?.occurrenceUnit, "perDay");
assert.equal(imported.data.settings.simulationDraws, 5000);
assert.equal(imported.data.history, undefined);
});