Add chance simulator toolbox and refine checklist interactions
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s
This commit is contained in:
parent
35fe877d58
commit
6b85e58765
25 changed files with 1518 additions and 18 deletions
|
|
@ -12,8 +12,9 @@ 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 { 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, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.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";
|
||||
|
||||
function createTextImportContext() {
|
||||
|
|
@ -34,6 +35,7 @@ function createTextImportContext() {
|
|||
normalizeEquipmentPlannerData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeRandomizerData,
|
||||
normalizeRngSimulatorData,
|
||||
normalizeUrl
|
||||
};
|
||||
}
|
||||
|
|
@ -284,6 +286,94 @@ test("randomizer text exchange round trips full configuration", () => {
|
|||
);
|
||||
});
|
||||
|
||||
test("rng simulator preserves rate precision and compacts storage", () => {
|
||||
assert.equal(normalizeRateString("2.500"), "2.500");
|
||||
assert.equal(normalizeRateString("0.001"), "0.001");
|
||||
assert.equal(normalizeRateString("0.000004%"), "0.000004");
|
||||
assert.equal(normalizeRateString("100.500"), "100");
|
||||
|
||||
const normalized = normalizeRngSimulatorData({
|
||||
activeRateId: "rate2",
|
||||
rates: [
|
||||
{ id: "rate1", label: "Drop rare", rate: "2.500" },
|
||||
{ id: "rate2", label: "Ultra rare", rate: "0.000004" },
|
||||
{ id: "bad", label: "", rate: "4" }
|
||||
],
|
||||
settings: { multiplier: 50, simulationDraws: 2000, perHour: 30, perDay: 120, perMonth: 3600 },
|
||||
history: [
|
||||
{ id: "h1", at: "2026-08-30T12:00:00.000Z", label: "Drop rare", rate: "2.500", multiplier: 50, draws: 100, successes: 4, fails: 400, longestFailStreak: 40 }
|
||||
]
|
||||
});
|
||||
|
||||
assert.equal(normalized.activeRateId, "rate2");
|
||||
assert.deepEqual(normalized.rates.map((rate) => [rate.label, rate.rate]), [["Drop rare", "2.500"], ["Ultra rare", "0.000004"]]);
|
||||
assert.equal(normalized.history, undefined);
|
||||
assert.equal(compactModuleDataForStorage("rngSimulator", normalizeRngSimulatorData({})), null);
|
||||
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).settings.multiplier, 50);
|
||||
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).settings.perMonth, 3600);
|
||||
assert.equal(compactModuleDataForStorage("rngSimulator", normalized).history, undefined);
|
||||
});
|
||||
|
||||
test("rng simulator calculates probability milestones and deterministic simulations", () => {
|
||||
assert.equal(calculateAtLeastOneProbability("100", 50), 1);
|
||||
assert.equal(calculateDrawsForChance("50", 0.5), 1);
|
||||
assert.equal(calculateDrawsForChance("1", 0.5), 69);
|
||||
assert.equal(getEffectiveRate("10", 50), 15);
|
||||
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 });
|
||||
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 });
|
||||
assert.equal(multipliedSummary.effectiveRate, 20);
|
||||
assert.equal(multipliedSummary.averageDraws, 5);
|
||||
|
||||
const result = simulateRngDraws("25", 8, (() => {
|
||||
const values = [0.1, 0.9, 0.2, 0.8, 0.7, 0.1, 0.6, 0.5];
|
||||
let index = 0;
|
||||
return () => values[index++];
|
||||
})());
|
||||
|
||||
assert.deepEqual(result, {
|
||||
draws: 8,
|
||||
successes: 3,
|
||||
fails: 5,
|
||||
observedRate: 37.5,
|
||||
longestFailStreak: 2
|
||||
});
|
||||
|
||||
const multipliedResult = simulateRngDraws("10", 4, (() => {
|
||||
const values = [0.15, 0.25, 0.05, 0.9];
|
||||
let index = 0;
|
||||
return () => values[index++];
|
||||
})(), 100);
|
||||
assert.equal(multipliedResult.successes, 2);
|
||||
});
|
||||
|
||||
test("rng simulator text exchange round trips rates and settings", () => {
|
||||
const context = createTextImportContext();
|
||||
const rngSimulator = normalizeRngSimulatorData({
|
||||
activeRateId: "rate2",
|
||||
rates: [
|
||||
{ id: "rate1", label: "Drop rare", rate: "2.500" },
|
||||
{ id: "rate2", label: "Ultra rare", rate: "0.000004" }
|
||||
],
|
||||
settings: { multiplier: 50, simulationDraws: 5000, perHour: 40, perDay: 120, perMonth: 3600 }
|
||||
});
|
||||
const exported = exportModuleText("rngSimulator", rngSimulator);
|
||||
assert.doesNotMatch(exported, /\[history\]/);
|
||||
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.id === imported.data.activeRateId)?.label, "Ultra rare");
|
||||
assert.equal(imported.data.settings.multiplier, 50);
|
||||
assert.equal(imported.data.settings.simulationDraws, 5000);
|
||||
assert.equal(imported.data.settings.perHour, 40);
|
||||
assert.equal(imported.data.history, undefined);
|
||||
});
|
||||
|
||||
test("notepad helpers keep empty html and invalid command states stable", () => {
|
||||
assert.equal(isHtmlEmpty("<p><br></p>", ""), true);
|
||||
assert.equal(isHtmlEmpty("<p> </p>", ""), true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue