From 2b63a4f0f6ccecd1e77211525cd6aa5893d1b528 Mon Sep 17 00:00:00 2001 From: Markus Date: Tue, 19 Aug 2025 06:23:16 +0000 Subject: [PATCH 01/84] feat: mod selection now recursive --- .../generated/precalculatedModCombinations.ts | 886 +-- ...precalculatedModCombinationsWithTunings.ts | 5060 +++++++++++++++++ src/app/services/results-builder.worker.ts | 185 +- 3 files changed, 5556 insertions(+), 575 deletions(-) create mode 100644 src/app/data/generated/precalculatedModCombinationsWithTunings.ts diff --git a/src/app/data/generated/precalculatedModCombinations.ts b/src/app/data/generated/precalculatedModCombinations.ts index d8285391..ab6f7328 100644 --- a/src/app/data/generated/precalculatedModCombinations.ts +++ b/src/app/data/generated/precalculatedModCombinations.ts @@ -17,565 +17,567 @@ // Dicts: [artifice, minor, major, total] -export const precalculatedModCombinations: { [key: number]: [number, number, number, number][] } = { +export const precalculatedModCombinations: { + [key: number]: [number, number, number, number, number, number][]; +} = { 1: [ - [1, 0, 0, 3], - [0, 1, 0, 5], - [0, 0, 1, 10], + [1, 0, 0, 0, 0, 3], + [0, 1, 0, 0, 0, 5], + [0, 0, 1, 0, 0, 10], ], 2: [ - [1, 0, 0, 3], - [0, 1, 0, 5], - [0, 0, 1, 10], + [1, 0, 0, 0, 0, 3], + [0, 1, 0, 0, 0, 5], + [0, 0, 1, 0, 0, 10], ], 3: [ - [1, 0, 0, 3], - [0, 1, 0, 5], - [0, 0, 1, 10], + [1, 0, 0, 0, 0, 3], + [0, 1, 0, 0, 0, 5], + [0, 0, 1, 0, 0, 10], ], 4: [ - [0, 1, 0, 5], - [2, 0, 0, 6], - [0, 0, 1, 10], + [0, 1, 0, 0, 0, 5], + [2, 0, 0, 0, 0, 6], + [0, 0, 1, 0, 0, 10], ], 5: [ - [0, 1, 0, 5], - [2, 0, 0, 6], - [0, 0, 1, 10], + [0, 1, 0, 0, 0, 5], + [2, 0, 0, 0, 0, 6], + [0, 0, 1, 0, 0, 10], ], 6: [ - [2, 0, 0, 6], - [1, 1, 0, 8], - [0, 2, 0, 10], - [0, 0, 1, 10], + [2, 0, 0, 0, 0, 6], + [1, 1, 0, 0, 0, 8], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], ], 7: [ - [1, 1, 0, 8], - [3, 0, 0, 9], - [0, 2, 0, 10], - [0, 0, 1, 10], + [1, 1, 0, 0, 0, 8], + [3, 0, 0, 0, 0, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], ], 8: [ - [1, 1, 0, 8], - [3, 0, 0, 9], - [0, 2, 0, 10], - [0, 0, 1, 10], + [1, 1, 0, 0, 0, 8], + [3, 0, 0, 0, 0, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], ], 9: [ - [3, 0, 0, 9], - [0, 2, 0, 10], - [0, 0, 1, 10], - [2, 1, 0, 11], + [3, 0, 0, 0, 0, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [2, 1, 0, 0, 0, 11], ], 10: [ - [0, 2, 0, 10], - [0, 0, 1, 10], - [2, 1, 0, 11], - [4, 0, 0, 12], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [2, 1, 0, 0, 0, 11], + [4, 0, 0, 0, 0, 12], ], 11: [ - [2, 1, 0, 11], - [4, 0, 0, 12], - [1, 2, 0, 13], - [1, 0, 1, 13], - [0, 3, 0, 15], - [0, 1, 1, 15], - [0, 0, 2, 20], + [2, 1, 0, 0, 0, 11], + [4, 0, 0, 0, 0, 12], + [1, 2, 0, 0, 0, 13], + [1, 0, 1, 0, 0, 13], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [0, 0, 2, 0, 0, 20], ], 12: [ - [4, 0, 0, 12], - [1, 2, 0, 13], - [1, 0, 1, 13], - [3, 1, 0, 14], - [0, 3, 0, 15], - [0, 1, 1, 15], - [0, 0, 2, 20], + [4, 0, 0, 0, 0, 12], + [1, 2, 0, 0, 0, 13], + [1, 0, 1, 0, 0, 13], + [3, 1, 0, 0, 0, 14], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [0, 0, 2, 0, 0, 20], ], 13: [ - [1, 2, 0, 13], - [1, 0, 1, 13], - [3, 1, 0, 14], - [5, 0, 0, 15], - [0, 3, 0, 15], - [0, 1, 1, 15], - [0, 0, 2, 20], + [1, 2, 0, 0, 0, 13], + [1, 0, 1, 0, 0, 13], + [3, 1, 0, 0, 0, 14], + [5, 0, 0, 0, 0, 15], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [0, 0, 2, 0, 0, 20], ], 14: [ - [3, 1, 0, 14], - [5, 0, 0, 15], - [0, 3, 0, 15], - [0, 1, 1, 15], - [2, 2, 0, 16], - [2, 0, 1, 16], - [0, 0, 2, 20], + [3, 1, 0, 0, 0, 14], + [5, 0, 0, 0, 0, 15], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [2, 2, 0, 0, 0, 16], + [2, 0, 1, 0, 0, 16], + [0, 0, 2, 0, 0, 20], ], 15: [ - [5, 0, 0, 15], - [0, 3, 0, 15], - [0, 1, 1, 15], - [2, 2, 0, 16], - [2, 0, 1, 16], - [4, 1, 0, 17], - [0, 0, 2, 20], + [5, 0, 0, 0, 0, 15], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [2, 2, 0, 0, 0, 16], + [2, 0, 1, 0, 0, 16], + [4, 1, 0, 0, 0, 17], + [0, 0, 2, 0, 0, 20], ], 16: [ - [2, 2, 0, 16], - [2, 0, 1, 16], - [4, 1, 0, 17], - [1, 3, 0, 18], - [1, 1, 1, 18], - [0, 4, 0, 20], - [0, 2, 1, 20], - [0, 0, 2, 20], + [2, 2, 0, 0, 0, 16], + [2, 0, 1, 0, 0, 16], + [4, 1, 0, 0, 0, 17], + [1, 3, 0, 0, 0, 18], + [1, 1, 1, 0, 0, 18], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], ], 17: [ - [4, 1, 0, 17], - [1, 3, 0, 18], - [1, 1, 1, 18], - [3, 2, 0, 19], - [3, 0, 1, 19], - [0, 4, 0, 20], - [0, 2, 1, 20], - [0, 0, 2, 20], + [4, 1, 0, 0, 0, 17], + [1, 3, 0, 0, 0, 18], + [1, 1, 1, 0, 0, 18], + [3, 2, 0, 0, 0, 19], + [3, 0, 1, 0, 0, 19], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], ], 18: [ - [1, 1, 1, 18], - [1, 3, 0, 18], - [3, 2, 0, 19], - [3, 0, 1, 19], - [5, 1, 0, 20], - [0, 4, 0, 20], - [0, 2, 1, 20], - [0, 0, 2, 20], + [1, 1, 1, 0, 0, 18], + [1, 3, 0, 0, 0, 18], + [3, 2, 0, 0, 0, 19], + [3, 0, 1, 0, 0, 19], + [5, 1, 0, 0, 0, 20], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], ], 19: [ - [3, 2, 0, 19], - [3, 0, 1, 19], - [5, 1, 0, 20], - [0, 4, 0, 20], - [0, 2, 1, 20], - [0, 0, 2, 20], - [2, 3, 0, 21], - [2, 1, 1, 21], + [3, 2, 0, 0, 0, 19], + [3, 0, 1, 0, 0, 19], + [5, 1, 0, 0, 0, 20], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [2, 3, 0, 0, 0, 21], + [2, 1, 1, 0, 0, 21], ], 20: [ - [5, 1, 0, 20], - [0, 4, 0, 20], - [0, 2, 1, 20], - [0, 0, 2, 20], - [2, 3, 0, 21], - [2, 1, 1, 21], - [4, 2, 0, 22], - [4, 0, 1, 22], + [5, 1, 0, 0, 0, 20], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [2, 3, 0, 0, 0, 21], + [2, 1, 1, 0, 0, 21], + [4, 2, 0, 0, 0, 22], + [4, 0, 1, 0, 0, 22], ], 21: [ - [2, 3, 0, 21], - [2, 1, 1, 21], - [4, 2, 0, 22], - [4, 0, 1, 22], - [1, 4, 0, 23], - [1, 2, 1, 23], - [1, 0, 2, 23], - [0, 5, 0, 25], - [0, 3, 1, 25], - [0, 1, 2, 25], - [0, 0, 3, 30], + [2, 3, 0, 0, 0, 21], + [2, 1, 1, 0, 0, 21], + [4, 2, 0, 0, 0, 22], + [4, 0, 1, 0, 0, 22], + [1, 4, 0, 0, 0, 23], + [1, 2, 1, 0, 0, 23], + [1, 0, 2, 0, 0, 23], + [0, 5, 0, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [0, 0, 3, 0, 0, 30], ], 22: [ - [4, 2, 0, 22], - [4, 0, 1, 22], - [1, 4, 0, 23], - [1, 2, 1, 23], - [1, 0, 2, 23], - [3, 3, 0, 24], - [3, 1, 1, 24], - [0, 5, 0, 25], - [0, 3, 1, 25], - [0, 1, 2, 25], - [0, 0, 3, 30], + [4, 2, 0, 0, 0, 22], + [4, 0, 1, 0, 0, 22], + [1, 4, 0, 0, 0, 23], + [1, 2, 1, 0, 0, 23], + [1, 0, 2, 0, 0, 23], + [3, 3, 0, 0, 0, 24], + [3, 1, 1, 0, 0, 24], + [0, 5, 0, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [0, 0, 3, 0, 0, 30], ], 23: [ - [1, 4, 0, 23], - [1, 2, 1, 23], - [1, 0, 2, 23], - [3, 3, 0, 24], - [3, 1, 1, 24], - [5, 2, 0, 25], - [0, 5, 0, 25], - [5, 0, 1, 25], - [0, 3, 1, 25], - [0, 1, 2, 25], - [0, 0, 3, 30], + [1, 4, 0, 0, 0, 23], + [1, 2, 1, 0, 0, 23], + [1, 0, 2, 0, 0, 23], + [3, 3, 0, 0, 0, 24], + [3, 1, 1, 0, 0, 24], + [5, 2, 0, 0, 0, 25], + [0, 5, 0, 0, 0, 25], + [5, 0, 1, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [0, 0, 3, 0, 0, 30], ], 24: [ - [3, 3, 0, 24], - [3, 1, 1, 24], - [5, 2, 0, 25], - [0, 5, 0, 25], - [5, 0, 1, 25], - [0, 3, 1, 25], - [0, 1, 2, 25], - [2, 4, 0, 26], - [2, 2, 1, 26], - [2, 0, 2, 26], - [0, 0, 3, 30], + [3, 3, 0, 0, 0, 24], + [3, 1, 1, 0, 0, 24], + [5, 2, 0, 0, 0, 25], + [0, 5, 0, 0, 0, 25], + [5, 0, 1, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [2, 4, 0, 0, 0, 26], + [2, 2, 1, 0, 0, 26], + [2, 0, 2, 0, 0, 26], + [0, 0, 3, 0, 0, 30], ], 25: [ - [5, 2, 0, 25], - [0, 5, 0, 25], - [5, 0, 1, 25], - [0, 3, 1, 25], - [0, 1, 2, 25], - [2, 4, 0, 26], - [2, 2, 1, 26], - [2, 0, 2, 26], - [4, 3, 0, 27], - [4, 1, 1, 27], - [0, 0, 3, 30], + [5, 2, 0, 0, 0, 25], + [0, 5, 0, 0, 0, 25], + [5, 0, 1, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [2, 4, 0, 0, 0, 26], + [2, 2, 1, 0, 0, 26], + [2, 0, 2, 0, 0, 26], + [4, 3, 0, 0, 0, 27], + [4, 1, 1, 0, 0, 27], + [0, 0, 3, 0, 0, 30], ], 26: [ - [2, 4, 0, 26], - [2, 2, 1, 26], - [2, 0, 2, 26], - [4, 3, 0, 27], - [4, 1, 1, 27], - [1, 5, 0, 28], - [1, 3, 1, 28], - [1, 1, 2, 28], - [0, 4, 1, 30], - [0, 2, 2, 30], - [0, 0, 3, 30], + [2, 4, 0, 0, 0, 26], + [2, 2, 1, 0, 0, 26], + [2, 0, 2, 0, 0, 26], + [4, 3, 0, 0, 0, 27], + [4, 1, 1, 0, 0, 27], + [1, 5, 0, 0, 0, 28], + [1, 3, 1, 0, 0, 28], + [1, 1, 2, 0, 0, 28], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], ], 27: [ - [4, 3, 0, 27], - [4, 1, 1, 27], - [1, 5, 0, 28], - [1, 3, 1, 28], - [1, 1, 2, 28], - [3, 4, 0, 29], - [3, 2, 1, 29], - [3, 0, 2, 29], - [0, 4, 1, 30], - [0, 2, 2, 30], - [0, 0, 3, 30], + [4, 3, 0, 0, 0, 27], + [4, 1, 1, 0, 0, 27], + [1, 5, 0, 0, 0, 28], + [1, 3, 1, 0, 0, 28], + [1, 1, 2, 0, 0, 28], + [3, 4, 0, 0, 0, 29], + [3, 2, 1, 0, 0, 29], + [3, 0, 2, 0, 0, 29], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], ], 28: [ - [1, 5, 0, 28], - [1, 3, 1, 28], - [1, 1, 2, 28], - [3, 4, 0, 29], - [3, 2, 1, 29], - [3, 0, 2, 29], - [5, 3, 0, 30], - [5, 1, 1, 30], - [0, 4, 1, 30], - [0, 2, 2, 30], - [0, 0, 3, 30], + [1, 5, 0, 0, 0, 28], + [1, 3, 1, 0, 0, 28], + [1, 1, 2, 0, 0, 28], + [3, 4, 0, 0, 0, 29], + [3, 2, 1, 0, 0, 29], + [3, 0, 2, 0, 0, 29], + [5, 3, 0, 0, 0, 30], + [5, 1, 1, 0, 0, 30], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], ], 29: [ - [3, 4, 0, 29], - [3, 2, 1, 29], - [3, 0, 2, 29], - [5, 3, 0, 30], - [5, 1, 1, 30], - [0, 4, 1, 30], - [0, 2, 2, 30], - [0, 0, 3, 30], - [2, 5, 0, 31], - [2, 3, 1, 31], - [2, 1, 2, 31], + [3, 4, 0, 0, 0, 29], + [3, 2, 1, 0, 0, 29], + [3, 0, 2, 0, 0, 29], + [5, 3, 0, 0, 0, 30], + [5, 1, 1, 0, 0, 30], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [2, 5, 0, 0, 0, 31], + [2, 3, 1, 0, 0, 31], + [2, 1, 2, 0, 0, 31], ], 30: [ - [5, 3, 0, 30], - [5, 1, 1, 30], - [0, 4, 1, 30], - [0, 2, 2, 30], - [0, 0, 3, 30], - [2, 5, 0, 31], - [2, 3, 1, 31], - [2, 1, 2, 31], - [4, 4, 0, 32], - [4, 2, 1, 32], - [4, 0, 2, 32], + [5, 3, 0, 0, 0, 30], + [5, 1, 1, 0, 0, 30], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [2, 5, 0, 0, 0, 31], + [2, 3, 1, 0, 0, 31], + [2, 1, 2, 0, 0, 31], + [4, 4, 0, 0, 0, 32], + [4, 2, 1, 0, 0, 32], + [4, 0, 2, 0, 0, 32], ], 31: [ - [2, 5, 0, 31], - [2, 3, 1, 31], - [2, 1, 2, 31], - [4, 4, 0, 32], - [4, 2, 1, 32], - [4, 0, 2, 32], - [1, 4, 1, 33], - [1, 2, 2, 33], - [1, 0, 3, 33], - [0, 3, 2, 35], - [0, 1, 3, 35], - [0, 0, 4, 40], + [2, 5, 0, 0, 0, 31], + [2, 3, 1, 0, 0, 31], + [2, 1, 2, 0, 0, 31], + [4, 4, 0, 0, 0, 32], + [4, 2, 1, 0, 0, 32], + [4, 0, 2, 0, 0, 32], + [1, 4, 1, 0, 0, 33], + [1, 2, 2, 0, 0, 33], + [1, 0, 3, 0, 0, 33], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [0, 0, 4, 0, 0, 40], ], 32: [ - [4, 4, 0, 32], - [4, 2, 1, 32], - [4, 0, 2, 32], - [1, 4, 1, 33], - [1, 2, 2, 33], - [1, 0, 3, 33], - [3, 5, 0, 34], - [3, 3, 1, 34], - [3, 1, 2, 34], - [0, 3, 2, 35], - [0, 1, 3, 35], - [0, 0, 4, 40], + [4, 4, 0, 0, 0, 32], + [4, 2, 1, 0, 0, 32], + [4, 0, 2, 0, 0, 32], + [1, 4, 1, 0, 0, 33], + [1, 2, 2, 0, 0, 33], + [1, 0, 3, 0, 0, 33], + [3, 5, 0, 0, 0, 34], + [3, 3, 1, 0, 0, 34], + [3, 1, 2, 0, 0, 34], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [0, 0, 4, 0, 0, 40], ], 33: [ - [1, 4, 1, 33], - [1, 2, 2, 33], - [1, 0, 3, 33], - [3, 5, 0, 34], - [3, 3, 1, 34], - [3, 1, 2, 34], - [5, 4, 0, 35], - [5, 2, 1, 35], - [5, 0, 2, 35], - [0, 3, 2, 35], - [0, 1, 3, 35], - [0, 0, 4, 40], + [1, 4, 1, 0, 0, 33], + [1, 2, 2, 0, 0, 33], + [1, 0, 3, 0, 0, 33], + [3, 5, 0, 0, 0, 34], + [3, 3, 1, 0, 0, 34], + [3, 1, 2, 0, 0, 34], + [5, 4, 0, 0, 0, 35], + [5, 2, 1, 0, 0, 35], + [5, 0, 2, 0, 0, 35], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [0, 0, 4, 0, 0, 40], ], 34: [ - [3, 5, 0, 34], - [3, 3, 1, 34], - [3, 1, 2, 34], - [5, 4, 0, 35], - [5, 2, 1, 35], - [5, 0, 2, 35], - [0, 3, 2, 35], - [0, 1, 3, 35], - [2, 4, 1, 36], - [2, 2, 2, 36], - [2, 0, 3, 36], - [0, 0, 4, 40], + [3, 5, 0, 0, 0, 34], + [3, 3, 1, 0, 0, 34], + [3, 1, 2, 0, 0, 34], + [5, 4, 0, 0, 0, 35], + [5, 2, 1, 0, 0, 35], + [5, 0, 2, 0, 0, 35], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [2, 4, 1, 0, 0, 36], + [2, 2, 2, 0, 0, 36], + [2, 0, 3, 0, 0, 36], + [0, 0, 4, 0, 0, 40], ], 35: [ - [5, 4, 0, 35], - [5, 2, 1, 35], - [5, 0, 2, 35], - [0, 3, 2, 35], - [0, 1, 3, 35], - [2, 4, 1, 36], - [2, 2, 2, 36], - [2, 0, 3, 36], - [4, 5, 0, 37], - [4, 3, 1, 37], - [4, 1, 2, 37], - [0, 0, 4, 40], + [5, 4, 0, 0, 0, 35], + [5, 2, 1, 0, 0, 35], + [5, 0, 2, 0, 0, 35], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [2, 4, 1, 0, 0, 36], + [2, 2, 2, 0, 0, 36], + [2, 0, 3, 0, 0, 36], + [4, 5, 0, 0, 0, 37], + [4, 3, 1, 0, 0, 37], + [4, 1, 2, 0, 0, 37], + [0, 0, 4, 0, 0, 40], ], 36: [ - [2, 4, 1, 36], - [2, 2, 2, 36], - [2, 0, 3, 36], - [4, 5, 0, 37], - [4, 3, 1, 37], - [4, 1, 2, 37], - [1, 3, 2, 38], - [1, 1, 3, 38], - [0, 2, 3, 40], - [0, 0, 4, 40], + [2, 4, 1, 0, 0, 36], + [2, 2, 2, 0, 0, 36], + [2, 0, 3, 0, 0, 36], + [4, 5, 0, 0, 0, 37], + [4, 3, 1, 0, 0, 37], + [4, 1, 2, 0, 0, 37], + [1, 3, 2, 0, 0, 38], + [1, 1, 3, 0, 0, 38], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], ], 37: [ - [4, 5, 0, 37], - [4, 3, 1, 37], - [4, 1, 2, 37], - [1, 3, 2, 38], - [1, 1, 3, 38], - [3, 4, 1, 39], - [3, 2, 2, 39], - [3, 0, 3, 39], - [0, 2, 3, 40], - [0, 0, 4, 40], + [4, 5, 0, 0, 0, 37], + [4, 3, 1, 0, 0, 37], + [4, 1, 2, 0, 0, 37], + [1, 3, 2, 0, 0, 38], + [1, 1, 3, 0, 0, 38], + [3, 4, 1, 0, 0, 39], + [3, 2, 2, 0, 0, 39], + [3, 0, 3, 0, 0, 39], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], ], 38: [ - [1, 3, 2, 38], - [1, 1, 3, 38], - [3, 4, 1, 39], - [3, 2, 2, 39], - [3, 0, 3, 39], - [5, 5, 0, 40], - [5, 3, 1, 40], - [5, 1, 2, 40], - [0, 2, 3, 40], - [0, 0, 4, 40], + [1, 3, 2, 0, 0, 38], + [1, 1, 3, 0, 0, 38], + [3, 4, 1, 0, 0, 39], + [3, 2, 2, 0, 0, 39], + [3, 0, 3, 0, 0, 39], + [5, 5, 0, 0, 0, 40], + [5, 3, 1, 0, 0, 40], + [5, 1, 2, 0, 0, 40], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], ], 39: [ - [3, 4, 1, 39], - [3, 2, 2, 39], - [3, 0, 3, 39], - [5, 5, 0, 40], - [5, 3, 1, 40], - [5, 1, 2, 40], - [0, 2, 3, 40], - [0, 0, 4, 40], - [2, 3, 2, 41], - [2, 1, 3, 41], + [3, 4, 1, 0, 0, 39], + [3, 2, 2, 0, 0, 39], + [3, 0, 3, 0, 0, 39], + [5, 5, 0, 0, 0, 40], + [5, 3, 1, 0, 0, 40], + [5, 1, 2, 0, 0, 40], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [2, 3, 2, 0, 0, 41], + [2, 1, 3, 0, 0, 41], ], 40: [ - [5, 5, 0, 40], - [5, 3, 1, 40], - [5, 1, 2, 40], - [0, 2, 3, 40], - [0, 0, 4, 40], - [2, 3, 2, 41], - [2, 1, 3, 41], - [4, 4, 1, 42], - [4, 2, 2, 42], - [4, 0, 3, 42], + [5, 5, 0, 0, 0, 40], + [5, 3, 1, 0, 0, 40], + [5, 1, 2, 0, 0, 40], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [2, 3, 2, 0, 0, 41], + [2, 1, 3, 0, 0, 41], + [4, 4, 1, 0, 0, 42], + [4, 2, 2, 0, 0, 42], + [4, 0, 3, 0, 0, 42], ], 41: [ - [2, 3, 2, 41], - [2, 1, 3, 41], - [4, 4, 1, 42], - [4, 2, 2, 42], - [4, 0, 3, 42], - [1, 2, 3, 43], - [1, 0, 4, 43], - [0, 1, 4, 45], - [0, 0, 5, 50], + [2, 3, 2, 0, 0, 41], + [2, 1, 3, 0, 0, 41], + [4, 4, 1, 0, 0, 42], + [4, 2, 2, 0, 0, 42], + [4, 0, 3, 0, 0, 42], + [1, 2, 3, 0, 0, 43], + [1, 0, 4, 0, 0, 43], + [0, 1, 4, 0, 0, 45], + [0, 0, 5, 0, 0, 50], ], 42: [ - [4, 4, 1, 42], - [4, 2, 2, 42], - [4, 0, 3, 42], - [1, 2, 3, 43], - [1, 0, 4, 43], - [3, 3, 2, 44], - [3, 1, 3, 44], - [0, 1, 4, 45], - [0, 0, 5, 50], + [4, 4, 1, 0, 0, 42], + [4, 2, 2, 0, 0, 42], + [4, 0, 3, 0, 0, 42], + [1, 2, 3, 0, 0, 43], + [1, 0, 4, 0, 0, 43], + [3, 3, 2, 0, 0, 44], + [3, 1, 3, 0, 0, 44], + [0, 1, 4, 0, 0, 45], + [0, 0, 5, 0, 0, 50], ], 43: [ - [1, 2, 3, 43], - [1, 0, 4, 43], - [3, 3, 2, 44], - [3, 1, 3, 44], - [5, 4, 1, 45], - [5, 2, 2, 45], - [5, 0, 3, 45], - [0, 1, 4, 45], - [0, 0, 5, 50], + [1, 2, 3, 0, 0, 43], + [1, 0, 4, 0, 0, 43], + [3, 3, 2, 0, 0, 44], + [3, 1, 3, 0, 0, 44], + [5, 4, 1, 0, 0, 45], + [5, 2, 2, 0, 0, 45], + [5, 0, 3, 0, 0, 45], + [0, 1, 4, 0, 0, 45], + [0, 0, 5, 0, 0, 50], ], 44: [ - [3, 3, 2, 44], - [3, 1, 3, 44], - [5, 4, 1, 45], - [5, 2, 2, 45], - [5, 0, 3, 45], - [0, 1, 4, 45], - [2, 2, 3, 46], - [2, 0, 4, 46], - [0, 0, 5, 50], + [3, 3, 2, 0, 0, 44], + [3, 1, 3, 0, 0, 44], + [5, 4, 1, 0, 0, 45], + [5, 2, 2, 0, 0, 45], + [5, 0, 3, 0, 0, 45], + [0, 1, 4, 0, 0, 45], + [2, 2, 3, 0, 0, 46], + [2, 0, 4, 0, 0, 46], + [0, 0, 5, 0, 0, 50], ], 45: [ - [5, 4, 1, 45], - [5, 2, 2, 45], - [5, 0, 3, 45], - [0, 1, 4, 45], - [2, 2, 3, 46], - [2, 0, 4, 46], - [4, 3, 2, 47], - [4, 1, 3, 47], - [0, 0, 5, 50], + [5, 4, 1, 0, 0, 45], + [5, 2, 2, 0, 0, 45], + [5, 0, 3, 0, 0, 45], + [0, 1, 4, 0, 0, 45], + [2, 2, 3, 0, 0, 46], + [2, 0, 4, 0, 0, 46], + [4, 3, 2, 0, 0, 47], + [4, 1, 3, 0, 0, 47], + [0, 0, 5, 0, 0, 50], ], 46: [ - [2, 2, 3, 46], - [2, 0, 4, 46], - [4, 3, 2, 47], - [4, 1, 3, 47], - [1, 1, 4, 48], - [0, 0, 5, 50], + [2, 2, 3, 0, 0, 46], + [2, 0, 4, 0, 0, 46], + [4, 3, 2, 0, 0, 47], + [4, 1, 3, 0, 0, 47], + [1, 1, 4, 0, 0, 48], + [0, 0, 5, 0, 0, 50], ], 47: [ - [4, 3, 2, 47], - [4, 1, 3, 47], - [1, 1, 4, 48], - [3, 2, 3, 49], - [3, 0, 4, 49], - [0, 0, 5, 50], + [4, 3, 2, 0, 0, 47], + [4, 1, 3, 0, 0, 47], + [1, 1, 4, 0, 0, 48], + [3, 2, 3, 0, 0, 49], + [3, 0, 4, 0, 0, 49], + [0, 0, 5, 0, 0, 50], ], 48: [ - [1, 1, 4, 48], - [3, 2, 3, 49], - [3, 0, 4, 49], - [5, 3, 2, 50], - [5, 1, 3, 50], - [0, 0, 5, 50], + [1, 1, 4, 0, 0, 48], + [3, 2, 3, 0, 0, 49], + [3, 0, 4, 0, 0, 49], + [5, 3, 2, 0, 0, 50], + [5, 1, 3, 0, 0, 50], + [0, 0, 5, 0, 0, 50], ], 49: [ - [3, 2, 3, 49], - [3, 0, 4, 49], - [5, 3, 2, 50], - [5, 1, 3, 50], - [0, 0, 5, 50], - [2, 1, 4, 51], + [3, 2, 3, 0, 0, 49], + [3, 0, 4, 0, 0, 49], + [5, 3, 2, 0, 0, 50], + [5, 1, 3, 0, 0, 50], + [0, 0, 5, 0, 0, 50], + [2, 1, 4, 0, 0, 51], ], 50: [ - [5, 3, 2, 50], - [5, 1, 3, 50], - [0, 0, 5, 50], - [2, 1, 4, 51], - [4, 2, 3, 52], - [4, 0, 4, 52], + [5, 3, 2, 0, 0, 50], + [5, 1, 3, 0, 0, 50], + [0, 0, 5, 0, 0, 50], + [2, 1, 4, 0, 0, 51], + [4, 2, 3, 0, 0, 52], + [4, 0, 4, 0, 0, 52], ], 51: [ - [2, 1, 4, 51], - [4, 2, 3, 52], - [4, 0, 4, 52], - [1, 0, 5, 53], + [2, 1, 4, 0, 0, 51], + [4, 2, 3, 0, 0, 52], + [4, 0, 4, 0, 0, 52], + [1, 0, 5, 0, 0, 53], ], 52: [ - [4, 2, 3, 52], - [4, 0, 4, 52], - [1, 0, 5, 53], - [3, 1, 4, 54], + [4, 2, 3, 0, 0, 52], + [4, 0, 4, 0, 0, 52], + [1, 0, 5, 0, 0, 53], + [3, 1, 4, 0, 0, 54], ], 53: [ - [1, 0, 5, 53], - [3, 1, 4, 54], - [5, 2, 3, 55], - [5, 0, 4, 55], + [1, 0, 5, 0, 0, 53], + [3, 1, 4, 0, 0, 54], + [5, 2, 3, 0, 0, 55], + [5, 0, 4, 0, 0, 55], ], 54: [ - [3, 1, 4, 54], - [5, 2, 3, 55], - [5, 0, 4, 55], - [2, 0, 5, 56], + [3, 1, 4, 0, 0, 54], + [5, 2, 3, 0, 0, 55], + [5, 0, 4, 0, 0, 55], + [2, 0, 5, 0, 0, 56], ], 55: [ - [5, 2, 3, 55], - [5, 0, 4, 55], - [2, 0, 5, 56], - [4, 1, 4, 57], + [5, 2, 3, 0, 0, 55], + [5, 0, 4, 0, 0, 55], + [2, 0, 5, 0, 0, 56], + [4, 1, 4, 0, 0, 57], ], 56: [ - [2, 0, 5, 56], - [4, 1, 4, 57], + [2, 0, 5, 0, 0, 56], + [4, 1, 4, 0, 0, 57], ], 57: [ - [4, 1, 4, 57], - [3, 0, 5, 59], + [4, 1, 4, 0, 0, 57], + [3, 0, 5, 0, 0, 59], ], 58: [ - [3, 0, 5, 59], - [5, 1, 4, 60], + [3, 0, 5, 0, 0, 59], + [5, 1, 4, 0, 0, 60], ], 59: [ - [3, 0, 5, 59], - [5, 1, 4, 60], + [3, 0, 5, 0, 0, 59], + [5, 1, 4, 0, 0, 60], ], 60: [ - [5, 1, 4, 60], - [4, 0, 5, 62], - ], - 61: [[4, 0, 5, 62]], - 62: [[4, 0, 5, 62]], - 63: [[5, 0, 5, 65]], - 64: [[5, 0, 5, 65]], - 65: [[5, 0, 5, 65]], + [5, 1, 4, 0, 0, 60], + [4, 0, 5, 0, 0, 62], + ], + 61: [[4, 0, 5, 0, 0, 62]], + 62: [[4, 0, 5, 0, 0, 62]], + 63: [[5, 0, 5, 0, 0, 65]], + 64: [[5, 0, 5, 0, 0, 65]], + 65: [[5, 0, 5, 0, 0, 65]], }; diff --git a/src/app/data/generated/precalculatedModCombinationsWithTunings.ts b/src/app/data/generated/precalculatedModCombinationsWithTunings.ts new file mode 100644 index 00000000..6b55c457 --- /dev/null +++ b/src/app/data/generated/precalculatedModCombinationsWithTunings.ts @@ -0,0 +1,5060 @@ +export const precalculatedTuningModCombinations: { + [key: number]: [number, number, number, number, number, number][]; +} = { + 1: [ + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 2, 2, 2], + [1, 0, 0, 0, 0, 3], + [0, 0, 0, 3, 3, 3], + [0, 0, 0, 4, 4, 4], + [0, 1, 0, 0, 0, 5], + [0, 0, 0, 5, 1, 5], + [0, 0, 0, 6, 2, 6], + [0, 0, 0, 7, 3, 7], + [0, 0, 0, 8, 4, 8], + [0, 0, 0, 9, 5, 9], + [0, 0, 1, 0, 0, 10], + [0, 0, 0, 10, 2, 10], + [0, 0, 0, 11, 3, 11], + [0, 0, 0, 12, 4, 12], + [0, 0, 0, 13, 5, 13], + [0, 0, 0, 15, 3, 15], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 2: [ + [0, 0, 0, 2, 2, 2], + [1, 0, 0, 0, 0, 3], + [1, 0, 0, 1, 1, 3], + [0, 0, 0, 3, 3, 3], + [0, 0, 0, 4, 4, 4], + [0, 1, 0, 0, 0, 5], + [0, 0, 0, 5, 1, 5], + [0, 1, 0, 1, 1, 6], + [0, 0, 0, 6, 2, 6], + [0, 0, 0, 7, 3, 7], + [0, 0, 0, 8, 4, 8], + [0, 0, 0, 9, 5, 9], + [0, 0, 1, 0, 0, 10], + [0, 0, 0, 10, 2, 10], + [0, 0, 1, 1, 1, 11], + [0, 0, 0, 11, 3, 11], + [0, 0, 0, 12, 4, 12], + [0, 0, 0, 13, 5, 13], + [0, 0, 0, 15, 3, 15], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 3: [ + [1, 0, 0, 0, 0, 3], + [1, 0, 0, 1, 1, 3], + [1, 0, 0, 2, 2, 3], + [0, 0, 0, 3, 3, 3], + [0, 0, 0, 4, 4, 4], + [0, 1, 0, 0, 0, 5], + [0, 0, 0, 5, 1, 5], + [0, 1, 0, 1, 1, 6], + [0, 0, 0, 6, 2, 6], + [0, 1, 0, 2, 2, 7], + [0, 0, 0, 7, 3, 7], + [0, 0, 0, 8, 4, 8], + [0, 0, 0, 9, 5, 9], + [0, 0, 1, 0, 0, 10], + [0, 0, 0, 10, 2, 10], + [0, 0, 1, 1, 1, 11], + [0, 0, 0, 11, 3, 11], + [0, 0, 1, 2, 2, 12], + [0, 0, 0, 12, 4, 12], + [0, 0, 0, 13, 5, 13], + [0, 0, 0, 15, 3, 15], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 4: [ + [1, 0, 0, 1, 1, 3], + [1, 0, 0, 2, 2, 3], + [1, 0, 0, 3, 3, 3], + [0, 0, 0, 4, 4, 4], + [0, 1, 0, 0, 0, 5], + [0, 0, 0, 5, 1, 5], + [2, 0, 0, 0, 0, 6], + [0, 1, 0, 1, 1, 6], + [0, 0, 0, 6, 2, 6], + [0, 1, 0, 2, 2, 7], + [0, 0, 0, 7, 3, 7], + [0, 1, 0, 3, 3, 8], + [0, 0, 0, 8, 4, 8], + [0, 0, 0, 9, 5, 9], + [0, 0, 1, 0, 0, 10], + [0, 0, 0, 10, 2, 10], + [0, 0, 1, 1, 1, 11], + [0, 0, 0, 11, 3, 11], + [0, 0, 1, 2, 2, 12], + [0, 0, 0, 12, 4, 12], + [0, 0, 1, 3, 3, 13], + [0, 0, 0, 13, 5, 13], + [0, 0, 0, 15, 3, 15], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 5: [ + [1, 0, 0, 2, 2, 3], + [1, 0, 0, 3, 3, 3], + [1, 0, 0, 4, 4, 3], + [0, 1, 0, 0, 0, 5], + [0, 0, 0, 5, 1, 5], + [2, 0, 0, 0, 0, 6], + [2, 0, 0, 1, 1, 6], + [0, 1, 0, 1, 1, 6], + [0, 0, 0, 6, 2, 6], + [0, 1, 0, 2, 2, 7], + [0, 0, 0, 7, 3, 7], + [0, 1, 0, 3, 3, 8], + [0, 0, 0, 8, 4, 8], + [0, 1, 0, 4, 4, 9], + [0, 0, 0, 9, 5, 9], + [0, 0, 1, 0, 0, 10], + [0, 0, 0, 10, 2, 10], + [0, 0, 1, 1, 1, 11], + [0, 0, 0, 11, 3, 11], + [0, 0, 1, 2, 2, 12], + [0, 0, 0, 12, 4, 12], + [0, 0, 1, 3, 3, 13], + [0, 0, 0, 13, 5, 13], + [0, 0, 1, 4, 4, 14], + [0, 0, 0, 15, 3, 15], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 6: [ + [1, 0, 0, 5, 1, 3], + [1, 0, 0, 3, 3, 3], + [1, 0, 0, 4, 4, 3], + [2, 0, 0, 0, 0, 6], + [2, 0, 0, 1, 1, 6], + [0, 1, 0, 1, 1, 6], + [2, 0, 0, 2, 2, 6], + [0, 0, 0, 6, 2, 6], + [0, 1, 0, 2, 2, 7], + [0, 0, 0, 7, 3, 7], + [1, 1, 0, 0, 0, 8], + [0, 1, 0, 3, 3, 8], + [0, 0, 0, 8, 4, 8], + [0, 1, 0, 4, 4, 9], + [0, 0, 0, 9, 5, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [0, 1, 0, 5, 1, 10], + [0, 0, 0, 10, 2, 10], + [0, 0, 1, 1, 1, 11], + [0, 0, 0, 11, 3, 11], + [0, 0, 1, 2, 2, 12], + [0, 0, 0, 12, 4, 12], + [0, 0, 1, 3, 3, 13], + [0, 0, 0, 13, 5, 13], + [0, 0, 1, 4, 4, 14], + [0, 0, 1, 5, 1, 15], + [0, 0, 0, 15, 3, 15], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 7: [ + [1, 0, 0, 5, 1, 3], + [1, 0, 0, 6, 2, 3], + [1, 0, 0, 4, 4, 3], + [2, 0, 0, 1, 1, 6], + [2, 0, 0, 2, 2, 6], + [2, 0, 0, 3, 3, 6], + [0, 1, 0, 2, 2, 7], + [0, 0, 0, 7, 3, 7], + [1, 1, 0, 0, 0, 8], + [0, 1, 0, 3, 3, 8], + [0, 0, 0, 8, 4, 8], + [3, 0, 0, 0, 0, 9], + [1, 1, 0, 1, 1, 9], + [0, 1, 0, 4, 4, 9], + [0, 0, 0, 9, 5, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [0, 1, 0, 5, 1, 10], + [0, 0, 0, 10, 2, 10], + [0, 2, 0, 1, 1, 11], + [0, 0, 1, 1, 1, 11], + [0, 1, 0, 6, 2, 11], + [0, 0, 0, 11, 3, 11], + [0, 0, 1, 2, 2, 12], + [0, 0, 0, 12, 4, 12], + [0, 0, 1, 3, 3, 13], + [0, 0, 0, 13, 5, 13], + [0, 0, 1, 4, 4, 14], + [0, 0, 1, 5, 1, 15], + [0, 0, 0, 15, 3, 15], + [0, 0, 1, 6, 2, 16], + [0, 0, 0, 16, 4, 16], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 8: [ + [1, 0, 0, 5, 1, 3], + [1, 0, 0, 6, 2, 3], + [1, 0, 0, 7, 3, 3], + [2, 0, 0, 2, 2, 6], + [2, 0, 0, 3, 3, 6], + [1, 1, 0, 0, 0, 8], + [0, 1, 0, 3, 3, 8], + [0, 0, 0, 8, 4, 8], + [3, 0, 0, 0, 0, 9], + [3, 0, 0, 1, 1, 9], + [1, 1, 0, 1, 1, 9], + [0, 1, 0, 4, 4, 9], + [0, 0, 0, 9, 5, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [0, 1, 0, 5, 1, 10], + [1, 1, 0, 2, 2, 10], + [0, 0, 0, 10, 2, 10], + [0, 2, 0, 1, 1, 11], + [0, 0, 1, 1, 1, 11], + [0, 1, 0, 6, 2, 11], + [0, 0, 0, 11, 3, 11], + [0, 2, 0, 2, 2, 12], + [0, 0, 1, 2, 2, 12], + [0, 1, 0, 7, 3, 12], + [0, 0, 0, 12, 4, 12], + [0, 0, 1, 3, 3, 13], + [0, 0, 0, 13, 5, 13], + [0, 0, 1, 4, 4, 14], + [0, 0, 1, 5, 1, 15], + [0, 0, 0, 15, 3, 15], + [0, 0, 1, 6, 2, 16], + [0, 0, 0, 16, 4, 16], + [0, 0, 1, 7, 3, 17], + [0, 0, 0, 17, 5, 17], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 9: [ + [1, 0, 0, 6, 2, 3], + [1, 0, 0, 7, 3, 3], + [1, 0, 0, 8, 4, 3], + [2, 0, 0, 5, 1, 6], + [2, 0, 0, 3, 3, 6], + [3, 0, 0, 0, 0, 9], + [3, 0, 0, 1, 1, 9], + [1, 1, 0, 1, 1, 9], + [3, 0, 0, 2, 2, 9], + [0, 1, 0, 4, 4, 9], + [0, 0, 0, 9, 5, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [0, 1, 0, 5, 1, 10], + [1, 1, 0, 2, 2, 10], + [0, 0, 0, 10, 2, 10], + [2, 1, 0, 0, 0, 11], + [0, 2, 0, 1, 1, 11], + [0, 0, 1, 1, 1, 11], + [0, 1, 0, 6, 2, 11], + [1, 1, 0, 3, 3, 11], + [0, 0, 0, 11, 3, 11], + [0, 2, 0, 2, 2, 12], + [0, 0, 1, 2, 2, 12], + [0, 1, 0, 7, 3, 12], + [0, 0, 0, 12, 4, 12], + [0, 2, 0, 3, 3, 13], + [0, 0, 1, 3, 3, 13], + [0, 1, 0, 8, 4, 13], + [0, 0, 0, 13, 5, 13], + [0, 0, 1, 4, 4, 14], + [0, 0, 1, 5, 1, 15], + [0, 0, 0, 15, 3, 15], + [0, 0, 1, 6, 2, 16], + [0, 0, 0, 16, 4, 16], + [0, 0, 1, 7, 3, 17], + [0, 0, 0, 17, 5, 17], + [0, 0, 1, 8, 4, 18], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 10: [ + [1, 0, 0, 7, 3, 3], + [1, 0, 0, 8, 4, 3], + [2, 0, 0, 5, 1, 6], + [2, 0, 0, 6, 2, 6], + [3, 0, 0, 1, 1, 9], + [3, 0, 0, 2, 2, 9], + [0, 2, 0, 0, 0, 10], + [0, 0, 1, 0, 0, 10], + [0, 1, 0, 5, 1, 10], + [1, 1, 0, 2, 2, 10], + [0, 0, 0, 10, 2, 10], + [2, 1, 0, 0, 0, 11], + [0, 2, 0, 1, 1, 11], + [0, 0, 1, 1, 1, 11], + [0, 1, 0, 6, 2, 11], + [1, 1, 0, 3, 3, 11], + [0, 0, 0, 11, 3, 11], + [4, 0, 0, 0, 0, 12], + [2, 1, 0, 1, 1, 12], + [0, 2, 0, 2, 2, 12], + [0, 0, 1, 2, 2, 12], + [0, 1, 0, 7, 3, 12], + [1, 1, 0, 4, 4, 12], + [0, 0, 0, 12, 4, 12], + [0, 2, 0, 3, 3, 13], + [0, 0, 1, 3, 3, 13], + [0, 1, 0, 8, 4, 13], + [0, 0, 0, 13, 5, 13], + [0, 2, 0, 4, 4, 14], + [0, 0, 1, 4, 4, 14], + [0, 1, 0, 9, 5, 14], + [0, 0, 1, 5, 1, 15], + [0, 0, 0, 15, 3, 15], + [0, 0, 1, 6, 2, 16], + [0, 0, 0, 16, 4, 16], + [0, 0, 1, 7, 3, 17], + [0, 0, 0, 17, 5, 17], + [0, 0, 1, 8, 4, 18], + [0, 0, 1, 9, 5, 19], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 11: [ + [1, 0, 0, 10, 2, 3], + [1, 0, 0, 8, 4, 3], + [2, 0, 0, 5, 1, 6], + [2, 0, 0, 6, 2, 6], + [2, 0, 0, 7, 3, 6], + [3, 0, 0, 2, 2, 9], + [2, 1, 0, 0, 0, 11], + [0, 2, 0, 1, 1, 11], + [0, 0, 1, 1, 1, 11], + [0, 1, 0, 6, 2, 11], + [1, 1, 0, 3, 3, 11], + [0, 0, 0, 11, 3, 11], + [4, 0, 0, 0, 0, 12], + [4, 0, 0, 1, 1, 12], + [2, 1, 0, 1, 1, 12], + [0, 2, 0, 2, 2, 12], + [0, 0, 1, 2, 2, 12], + [0, 1, 0, 7, 3, 12], + [1, 1, 0, 4, 4, 12], + [0, 0, 0, 12, 4, 12], + [1, 2, 0, 0, 0, 13], + [1, 0, 1, 0, 0, 13], + [1, 1, 0, 5, 1, 13], + [2, 1, 0, 2, 2, 13], + [0, 2, 0, 3, 3, 13], + [0, 0, 1, 3, 3, 13], + [0, 1, 0, 8, 4, 13], + [0, 0, 0, 13, 5, 13], + [0, 2, 0, 4, 4, 14], + [0, 0, 1, 4, 4, 14], + [0, 1, 0, 9, 5, 14], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [0, 2, 0, 5, 1, 15], + [0, 0, 1, 5, 1, 15], + [0, 1, 0, 10, 2, 15], + [0, 0, 0, 15, 3, 15], + [0, 0, 1, 6, 2, 16], + [0, 0, 0, 16, 4, 16], + [0, 0, 1, 7, 3, 17], + [0, 0, 0, 17, 5, 17], + [0, 0, 1, 8, 4, 18], + [0, 0, 1, 9, 5, 19], + [0, 0, 2, 0, 0, 20], + [0, 0, 1, 10, 2, 20], + [0, 0, 0, 20, 4, 20], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 12: [ + [1, 0, 0, 10, 2, 3], + [1, 0, 0, 11, 3, 3], + [2, 0, 0, 6, 2, 6], + [2, 0, 0, 7, 3, 6], + [3, 0, 0, 5, 1, 9], + [4, 0, 0, 0, 0, 12], + [4, 0, 0, 1, 1, 12], + [2, 1, 0, 1, 1, 12], + [0, 2, 0, 2, 2, 12], + [0, 0, 1, 2, 2, 12], + [0, 1, 0, 7, 3, 12], + [1, 1, 0, 4, 4, 12], + [0, 0, 0, 12, 4, 12], + [1, 2, 0, 0, 0, 13], + [1, 0, 1, 0, 0, 13], + [1, 1, 0, 5, 1, 13], + [2, 1, 0, 2, 2, 13], + [0, 2, 0, 3, 3, 13], + [0, 0, 1, 3, 3, 13], + [0, 1, 0, 8, 4, 13], + [0, 0, 0, 13, 5, 13], + [3, 1, 0, 0, 0, 14], + [1, 2, 0, 1, 1, 14], + [1, 0, 1, 1, 1, 14], + [1, 1, 0, 6, 2, 14], + [2, 1, 0, 3, 3, 14], + [0, 2, 0, 4, 4, 14], + [0, 0, 1, 4, 4, 14], + [0, 1, 0, 9, 5, 14], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [0, 2, 0, 5, 1, 15], + [0, 0, 1, 5, 1, 15], + [0, 1, 0, 10, 2, 15], + [0, 0, 0, 15, 3, 15], + [0, 3, 0, 1, 1, 16], + [0, 1, 1, 1, 1, 16], + [0, 2, 0, 6, 2, 16], + [0, 0, 1, 6, 2, 16], + [0, 1, 0, 11, 3, 16], + [0, 0, 0, 16, 4, 16], + [0, 0, 1, 7, 3, 17], + [0, 0, 0, 17, 5, 17], + [0, 0, 1, 8, 4, 18], + [0, 0, 1, 9, 5, 19], + [0, 0, 2, 0, 0, 20], + [0, 0, 1, 10, 2, 20], + [0, 0, 0, 20, 4, 20], + [0, 0, 2, 1, 1, 21], + [0, 0, 1, 11, 3, 21], + [0, 0, 0, 21, 5, 21], + [0, 0, 0, 25, 5, 25], + ], + 13: [ + [1, 0, 0, 10, 2, 3], + [1, 0, 0, 11, 3, 3], + [1, 0, 0, 12, 4, 3], + [2, 0, 0, 7, 3, 6], + [3, 0, 0, 5, 1, 9], + [3, 0, 0, 6, 2, 9], + [4, 0, 0, 1, 1, 12], + [1, 2, 0, 0, 0, 13], + [1, 0, 1, 0, 0, 13], + [1, 1, 0, 5, 1, 13], + [2, 1, 0, 2, 2, 13], + [0, 2, 0, 3, 3, 13], + [0, 0, 1, 3, 3, 13], + [0, 1, 0, 8, 4, 13], + [0, 0, 0, 13, 5, 13], + [3, 1, 0, 0, 0, 14], + [1, 2, 0, 1, 1, 14], + [1, 0, 1, 1, 1, 14], + [1, 1, 0, 6, 2, 14], + [2, 1, 0, 3, 3, 14], + [0, 2, 0, 4, 4, 14], + [0, 0, 1, 4, 4, 14], + [0, 1, 0, 9, 5, 14], + [5, 0, 0, 0, 0, 15], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [3, 1, 0, 1, 1, 15], + [0, 2, 0, 5, 1, 15], + [0, 0, 1, 5, 1, 15], + [1, 2, 0, 2, 2, 15], + [1, 0, 1, 2, 2, 15], + [0, 1, 0, 10, 2, 15], + [1, 1, 0, 7, 3, 15], + [0, 0, 0, 15, 3, 15], + [0, 3, 0, 1, 1, 16], + [0, 1, 1, 1, 1, 16], + [0, 2, 0, 6, 2, 16], + [0, 0, 1, 6, 2, 16], + [0, 1, 0, 11, 3, 16], + [0, 0, 0, 16, 4, 16], + [0, 3, 0, 2, 2, 17], + [0, 1, 1, 2, 2, 17], + [0, 2, 0, 7, 3, 17], + [0, 0, 1, 7, 3, 17], + [0, 1, 0, 12, 4, 17], + [0, 0, 0, 17, 5, 17], + [0, 0, 1, 8, 4, 18], + [0, 0, 1, 9, 5, 19], + [0, 0, 2, 0, 0, 20], + [0, 0, 1, 10, 2, 20], + [0, 0, 0, 20, 4, 20], + [0, 0, 2, 1, 1, 21], + [0, 0, 1, 11, 3, 21], + [0, 0, 0, 21, 5, 21], + [0, 0, 2, 2, 2, 22], + [0, 0, 1, 12, 4, 22], + [0, 0, 0, 25, 5, 25], + ], + 14: [ + [1, 0, 0, 11, 3, 3], + [1, 0, 0, 12, 4, 3], + [2, 0, 0, 10, 2, 6], + [3, 0, 0, 5, 1, 9], + [3, 0, 0, 6, 2, 9], + [3, 1, 0, 0, 0, 14], + [1, 2, 0, 1, 1, 14], + [1, 0, 1, 1, 1, 14], + [1, 1, 0, 6, 2, 14], + [2, 1, 0, 3, 3, 14], + [0, 2, 0, 4, 4, 14], + [0, 0, 1, 4, 4, 14], + [0, 1, 0, 9, 5, 14], + [5, 0, 0, 0, 0, 15], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [3, 1, 0, 1, 1, 15], + [0, 2, 0, 5, 1, 15], + [0, 0, 1, 5, 1, 15], + [1, 2, 0, 2, 2, 15], + [1, 0, 1, 2, 2, 15], + [0, 1, 0, 10, 2, 15], + [1, 1, 0, 7, 3, 15], + [0, 0, 0, 15, 3, 15], + [2, 2, 0, 0, 0, 16], + [2, 0, 1, 0, 0, 16], + [0, 3, 0, 1, 1, 16], + [0, 1, 1, 1, 1, 16], + [2, 1, 0, 5, 1, 16], + [3, 1, 0, 2, 2, 16], + [0, 2, 0, 6, 2, 16], + [0, 0, 1, 6, 2, 16], + [1, 2, 0, 3, 3, 16], + [1, 0, 1, 3, 3, 16], + [0, 1, 0, 11, 3, 16], + [1, 1, 0, 8, 4, 16], + [0, 0, 0, 16, 4, 16], + [0, 3, 0, 2, 2, 17], + [0, 1, 1, 2, 2, 17], + [0, 2, 0, 7, 3, 17], + [0, 0, 1, 7, 3, 17], + [0, 1, 0, 12, 4, 17], + [0, 0, 0, 17, 5, 17], + [0, 3, 0, 3, 3, 18], + [0, 1, 1, 3, 3, 18], + [0, 2, 0, 8, 4, 18], + [0, 0, 1, 8, 4, 18], + [0, 1, 0, 13, 5, 18], + [0, 0, 1, 9, 5, 19], + [0, 0, 2, 0, 0, 20], + [0, 0, 1, 10, 2, 20], + [0, 0, 0, 20, 4, 20], + [0, 0, 2, 1, 1, 21], + [0, 0, 1, 11, 3, 21], + [0, 0, 0, 21, 5, 21], + [0, 0, 2, 2, 2, 22], + [0, 0, 1, 12, 4, 22], + [0, 0, 2, 3, 3, 23], + [0, 0, 1, 13, 5, 23], + [0, 0, 0, 25, 5, 25], + ], + 15: [ + [1, 0, 0, 12, 4, 3], + [2, 0, 0, 10, 2, 6], + [2, 0, 0, 11, 3, 6], + [3, 0, 0, 6, 2, 9], + [4, 0, 0, 5, 1, 12], + [5, 0, 0, 0, 0, 15], + [0, 3, 0, 0, 0, 15], + [0, 1, 1, 0, 0, 15], + [3, 1, 0, 1, 1, 15], + [0, 2, 0, 5, 1, 15], + [0, 0, 1, 5, 1, 15], + [1, 2, 0, 2, 2, 15], + [1, 0, 1, 2, 2, 15], + [0, 1, 0, 10, 2, 15], + [1, 1, 0, 7, 3, 15], + [0, 0, 0, 15, 3, 15], + [2, 2, 0, 0, 0, 16], + [2, 0, 1, 0, 0, 16], + [0, 3, 0, 1, 1, 16], + [0, 1, 1, 1, 1, 16], + [2, 1, 0, 5, 1, 16], + [3, 1, 0, 2, 2, 16], + [0, 2, 0, 6, 2, 16], + [0, 0, 1, 6, 2, 16], + [1, 2, 0, 3, 3, 16], + [1, 0, 1, 3, 3, 16], + [0, 1, 0, 11, 3, 16], + [1, 1, 0, 8, 4, 16], + [0, 0, 0, 16, 4, 16], + [4, 1, 0, 0, 0, 17], + [2, 2, 0, 1, 1, 17], + [2, 0, 1, 1, 1, 17], + [0, 3, 0, 2, 2, 17], + [0, 1, 1, 2, 2, 17], + [2, 1, 0, 6, 2, 17], + [0, 2, 0, 7, 3, 17], + [0, 0, 1, 7, 3, 17], + [1, 2, 0, 4, 4, 17], + [1, 0, 1, 4, 4, 17], + [0, 1, 0, 12, 4, 17], + [0, 0, 0, 17, 5, 17], + [0, 3, 0, 3, 3, 18], + [0, 1, 1, 3, 3, 18], + [0, 2, 0, 8, 4, 18], + [0, 0, 1, 8, 4, 18], + [0, 1, 0, 13, 5, 18], + [0, 3, 0, 4, 4, 19], + [0, 1, 1, 4, 4, 19], + [0, 2, 0, 9, 5, 19], + [0, 0, 1, 9, 5, 19], + [0, 0, 2, 0, 0, 20], + [0, 0, 1, 10, 2, 20], + [0, 0, 0, 20, 4, 20], + [0, 0, 2, 1, 1, 21], + [0, 0, 1, 11, 3, 21], + [0, 0, 0, 21, 5, 21], + [0, 0, 2, 2, 2, 22], + [0, 0, 1, 12, 4, 22], + [0, 0, 2, 3, 3, 23], + [0, 0, 1, 13, 5, 23], + [0, 0, 2, 4, 4, 24], + [0, 0, 0, 25, 5, 25], + ], + 16: [ + [1, 0, 0, 15, 3, 3], + [2, 0, 0, 10, 2, 6], + [2, 0, 0, 11, 3, 6], + [4, 0, 0, 5, 1, 12], + [2, 2, 0, 0, 0, 16], + [2, 0, 1, 0, 0, 16], + [0, 3, 0, 1, 1, 16], + [0, 1, 1, 1, 1, 16], + [2, 1, 0, 5, 1, 16], + [3, 1, 0, 2, 2, 16], + [0, 2, 0, 6, 2, 16], + [0, 0, 1, 6, 2, 16], + [1, 2, 0, 3, 3, 16], + [1, 0, 1, 3, 3, 16], + [0, 1, 0, 11, 3, 16], + [1, 1, 0, 8, 4, 16], + [0, 0, 0, 16, 4, 16], + [4, 1, 0, 0, 0, 17], + [2, 2, 0, 1, 1, 17], + [2, 0, 1, 1, 1, 17], + [0, 3, 0, 2, 2, 17], + [0, 1, 1, 2, 2, 17], + [2, 1, 0, 6, 2, 17], + [0, 2, 0, 7, 3, 17], + [0, 0, 1, 7, 3, 17], + [1, 2, 0, 4, 4, 17], + [1, 0, 1, 4, 4, 17], + [0, 1, 0, 12, 4, 17], + [0, 0, 0, 17, 5, 17], + [1, 3, 0, 0, 0, 18], + [1, 1, 1, 0, 0, 18], + [4, 1, 0, 1, 1, 18], + [1, 2, 0, 5, 1, 18], + [1, 0, 1, 5, 1, 18], + [2, 2, 0, 2, 2, 18], + [2, 0, 1, 2, 2, 18], + [1, 1, 0, 10, 2, 18], + [0, 3, 0, 3, 3, 18], + [0, 1, 1, 3, 3, 18], + [2, 1, 0, 7, 3, 18], + [0, 2, 0, 8, 4, 18], + [0, 0, 1, 8, 4, 18], + [0, 1, 0, 13, 5, 18], + [0, 3, 0, 4, 4, 19], + [0, 1, 1, 4, 4, 19], + [0, 2, 0, 9, 5, 19], + [0, 0, 1, 9, 5, 19], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [0, 3, 0, 5, 1, 20], + [0, 1, 1, 5, 1, 20], + [0, 2, 0, 10, 2, 20], + [0, 0, 1, 10, 2, 20], + [0, 1, 0, 15, 3, 20], + [0, 0, 0, 20, 4, 20], + [0, 0, 2, 1, 1, 21], + [0, 0, 1, 11, 3, 21], + [0, 0, 0, 21, 5, 21], + [0, 0, 2, 2, 2, 22], + [0, 0, 1, 12, 4, 22], + [0, 0, 2, 3, 3, 23], + [0, 0, 1, 13, 5, 23], + [0, 0, 2, 4, 4, 24], + [0, 0, 2, 5, 1, 25], + [0, 0, 1, 15, 3, 25], + [0, 0, 0, 25, 5, 25], + ], + 17: [ + [1, 0, 0, 15, 3, 3], + [1, 0, 0, 16, 4, 3], + [2, 0, 0, 11, 3, 6], + [3, 0, 0, 10, 2, 9], + [4, 0, 0, 5, 1, 12], + [4, 1, 0, 0, 0, 17], + [2, 2, 0, 1, 1, 17], + [2, 0, 1, 1, 1, 17], + [0, 3, 0, 2, 2, 17], + [0, 1, 1, 2, 2, 17], + [2, 1, 0, 6, 2, 17], + [0, 2, 0, 7, 3, 17], + [0, 0, 1, 7, 3, 17], + [1, 2, 0, 4, 4, 17], + [1, 0, 1, 4, 4, 17], + [0, 1, 0, 12, 4, 17], + [0, 0, 0, 17, 5, 17], + [1, 3, 0, 0, 0, 18], + [1, 1, 1, 0, 0, 18], + [4, 1, 0, 1, 1, 18], + [1, 2, 0, 5, 1, 18], + [1, 0, 1, 5, 1, 18], + [2, 2, 0, 2, 2, 18], + [2, 0, 1, 2, 2, 18], + [1, 1, 0, 10, 2, 18], + [0, 3, 0, 3, 3, 18], + [0, 1, 1, 3, 3, 18], + [2, 1, 0, 7, 3, 18], + [0, 2, 0, 8, 4, 18], + [0, 0, 1, 8, 4, 18], + [0, 1, 0, 13, 5, 18], + [3, 2, 0, 0, 0, 19], + [3, 0, 1, 0, 0, 19], + [1, 3, 0, 1, 1, 19], + [1, 1, 1, 1, 1, 19], + [3, 1, 0, 5, 1, 19], + [1, 2, 0, 6, 2, 19], + [1, 0, 1, 6, 2, 19], + [2, 2, 0, 3, 3, 19], + [2, 0, 1, 3, 3, 19], + [1, 1, 0, 11, 3, 19], + [0, 3, 0, 4, 4, 19], + [0, 1, 1, 4, 4, 19], + [0, 2, 0, 9, 5, 19], + [0, 0, 1, 9, 5, 19], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [0, 3, 0, 5, 1, 20], + [0, 1, 1, 5, 1, 20], + [0, 2, 0, 10, 2, 20], + [0, 0, 1, 10, 2, 20], + [0, 1, 0, 15, 3, 20], + [0, 0, 0, 20, 4, 20], + [0, 4, 0, 1, 1, 21], + [0, 2, 1, 1, 1, 21], + [0, 0, 2, 1, 1, 21], + [0, 3, 0, 6, 2, 21], + [0, 1, 1, 6, 2, 21], + [0, 2, 0, 11, 3, 21], + [0, 0, 1, 11, 3, 21], + [0, 1, 0, 16, 4, 21], + [0, 0, 0, 21, 5, 21], + [0, 0, 2, 2, 2, 22], + [0, 0, 1, 12, 4, 22], + [0, 0, 2, 3, 3, 23], + [0, 0, 1, 13, 5, 23], + [0, 0, 2, 4, 4, 24], + [0, 0, 2, 5, 1, 25], + [0, 0, 1, 15, 3, 25], + [0, 0, 0, 25, 5, 25], + [0, 0, 2, 6, 2, 26], + [0, 0, 1, 16, 4, 26], + ], + 18: [ + [1, 0, 0, 15, 3, 3], + [1, 0, 0, 16, 4, 3], + [3, 0, 0, 10, 2, 9], + [1, 3, 0, 0, 0, 18], + [1, 1, 1, 0, 0, 18], + [4, 1, 0, 1, 1, 18], + [1, 2, 0, 5, 1, 18], + [1, 0, 1, 5, 1, 18], + [2, 2, 0, 2, 2, 18], + [2, 0, 1, 2, 2, 18], + [1, 1, 0, 10, 2, 18], + [0, 3, 0, 3, 3, 18], + [0, 1, 1, 3, 3, 18], + [2, 1, 0, 7, 3, 18], + [0, 2, 0, 8, 4, 18], + [0, 0, 1, 8, 4, 18], + [0, 1, 0, 13, 5, 18], + [3, 2, 0, 0, 0, 19], + [3, 0, 1, 0, 0, 19], + [1, 3, 0, 1, 1, 19], + [1, 1, 1, 1, 1, 19], + [3, 1, 0, 5, 1, 19], + [1, 2, 0, 6, 2, 19], + [1, 0, 1, 6, 2, 19], + [2, 2, 0, 3, 3, 19], + [2, 0, 1, 3, 3, 19], + [1, 1, 0, 11, 3, 19], + [0, 3, 0, 4, 4, 19], + [0, 1, 1, 4, 4, 19], + [0, 2, 0, 9, 5, 19], + [0, 0, 1, 9, 5, 19], + [5, 1, 0, 0, 0, 20], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [3, 2, 0, 1, 1, 20], + [3, 0, 1, 1, 1, 20], + [0, 3, 0, 5, 1, 20], + [0, 1, 1, 5, 1, 20], + [1, 3, 0, 2, 2, 20], + [1, 1, 1, 2, 2, 20], + [3, 1, 0, 6, 2, 20], + [0, 2, 0, 10, 2, 20], + [0, 0, 1, 10, 2, 20], + [1, 2, 0, 7, 3, 20], + [1, 0, 1, 7, 3, 20], + [0, 1, 0, 15, 3, 20], + [1, 1, 0, 12, 4, 20], + [0, 0, 0, 20, 4, 20], + [0, 4, 0, 1, 1, 21], + [0, 2, 1, 1, 1, 21], + [0, 0, 2, 1, 1, 21], + [0, 3, 0, 6, 2, 21], + [0, 1, 1, 6, 2, 21], + [0, 2, 0, 11, 3, 21], + [0, 0, 1, 11, 3, 21], + [0, 1, 0, 16, 4, 21], + [0, 0, 0, 21, 5, 21], + [0, 4, 0, 2, 2, 22], + [0, 2, 1, 2, 2, 22], + [0, 0, 2, 2, 2, 22], + [0, 3, 0, 7, 3, 22], + [0, 1, 1, 7, 3, 22], + [0, 2, 0, 12, 4, 22], + [0, 0, 1, 12, 4, 22], + [0, 1, 0, 17, 5, 22], + [0, 0, 2, 3, 3, 23], + [0, 0, 1, 13, 5, 23], + [0, 0, 2, 4, 4, 24], + [0, 0, 2, 5, 1, 25], + [0, 0, 1, 15, 3, 25], + [0, 0, 0, 25, 5, 25], + [0, 0, 2, 6, 2, 26], + [0, 0, 1, 16, 4, 26], + [0, 0, 2, 7, 3, 27], + [0, 0, 1, 17, 5, 27], + ], + 19: [ + [1, 0, 0, 16, 4, 3], + [2, 0, 0, 15, 3, 6], + [3, 0, 0, 10, 2, 9], + [3, 2, 0, 0, 0, 19], + [3, 0, 1, 0, 0, 19], + [1, 3, 0, 1, 1, 19], + [1, 1, 1, 1, 1, 19], + [3, 1, 0, 5, 1, 19], + [1, 2, 0, 6, 2, 19], + [1, 0, 1, 6, 2, 19], + [2, 2, 0, 3, 3, 19], + [2, 0, 1, 3, 3, 19], + [1, 1, 0, 11, 3, 19], + [0, 3, 0, 4, 4, 19], + [0, 1, 1, 4, 4, 19], + [0, 2, 0, 9, 5, 19], + [0, 0, 1, 9, 5, 19], + [5, 1, 0, 0, 0, 20], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [3, 2, 0, 1, 1, 20], + [3, 0, 1, 1, 1, 20], + [0, 3, 0, 5, 1, 20], + [0, 1, 1, 5, 1, 20], + [1, 3, 0, 2, 2, 20], + [1, 1, 1, 2, 2, 20], + [3, 1, 0, 6, 2, 20], + [0, 2, 0, 10, 2, 20], + [0, 0, 1, 10, 2, 20], + [1, 2, 0, 7, 3, 20], + [1, 0, 1, 7, 3, 20], + [0, 1, 0, 15, 3, 20], + [1, 1, 0, 12, 4, 20], + [0, 0, 0, 20, 4, 20], + [2, 3, 0, 0, 0, 21], + [2, 1, 1, 0, 0, 21], + [0, 4, 0, 1, 1, 21], + [0, 2, 1, 1, 1, 21], + [0, 0, 2, 1, 1, 21], + [2, 2, 0, 5, 1, 21], + [2, 0, 1, 5, 1, 21], + [3, 2, 0, 2, 2, 21], + [3, 0, 1, 2, 2, 21], + [0, 3, 0, 6, 2, 21], + [0, 1, 1, 6, 2, 21], + [2, 1, 0, 10, 2, 21], + [1, 3, 0, 3, 3, 21], + [1, 1, 1, 3, 3, 21], + [0, 2, 0, 11, 3, 21], + [0, 0, 1, 11, 3, 21], + [1, 2, 0, 8, 4, 21], + [1, 0, 1, 8, 4, 21], + [0, 1, 0, 16, 4, 21], + [0, 0, 0, 21, 5, 21], + [0, 4, 0, 2, 2, 22], + [0, 2, 1, 2, 2, 22], + [0, 0, 2, 2, 2, 22], + [0, 3, 0, 7, 3, 22], + [0, 1, 1, 7, 3, 22], + [0, 2, 0, 12, 4, 22], + [0, 0, 1, 12, 4, 22], + [0, 1, 0, 17, 5, 22], + [0, 4, 0, 3, 3, 23], + [0, 2, 1, 3, 3, 23], + [0, 0, 2, 3, 3, 23], + [0, 3, 0, 8, 4, 23], + [0, 1, 1, 8, 4, 23], + [0, 2, 0, 13, 5, 23], + [0, 0, 1, 13, 5, 23], + [0, 0, 2, 4, 4, 24], + [0, 0, 2, 5, 1, 25], + [0, 0, 1, 15, 3, 25], + [0, 0, 0, 25, 5, 25], + [0, 0, 2, 6, 2, 26], + [0, 0, 1, 16, 4, 26], + [0, 0, 2, 7, 3, 27], + [0, 0, 1, 17, 5, 27], + [0, 0, 2, 8, 4, 28], + ], + 20: [ + [2, 0, 0, 15, 3, 6], + [5, 1, 0, 0, 0, 20], + [0, 4, 0, 0, 0, 20], + [0, 2, 1, 0, 0, 20], + [0, 0, 2, 0, 0, 20], + [3, 2, 0, 1, 1, 20], + [3, 0, 1, 1, 1, 20], + [0, 3, 0, 5, 1, 20], + [0, 1, 1, 5, 1, 20], + [1, 3, 0, 2, 2, 20], + [1, 1, 1, 2, 2, 20], + [3, 1, 0, 6, 2, 20], + [0, 2, 0, 10, 2, 20], + [0, 0, 1, 10, 2, 20], + [1, 2, 0, 7, 3, 20], + [1, 0, 1, 7, 3, 20], + [0, 1, 0, 15, 3, 20], + [1, 1, 0, 12, 4, 20], + [0, 0, 0, 20, 4, 20], + [2, 3, 0, 0, 0, 21], + [2, 1, 1, 0, 0, 21], + [0, 4, 0, 1, 1, 21], + [0, 2, 1, 1, 1, 21], + [0, 0, 2, 1, 1, 21], + [2, 2, 0, 5, 1, 21], + [2, 0, 1, 5, 1, 21], + [3, 2, 0, 2, 2, 21], + [3, 0, 1, 2, 2, 21], + [0, 3, 0, 6, 2, 21], + [0, 1, 1, 6, 2, 21], + [2, 1, 0, 10, 2, 21], + [1, 3, 0, 3, 3, 21], + [1, 1, 1, 3, 3, 21], + [0, 2, 0, 11, 3, 21], + [0, 0, 1, 11, 3, 21], + [1, 2, 0, 8, 4, 21], + [1, 0, 1, 8, 4, 21], + [0, 1, 0, 16, 4, 21], + [0, 0, 0, 21, 5, 21], + [4, 2, 0, 0, 0, 22], + [4, 0, 1, 0, 0, 22], + [2, 3, 0, 1, 1, 22], + [2, 1, 1, 1, 1, 22], + [4, 1, 0, 5, 1, 22], + [0, 4, 0, 2, 2, 22], + [0, 2, 1, 2, 2, 22], + [0, 0, 2, 2, 2, 22], + [2, 2, 0, 6, 2, 22], + [2, 0, 1, 6, 2, 22], + [0, 3, 0, 7, 3, 22], + [0, 1, 1, 7, 3, 22], + [2, 1, 0, 11, 3, 22], + [1, 3, 0, 4, 4, 22], + [1, 1, 1, 4, 4, 22], + [0, 2, 0, 12, 4, 22], + [0, 0, 1, 12, 4, 22], + [0, 1, 0, 17, 5, 22], + [0, 4, 0, 3, 3, 23], + [0, 2, 1, 3, 3, 23], + [0, 0, 2, 3, 3, 23], + [0, 3, 0, 8, 4, 23], + [0, 1, 1, 8, 4, 23], + [0, 2, 0, 13, 5, 23], + [0, 0, 1, 13, 5, 23], + [0, 4, 0, 4, 4, 24], + [0, 2, 1, 4, 4, 24], + [0, 0, 2, 4, 4, 24], + [0, 3, 0, 9, 5, 24], + [0, 1, 1, 9, 5, 24], + [0, 0, 2, 5, 1, 25], + [0, 0, 1, 15, 3, 25], + [0, 0, 0, 25, 5, 25], + [0, 0, 2, 6, 2, 26], + [0, 0, 1, 16, 4, 26], + [0, 0, 2, 7, 3, 27], + [0, 0, 1, 17, 5, 27], + [0, 0, 2, 8, 4, 28], + [0, 0, 2, 9, 5, 29], + ], + 21: [ + [1, 0, 0, 20, 4, 3], + [2, 0, 0, 15, 3, 6], + [2, 3, 0, 0, 0, 21], + [2, 1, 1, 0, 0, 21], + [0, 4, 0, 1, 1, 21], + [0, 2, 1, 1, 1, 21], + [0, 0, 2, 1, 1, 21], + [2, 2, 0, 5, 1, 21], + [2, 0, 1, 5, 1, 21], + [3, 2, 0, 2, 2, 21], + [3, 0, 1, 2, 2, 21], + [0, 3, 0, 6, 2, 21], + [0, 1, 1, 6, 2, 21], + [2, 1, 0, 10, 2, 21], + [1, 3, 0, 3, 3, 21], + [1, 1, 1, 3, 3, 21], + [0, 2, 0, 11, 3, 21], + [0, 0, 1, 11, 3, 21], + [1, 2, 0, 8, 4, 21], + [1, 0, 1, 8, 4, 21], + [0, 1, 0, 16, 4, 21], + [0, 0, 0, 21, 5, 21], + [4, 2, 0, 0, 0, 22], + [4, 0, 1, 0, 0, 22], + [2, 3, 0, 1, 1, 22], + [2, 1, 1, 1, 1, 22], + [4, 1, 0, 5, 1, 22], + [0, 4, 0, 2, 2, 22], + [0, 2, 1, 2, 2, 22], + [0, 0, 2, 2, 2, 22], + [2, 2, 0, 6, 2, 22], + [2, 0, 1, 6, 2, 22], + [0, 3, 0, 7, 3, 22], + [0, 1, 1, 7, 3, 22], + [2, 1, 0, 11, 3, 22], + [1, 3, 0, 4, 4, 22], + [1, 1, 1, 4, 4, 22], + [0, 2, 0, 12, 4, 22], + [0, 0, 1, 12, 4, 22], + [0, 1, 0, 17, 5, 22], + [1, 4, 0, 0, 0, 23], + [1, 2, 1, 0, 0, 23], + [1, 0, 2, 0, 0, 23], + [4, 2, 0, 1, 1, 23], + [4, 0, 1, 1, 1, 23], + [1, 3, 0, 5, 1, 23], + [1, 1, 1, 5, 1, 23], + [2, 3, 0, 2, 2, 23], + [2, 1, 1, 2, 2, 23], + [1, 2, 0, 10, 2, 23], + [1, 0, 1, 10, 2, 23], + [0, 4, 0, 3, 3, 23], + [0, 2, 1, 3, 3, 23], + [0, 0, 2, 3, 3, 23], + [2, 2, 0, 7, 3, 23], + [2, 0, 1, 7, 3, 23], + [1, 1, 0, 15, 3, 23], + [0, 3, 0, 8, 4, 23], + [0, 1, 1, 8, 4, 23], + [0, 2, 0, 13, 5, 23], + [0, 0, 1, 13, 5, 23], + [0, 4, 0, 4, 4, 24], + [0, 2, 1, 4, 4, 24], + [0, 0, 2, 4, 4, 24], + [0, 3, 0, 9, 5, 24], + [0, 1, 1, 9, 5, 24], + [0, 5, 0, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [0, 4, 0, 5, 1, 25], + [0, 2, 1, 5, 1, 25], + [0, 0, 2, 5, 1, 25], + [0, 3, 0, 10, 2, 25], + [0, 1, 1, 10, 2, 25], + [0, 2, 0, 15, 3, 25], + [0, 0, 1, 15, 3, 25], + [0, 1, 0, 20, 4, 25], + [0, 0, 0, 25, 5, 25], + [0, 0, 2, 6, 2, 26], + [0, 0, 1, 16, 4, 26], + [0, 0, 2, 7, 3, 27], + [0, 0, 1, 17, 5, 27], + [0, 0, 2, 8, 4, 28], + [0, 0, 2, 9, 5, 29], + [0, 0, 3, 0, 0, 30], + [0, 0, 2, 10, 2, 30], + [0, 0, 1, 20, 4, 30], + ], + 22: [ + [1, 0, 0, 20, 4, 3], + [4, 2, 0, 0, 0, 22], + [4, 0, 1, 0, 0, 22], + [2, 3, 0, 1, 1, 22], + [2, 1, 1, 1, 1, 22], + [4, 1, 0, 5, 1, 22], + [0, 4, 0, 2, 2, 22], + [0, 2, 1, 2, 2, 22], + [0, 0, 2, 2, 2, 22], + [2, 2, 0, 6, 2, 22], + [2, 0, 1, 6, 2, 22], + [0, 3, 0, 7, 3, 22], + [0, 1, 1, 7, 3, 22], + [2, 1, 0, 11, 3, 22], + [1, 3, 0, 4, 4, 22], + [1, 1, 1, 4, 4, 22], + [0, 2, 0, 12, 4, 22], + [0, 0, 1, 12, 4, 22], + [0, 1, 0, 17, 5, 22], + [1, 4, 0, 0, 0, 23], + [1, 2, 1, 0, 0, 23], + [1, 0, 2, 0, 0, 23], + [4, 2, 0, 1, 1, 23], + [4, 0, 1, 1, 1, 23], + [1, 3, 0, 5, 1, 23], + [1, 1, 1, 5, 1, 23], + [2, 3, 0, 2, 2, 23], + [2, 1, 1, 2, 2, 23], + [1, 2, 0, 10, 2, 23], + [1, 0, 1, 10, 2, 23], + [0, 4, 0, 3, 3, 23], + [0, 2, 1, 3, 3, 23], + [0, 0, 2, 3, 3, 23], + [2, 2, 0, 7, 3, 23], + [2, 0, 1, 7, 3, 23], + [1, 1, 0, 15, 3, 23], + [0, 3, 0, 8, 4, 23], + [0, 1, 1, 8, 4, 23], + [0, 2, 0, 13, 5, 23], + [0, 0, 1, 13, 5, 23], + [3, 3, 0, 0, 0, 24], + [3, 1, 1, 0, 0, 24], + [1, 4, 0, 1, 1, 24], + [1, 2, 1, 1, 1, 24], + [1, 0, 2, 1, 1, 24], + [3, 2, 0, 5, 1, 24], + [3, 0, 1, 5, 1, 24], + [1, 3, 0, 6, 2, 24], + [1, 1, 1, 6, 2, 24], + [3, 1, 0, 10, 2, 24], + [2, 3, 0, 3, 3, 24], + [2, 1, 1, 3, 3, 24], + [1, 2, 0, 11, 3, 24], + [1, 0, 1, 11, 3, 24], + [0, 4, 0, 4, 4, 24], + [0, 2, 1, 4, 4, 24], + [0, 0, 2, 4, 4, 24], + [1, 1, 0, 16, 4, 24], + [0, 3, 0, 9, 5, 24], + [0, 1, 1, 9, 5, 24], + [0, 5, 0, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [0, 4, 0, 5, 1, 25], + [0, 2, 1, 5, 1, 25], + [0, 0, 2, 5, 1, 25], + [0, 3, 0, 10, 2, 25], + [0, 1, 1, 10, 2, 25], + [0, 2, 0, 15, 3, 25], + [0, 0, 1, 15, 3, 25], + [0, 1, 0, 20, 4, 25], + [0, 0, 0, 25, 5, 25], + [0, 5, 0, 1, 1, 26], + [0, 3, 1, 1, 1, 26], + [0, 1, 2, 1, 1, 26], + [0, 4, 0, 6, 2, 26], + [0, 2, 1, 6, 2, 26], + [0, 0, 2, 6, 2, 26], + [0, 3, 0, 11, 3, 26], + [0, 1, 1, 11, 3, 26], + [0, 2, 0, 16, 4, 26], + [0, 0, 1, 16, 4, 26], + [0, 1, 0, 21, 5, 26], + [0, 0, 2, 7, 3, 27], + [0, 0, 1, 17, 5, 27], + [0, 0, 2, 8, 4, 28], + [0, 0, 2, 9, 5, 29], + [0, 0, 3, 0, 0, 30], + [0, 0, 2, 10, 2, 30], + [0, 0, 1, 20, 4, 30], + [0, 0, 3, 1, 1, 31], + [0, 0, 2, 11, 3, 31], + [0, 0, 1, 21, 5, 31], + ], + 23: [ + [1, 0, 0, 20, 4, 3], + [1, 4, 0, 0, 0, 23], + [1, 2, 1, 0, 0, 23], + [1, 0, 2, 0, 0, 23], + [4, 2, 0, 1, 1, 23], + [4, 0, 1, 1, 1, 23], + [1, 3, 0, 5, 1, 23], + [1, 1, 1, 5, 1, 23], + [2, 3, 0, 2, 2, 23], + [2, 1, 1, 2, 2, 23], + [1, 2, 0, 10, 2, 23], + [1, 0, 1, 10, 2, 23], + [0, 4, 0, 3, 3, 23], + [0, 2, 1, 3, 3, 23], + [0, 0, 2, 3, 3, 23], + [2, 2, 0, 7, 3, 23], + [2, 0, 1, 7, 3, 23], + [1, 1, 0, 15, 3, 23], + [0, 3, 0, 8, 4, 23], + [0, 1, 1, 8, 4, 23], + [0, 2, 0, 13, 5, 23], + [0, 0, 1, 13, 5, 23], + [3, 3, 0, 0, 0, 24], + [3, 1, 1, 0, 0, 24], + [1, 4, 0, 1, 1, 24], + [1, 2, 1, 1, 1, 24], + [1, 0, 2, 1, 1, 24], + [3, 2, 0, 5, 1, 24], + [3, 0, 1, 5, 1, 24], + [1, 3, 0, 6, 2, 24], + [1, 1, 1, 6, 2, 24], + [3, 1, 0, 10, 2, 24], + [2, 3, 0, 3, 3, 24], + [2, 1, 1, 3, 3, 24], + [1, 2, 0, 11, 3, 24], + [1, 0, 1, 11, 3, 24], + [0, 4, 0, 4, 4, 24], + [0, 2, 1, 4, 4, 24], + [0, 0, 2, 4, 4, 24], + [1, 1, 0, 16, 4, 24], + [0, 3, 0, 9, 5, 24], + [0, 1, 1, 9, 5, 24], + [5, 2, 0, 0, 0, 25], + [0, 5, 0, 0, 0, 25], + [5, 0, 1, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [3, 3, 0, 1, 1, 25], + [3, 1, 1, 1, 1, 25], + [0, 4, 0, 5, 1, 25], + [0, 2, 1, 5, 1, 25], + [0, 0, 2, 5, 1, 25], + [1, 4, 0, 2, 2, 25], + [1, 2, 1, 2, 2, 25], + [1, 0, 2, 2, 2, 25], + [3, 2, 0, 6, 2, 25], + [3, 0, 1, 6, 2, 25], + [0, 3, 0, 10, 2, 25], + [0, 1, 1, 10, 2, 25], + [1, 3, 0, 7, 3, 25], + [1, 1, 1, 7, 3, 25], + [0, 2, 0, 15, 3, 25], + [0, 0, 1, 15, 3, 25], + [1, 2, 0, 12, 4, 25], + [1, 0, 1, 12, 4, 25], + [0, 1, 0, 20, 4, 25], + [0, 0, 0, 25, 5, 25], + [0, 5, 0, 1, 1, 26], + [0, 3, 1, 1, 1, 26], + [0, 1, 2, 1, 1, 26], + [0, 4, 0, 6, 2, 26], + [0, 2, 1, 6, 2, 26], + [0, 0, 2, 6, 2, 26], + [0, 3, 0, 11, 3, 26], + [0, 1, 1, 11, 3, 26], + [0, 2, 0, 16, 4, 26], + [0, 0, 1, 16, 4, 26], + [0, 1, 0, 21, 5, 26], + [0, 5, 0, 2, 2, 27], + [0, 3, 1, 2, 2, 27], + [0, 1, 2, 2, 2, 27], + [0, 4, 0, 7, 3, 27], + [0, 2, 1, 7, 3, 27], + [0, 0, 2, 7, 3, 27], + [0, 3, 0, 12, 4, 27], + [0, 1, 1, 12, 4, 27], + [0, 2, 0, 17, 5, 27], + [0, 0, 1, 17, 5, 27], + [0, 0, 2, 8, 4, 28], + [0, 0, 2, 9, 5, 29], + [0, 0, 3, 0, 0, 30], + [0, 0, 2, 10, 2, 30], + [0, 0, 1, 20, 4, 30], + [0, 0, 3, 1, 1, 31], + [0, 0, 2, 11, 3, 31], + [0, 0, 1, 21, 5, 31], + [0, 0, 3, 2, 2, 32], + [0, 0, 2, 12, 4, 32], + ], + 24: [ + [3, 3, 0, 0, 0, 24], + [3, 1, 1, 0, 0, 24], + [1, 4, 0, 1, 1, 24], + [1, 2, 1, 1, 1, 24], + [1, 0, 2, 1, 1, 24], + [3, 2, 0, 5, 1, 24], + [3, 0, 1, 5, 1, 24], + [1, 3, 0, 6, 2, 24], + [1, 1, 1, 6, 2, 24], + [3, 1, 0, 10, 2, 24], + [2, 3, 0, 3, 3, 24], + [2, 1, 1, 3, 3, 24], + [1, 2, 0, 11, 3, 24], + [1, 0, 1, 11, 3, 24], + [0, 4, 0, 4, 4, 24], + [0, 2, 1, 4, 4, 24], + [0, 0, 2, 4, 4, 24], + [1, 1, 0, 16, 4, 24], + [0, 3, 0, 9, 5, 24], + [0, 1, 1, 9, 5, 24], + [5, 2, 0, 0, 0, 25], + [0, 5, 0, 0, 0, 25], + [5, 0, 1, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [3, 3, 0, 1, 1, 25], + [3, 1, 1, 1, 1, 25], + [0, 4, 0, 5, 1, 25], + [0, 2, 1, 5, 1, 25], + [0, 0, 2, 5, 1, 25], + [1, 4, 0, 2, 2, 25], + [1, 2, 1, 2, 2, 25], + [1, 0, 2, 2, 2, 25], + [3, 2, 0, 6, 2, 25], + [3, 0, 1, 6, 2, 25], + [0, 3, 0, 10, 2, 25], + [0, 1, 1, 10, 2, 25], + [1, 3, 0, 7, 3, 25], + [1, 1, 1, 7, 3, 25], + [0, 2, 0, 15, 3, 25], + [0, 0, 1, 15, 3, 25], + [1, 2, 0, 12, 4, 25], + [1, 0, 1, 12, 4, 25], + [0, 1, 0, 20, 4, 25], + [0, 0, 0, 25, 5, 25], + [2, 4, 0, 0, 0, 26], + [2, 2, 1, 0, 0, 26], + [2, 0, 2, 0, 0, 26], + [0, 5, 0, 1, 1, 26], + [0, 3, 1, 1, 1, 26], + [0, 1, 2, 1, 1, 26], + [2, 3, 0, 5, 1, 26], + [2, 1, 1, 5, 1, 26], + [3, 3, 0, 2, 2, 26], + [3, 1, 1, 2, 2, 26], + [0, 4, 0, 6, 2, 26], + [0, 2, 1, 6, 2, 26], + [0, 0, 2, 6, 2, 26], + [2, 2, 0, 10, 2, 26], + [2, 0, 1, 10, 2, 26], + [1, 4, 0, 3, 3, 26], + [1, 2, 1, 3, 3, 26], + [1, 0, 2, 3, 3, 26], + [0, 3, 0, 11, 3, 26], + [0, 1, 1, 11, 3, 26], + [2, 1, 0, 15, 3, 26], + [1, 3, 0, 8, 4, 26], + [1, 1, 1, 8, 4, 26], + [0, 2, 0, 16, 4, 26], + [0, 0, 1, 16, 4, 26], + [0, 1, 0, 21, 5, 26], + [0, 5, 0, 2, 2, 27], + [0, 3, 1, 2, 2, 27], + [0, 1, 2, 2, 2, 27], + [0, 4, 0, 7, 3, 27], + [0, 2, 1, 7, 3, 27], + [0, 0, 2, 7, 3, 27], + [0, 3, 0, 12, 4, 27], + [0, 1, 1, 12, 4, 27], + [0, 2, 0, 17, 5, 27], + [0, 0, 1, 17, 5, 27], + [0, 5, 0, 3, 3, 28], + [0, 3, 1, 3, 3, 28], + [0, 1, 2, 3, 3, 28], + [0, 4, 0, 8, 4, 28], + [0, 2, 1, 8, 4, 28], + [0, 0, 2, 8, 4, 28], + [0, 3, 0, 13, 5, 28], + [0, 1, 1, 13, 5, 28], + [0, 0, 2, 9, 5, 29], + [0, 0, 3, 0, 0, 30], + [0, 0, 2, 10, 2, 30], + [0, 0, 1, 20, 4, 30], + [0, 0, 3, 1, 1, 31], + [0, 0, 2, 11, 3, 31], + [0, 0, 1, 21, 5, 31], + [0, 0, 3, 2, 2, 32], + [0, 0, 2, 12, 4, 32], + [0, 0, 3, 3, 3, 33], + [0, 0, 2, 13, 5, 33], + ], + 25: [ + [5, 2, 0, 0, 0, 25], + [0, 5, 0, 0, 0, 25], + [5, 0, 1, 0, 0, 25], + [0, 3, 1, 0, 0, 25], + [0, 1, 2, 0, 0, 25], + [3, 3, 0, 1, 1, 25], + [3, 1, 1, 1, 1, 25], + [0, 4, 0, 5, 1, 25], + [0, 2, 1, 5, 1, 25], + [0, 0, 2, 5, 1, 25], + [1, 4, 0, 2, 2, 25], + [1, 2, 1, 2, 2, 25], + [1, 0, 2, 2, 2, 25], + [3, 2, 0, 6, 2, 25], + [3, 0, 1, 6, 2, 25], + [0, 3, 0, 10, 2, 25], + [0, 1, 1, 10, 2, 25], + [1, 3, 0, 7, 3, 25], + [1, 1, 1, 7, 3, 25], + [0, 2, 0, 15, 3, 25], + [0, 0, 1, 15, 3, 25], + [1, 2, 0, 12, 4, 25], + [1, 0, 1, 12, 4, 25], + [0, 1, 0, 20, 4, 25], + [0, 0, 0, 25, 5, 25], + [2, 4, 0, 0, 0, 26], + [2, 2, 1, 0, 0, 26], + [2, 0, 2, 0, 0, 26], + [0, 5, 0, 1, 1, 26], + [0, 3, 1, 1, 1, 26], + [0, 1, 2, 1, 1, 26], + [2, 3, 0, 5, 1, 26], + [2, 1, 1, 5, 1, 26], + [3, 3, 0, 2, 2, 26], + [3, 1, 1, 2, 2, 26], + [0, 4, 0, 6, 2, 26], + [0, 2, 1, 6, 2, 26], + [0, 0, 2, 6, 2, 26], + [2, 2, 0, 10, 2, 26], + [2, 0, 1, 10, 2, 26], + [1, 4, 0, 3, 3, 26], + [1, 2, 1, 3, 3, 26], + [1, 0, 2, 3, 3, 26], + [0, 3, 0, 11, 3, 26], + [0, 1, 1, 11, 3, 26], + [2, 1, 0, 15, 3, 26], + [1, 3, 0, 8, 4, 26], + [1, 1, 1, 8, 4, 26], + [0, 2, 0, 16, 4, 26], + [0, 0, 1, 16, 4, 26], + [0, 1, 0, 21, 5, 26], + [4, 3, 0, 0, 0, 27], + [4, 1, 1, 0, 0, 27], + [2, 4, 0, 1, 1, 27], + [2, 2, 1, 1, 1, 27], + [2, 0, 2, 1, 1, 27], + [4, 2, 0, 5, 1, 27], + [4, 0, 1, 5, 1, 27], + [0, 5, 0, 2, 2, 27], + [0, 3, 1, 2, 2, 27], + [0, 1, 2, 2, 2, 27], + [2, 3, 0, 6, 2, 27], + [2, 1, 1, 6, 2, 27], + [0, 4, 0, 7, 3, 27], + [0, 2, 1, 7, 3, 27], + [0, 0, 2, 7, 3, 27], + [2, 2, 0, 11, 3, 27], + [2, 0, 1, 11, 3, 27], + [1, 4, 0, 4, 4, 27], + [1, 2, 1, 4, 4, 27], + [1, 0, 2, 4, 4, 27], + [0, 3, 0, 12, 4, 27], + [0, 1, 1, 12, 4, 27], + [0, 2, 0, 17, 5, 27], + [0, 0, 1, 17, 5, 27], + [0, 5, 0, 3, 3, 28], + [0, 3, 1, 3, 3, 28], + [0, 1, 2, 3, 3, 28], + [0, 4, 0, 8, 4, 28], + [0, 2, 1, 8, 4, 28], + [0, 0, 2, 8, 4, 28], + [0, 3, 0, 13, 5, 28], + [0, 1, 1, 13, 5, 28], + [0, 5, 0, 4, 4, 29], + [0, 3, 1, 4, 4, 29], + [0, 1, 2, 4, 4, 29], + [0, 4, 0, 9, 5, 29], + [0, 2, 1, 9, 5, 29], + [0, 0, 2, 9, 5, 29], + [0, 0, 3, 0, 0, 30], + [0, 0, 2, 10, 2, 30], + [0, 0, 1, 20, 4, 30], + [0, 0, 3, 1, 1, 31], + [0, 0, 2, 11, 3, 31], + [0, 0, 1, 21, 5, 31], + [0, 0, 3, 2, 2, 32], + [0, 0, 2, 12, 4, 32], + [0, 0, 3, 3, 3, 33], + [0, 0, 2, 13, 5, 33], + [0, 0, 3, 4, 4, 34], + ], + 26: [ + [2, 4, 0, 0, 0, 26], + [2, 2, 1, 0, 0, 26], + [2, 0, 2, 0, 0, 26], + [0, 5, 0, 1, 1, 26], + [0, 3, 1, 1, 1, 26], + [0, 1, 2, 1, 1, 26], + [2, 3, 0, 5, 1, 26], + [2, 1, 1, 5, 1, 26], + [3, 3, 0, 2, 2, 26], + [3, 1, 1, 2, 2, 26], + [0, 4, 0, 6, 2, 26], + [0, 2, 1, 6, 2, 26], + [0, 0, 2, 6, 2, 26], + [2, 2, 0, 10, 2, 26], + [2, 0, 1, 10, 2, 26], + [1, 4, 0, 3, 3, 26], + [1, 2, 1, 3, 3, 26], + [1, 0, 2, 3, 3, 26], + [0, 3, 0, 11, 3, 26], + [0, 1, 1, 11, 3, 26], + [2, 1, 0, 15, 3, 26], + [1, 3, 0, 8, 4, 26], + [1, 1, 1, 8, 4, 26], + [0, 2, 0, 16, 4, 26], + [0, 0, 1, 16, 4, 26], + [0, 1, 0, 21, 5, 26], + [4, 3, 0, 0, 0, 27], + [4, 1, 1, 0, 0, 27], + [2, 4, 0, 1, 1, 27], + [2, 2, 1, 1, 1, 27], + [2, 0, 2, 1, 1, 27], + [4, 2, 0, 5, 1, 27], + [4, 0, 1, 5, 1, 27], + [0, 5, 0, 2, 2, 27], + [0, 3, 1, 2, 2, 27], + [0, 1, 2, 2, 2, 27], + [2, 3, 0, 6, 2, 27], + [2, 1, 1, 6, 2, 27], + [0, 4, 0, 7, 3, 27], + [0, 2, 1, 7, 3, 27], + [0, 0, 2, 7, 3, 27], + [2, 2, 0, 11, 3, 27], + [2, 0, 1, 11, 3, 27], + [1, 4, 0, 4, 4, 27], + [1, 2, 1, 4, 4, 27], + [1, 0, 2, 4, 4, 27], + [0, 3, 0, 12, 4, 27], + [0, 1, 1, 12, 4, 27], + [0, 2, 0, 17, 5, 27], + [0, 0, 1, 17, 5, 27], + [1, 5, 0, 0, 0, 28], + [1, 3, 1, 0, 0, 28], + [1, 1, 2, 0, 0, 28], + [4, 3, 0, 1, 1, 28], + [4, 1, 1, 1, 1, 28], + [1, 4, 0, 5, 1, 28], + [1, 2, 1, 5, 1, 28], + [1, 0, 2, 5, 1, 28], + [2, 4, 0, 2, 2, 28], + [2, 2, 1, 2, 2, 28], + [2, 0, 2, 2, 2, 28], + [1, 3, 0, 10, 2, 28], + [1, 1, 1, 10, 2, 28], + [0, 5, 0, 3, 3, 28], + [0, 3, 1, 3, 3, 28], + [0, 1, 2, 3, 3, 28], + [2, 3, 0, 7, 3, 28], + [2, 1, 1, 7, 3, 28], + [1, 2, 0, 15, 3, 28], + [1, 0, 1, 15, 3, 28], + [0, 4, 0, 8, 4, 28], + [0, 2, 1, 8, 4, 28], + [0, 0, 2, 8, 4, 28], + [1, 1, 0, 20, 4, 28], + [0, 3, 0, 13, 5, 28], + [0, 1, 1, 13, 5, 28], + [0, 5, 0, 4, 4, 29], + [0, 3, 1, 4, 4, 29], + [0, 1, 2, 4, 4, 29], + [0, 4, 0, 9, 5, 29], + [0, 2, 1, 9, 5, 29], + [0, 0, 2, 9, 5, 29], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [0, 5, 0, 5, 1, 30], + [0, 3, 1, 5, 1, 30], + [0, 1, 2, 5, 1, 30], + [0, 4, 0, 10, 2, 30], + [0, 2, 1, 10, 2, 30], + [0, 0, 2, 10, 2, 30], + [0, 3, 0, 15, 3, 30], + [0, 1, 1, 15, 3, 30], + [0, 2, 0, 20, 4, 30], + [0, 0, 1, 20, 4, 30], + [0, 1, 0, 25, 5, 30], + [0, 0, 3, 1, 1, 31], + [0, 0, 2, 11, 3, 31], + [0, 0, 1, 21, 5, 31], + [0, 0, 3, 2, 2, 32], + [0, 0, 2, 12, 4, 32], + [0, 0, 3, 3, 3, 33], + [0, 0, 2, 13, 5, 33], + [0, 0, 3, 4, 4, 34], + [0, 0, 3, 5, 1, 35], + [0, 0, 2, 15, 3, 35], + [0, 0, 1, 25, 5, 35], + ], + 27: [ + [4, 3, 0, 0, 0, 27], + [4, 1, 1, 0, 0, 27], + [2, 4, 0, 1, 1, 27], + [2, 2, 1, 1, 1, 27], + [2, 0, 2, 1, 1, 27], + [4, 2, 0, 5, 1, 27], + [4, 0, 1, 5, 1, 27], + [0, 5, 0, 2, 2, 27], + [0, 3, 1, 2, 2, 27], + [0, 1, 2, 2, 2, 27], + [2, 3, 0, 6, 2, 27], + [2, 1, 1, 6, 2, 27], + [0, 4, 0, 7, 3, 27], + [0, 2, 1, 7, 3, 27], + [0, 0, 2, 7, 3, 27], + [2, 2, 0, 11, 3, 27], + [2, 0, 1, 11, 3, 27], + [1, 4, 0, 4, 4, 27], + [1, 2, 1, 4, 4, 27], + [1, 0, 2, 4, 4, 27], + [0, 3, 0, 12, 4, 27], + [0, 1, 1, 12, 4, 27], + [0, 2, 0, 17, 5, 27], + [0, 0, 1, 17, 5, 27], + [1, 5, 0, 0, 0, 28], + [1, 3, 1, 0, 0, 28], + [1, 1, 2, 0, 0, 28], + [4, 3, 0, 1, 1, 28], + [4, 1, 1, 1, 1, 28], + [1, 4, 0, 5, 1, 28], + [1, 2, 1, 5, 1, 28], + [1, 0, 2, 5, 1, 28], + [2, 4, 0, 2, 2, 28], + [2, 2, 1, 2, 2, 28], + [2, 0, 2, 2, 2, 28], + [1, 3, 0, 10, 2, 28], + [1, 1, 1, 10, 2, 28], + [0, 5, 0, 3, 3, 28], + [0, 3, 1, 3, 3, 28], + [0, 1, 2, 3, 3, 28], + [2, 3, 0, 7, 3, 28], + [2, 1, 1, 7, 3, 28], + [1, 2, 0, 15, 3, 28], + [1, 0, 1, 15, 3, 28], + [0, 4, 0, 8, 4, 28], + [0, 2, 1, 8, 4, 28], + [0, 0, 2, 8, 4, 28], + [1, 1, 0, 20, 4, 28], + [0, 3, 0, 13, 5, 28], + [0, 1, 1, 13, 5, 28], + [3, 4, 0, 0, 0, 29], + [3, 2, 1, 0, 0, 29], + [3, 0, 2, 0, 0, 29], + [1, 5, 0, 1, 1, 29], + [1, 3, 1, 1, 1, 29], + [1, 1, 2, 1, 1, 29], + [3, 3, 0, 5, 1, 29], + [3, 1, 1, 5, 1, 29], + [1, 4, 0, 6, 2, 29], + [1, 2, 1, 6, 2, 29], + [1, 0, 2, 6, 2, 29], + [3, 2, 0, 10, 2, 29], + [3, 0, 1, 10, 2, 29], + [2, 4, 0, 3, 3, 29], + [2, 2, 1, 3, 3, 29], + [2, 0, 2, 3, 3, 29], + [1, 3, 0, 11, 3, 29], + [1, 1, 1, 11, 3, 29], + [0, 5, 0, 4, 4, 29], + [0, 3, 1, 4, 4, 29], + [0, 1, 2, 4, 4, 29], + [1, 2, 0, 16, 4, 29], + [1, 0, 1, 16, 4, 29], + [0, 4, 0, 9, 5, 29], + [0, 2, 1, 9, 5, 29], + [0, 0, 2, 9, 5, 29], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [0, 5, 0, 5, 1, 30], + [0, 3, 1, 5, 1, 30], + [0, 1, 2, 5, 1, 30], + [0, 4, 0, 10, 2, 30], + [0, 2, 1, 10, 2, 30], + [0, 0, 2, 10, 2, 30], + [0, 3, 0, 15, 3, 30], + [0, 1, 1, 15, 3, 30], + [0, 2, 0, 20, 4, 30], + [0, 0, 1, 20, 4, 30], + [0, 1, 0, 25, 5, 30], + [0, 4, 1, 1, 1, 31], + [0, 2, 2, 1, 1, 31], + [0, 0, 3, 1, 1, 31], + [0, 5, 0, 6, 2, 31], + [0, 3, 1, 6, 2, 31], + [0, 1, 2, 6, 2, 31], + [0, 4, 0, 11, 3, 31], + [0, 2, 1, 11, 3, 31], + [0, 0, 2, 11, 3, 31], + [0, 3, 0, 16, 4, 31], + [0, 1, 1, 16, 4, 31], + [0, 2, 0, 21, 5, 31], + [0, 0, 1, 21, 5, 31], + [0, 0, 3, 2, 2, 32], + [0, 0, 2, 12, 4, 32], + [0, 0, 3, 3, 3, 33], + [0, 0, 2, 13, 5, 33], + [0, 0, 3, 4, 4, 34], + [0, 0, 3, 5, 1, 35], + [0, 0, 2, 15, 3, 35], + [0, 0, 1, 25, 5, 35], + [0, 0, 3, 6, 2, 36], + [0, 0, 2, 16, 4, 36], + ], + 28: [ + [1, 5, 0, 0, 0, 28], + [1, 3, 1, 0, 0, 28], + [1, 1, 2, 0, 0, 28], + [4, 3, 0, 1, 1, 28], + [4, 1, 1, 1, 1, 28], + [1, 4, 0, 5, 1, 28], + [1, 2, 1, 5, 1, 28], + [1, 0, 2, 5, 1, 28], + [2, 4, 0, 2, 2, 28], + [2, 2, 1, 2, 2, 28], + [2, 0, 2, 2, 2, 28], + [1, 3, 0, 10, 2, 28], + [1, 1, 1, 10, 2, 28], + [0, 5, 0, 3, 3, 28], + [0, 3, 1, 3, 3, 28], + [0, 1, 2, 3, 3, 28], + [2, 3, 0, 7, 3, 28], + [2, 1, 1, 7, 3, 28], + [1, 2, 0, 15, 3, 28], + [1, 0, 1, 15, 3, 28], + [0, 4, 0, 8, 4, 28], + [0, 2, 1, 8, 4, 28], + [0, 0, 2, 8, 4, 28], + [1, 1, 0, 20, 4, 28], + [0, 3, 0, 13, 5, 28], + [0, 1, 1, 13, 5, 28], + [3, 4, 0, 0, 0, 29], + [3, 2, 1, 0, 0, 29], + [3, 0, 2, 0, 0, 29], + [1, 5, 0, 1, 1, 29], + [1, 3, 1, 1, 1, 29], + [1, 1, 2, 1, 1, 29], + [3, 3, 0, 5, 1, 29], + [3, 1, 1, 5, 1, 29], + [1, 4, 0, 6, 2, 29], + [1, 2, 1, 6, 2, 29], + [1, 0, 2, 6, 2, 29], + [3, 2, 0, 10, 2, 29], + [3, 0, 1, 10, 2, 29], + [2, 4, 0, 3, 3, 29], + [2, 2, 1, 3, 3, 29], + [2, 0, 2, 3, 3, 29], + [1, 3, 0, 11, 3, 29], + [1, 1, 1, 11, 3, 29], + [0, 5, 0, 4, 4, 29], + [0, 3, 1, 4, 4, 29], + [0, 1, 2, 4, 4, 29], + [1, 2, 0, 16, 4, 29], + [1, 0, 1, 16, 4, 29], + [0, 4, 0, 9, 5, 29], + [0, 2, 1, 9, 5, 29], + [0, 0, 2, 9, 5, 29], + [5, 3, 0, 0, 0, 30], + [5, 1, 1, 0, 0, 30], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [3, 4, 0, 1, 1, 30], + [3, 2, 1, 1, 1, 30], + [3, 0, 2, 1, 1, 30], + [0, 5, 0, 5, 1, 30], + [0, 3, 1, 5, 1, 30], + [0, 1, 2, 5, 1, 30], + [1, 5, 0, 2, 2, 30], + [1, 3, 1, 2, 2, 30], + [1, 1, 2, 2, 2, 30], + [3, 3, 0, 6, 2, 30], + [3, 1, 1, 6, 2, 30], + [0, 4, 0, 10, 2, 30], + [0, 2, 1, 10, 2, 30], + [0, 0, 2, 10, 2, 30], + [1, 4, 0, 7, 3, 30], + [1, 2, 1, 7, 3, 30], + [1, 0, 2, 7, 3, 30], + [0, 3, 0, 15, 3, 30], + [0, 1, 1, 15, 3, 30], + [1, 3, 0, 12, 4, 30], + [1, 1, 1, 12, 4, 30], + [0, 2, 0, 20, 4, 30], + [0, 0, 1, 20, 4, 30], + [0, 1, 0, 25, 5, 30], + [0, 4, 1, 1, 1, 31], + [0, 2, 2, 1, 1, 31], + [0, 0, 3, 1, 1, 31], + [0, 5, 0, 6, 2, 31], + [0, 3, 1, 6, 2, 31], + [0, 1, 2, 6, 2, 31], + [0, 4, 0, 11, 3, 31], + [0, 2, 1, 11, 3, 31], + [0, 0, 2, 11, 3, 31], + [0, 3, 0, 16, 4, 31], + [0, 1, 1, 16, 4, 31], + [0, 2, 0, 21, 5, 31], + [0, 0, 1, 21, 5, 31], + [0, 4, 1, 2, 2, 32], + [0, 2, 2, 2, 2, 32], + [0, 0, 3, 2, 2, 32], + [0, 5, 0, 7, 3, 32], + [0, 3, 1, 7, 3, 32], + [0, 1, 2, 7, 3, 32], + [0, 4, 0, 12, 4, 32], + [0, 2, 1, 12, 4, 32], + [0, 0, 2, 12, 4, 32], + [0, 3, 0, 17, 5, 32], + [0, 1, 1, 17, 5, 32], + [0, 0, 3, 3, 3, 33], + [0, 0, 2, 13, 5, 33], + [0, 0, 3, 4, 4, 34], + [0, 0, 3, 5, 1, 35], + [0, 0, 2, 15, 3, 35], + [0, 0, 1, 25, 5, 35], + [0, 0, 3, 6, 2, 36], + [0, 0, 2, 16, 4, 36], + [0, 0, 3, 7, 3, 37], + [0, 0, 2, 17, 5, 37], + ], + 29: [ + [3, 4, 0, 0, 0, 29], + [3, 2, 1, 0, 0, 29], + [3, 0, 2, 0, 0, 29], + [1, 5, 0, 1, 1, 29], + [1, 3, 1, 1, 1, 29], + [1, 1, 2, 1, 1, 29], + [3, 3, 0, 5, 1, 29], + [3, 1, 1, 5, 1, 29], + [1, 4, 0, 6, 2, 29], + [1, 2, 1, 6, 2, 29], + [1, 0, 2, 6, 2, 29], + [3, 2, 0, 10, 2, 29], + [3, 0, 1, 10, 2, 29], + [2, 4, 0, 3, 3, 29], + [2, 2, 1, 3, 3, 29], + [2, 0, 2, 3, 3, 29], + [1, 3, 0, 11, 3, 29], + [1, 1, 1, 11, 3, 29], + [0, 5, 0, 4, 4, 29], + [0, 3, 1, 4, 4, 29], + [0, 1, 2, 4, 4, 29], + [1, 2, 0, 16, 4, 29], + [1, 0, 1, 16, 4, 29], + [0, 4, 0, 9, 5, 29], + [0, 2, 1, 9, 5, 29], + [0, 0, 2, 9, 5, 29], + [5, 3, 0, 0, 0, 30], + [5, 1, 1, 0, 0, 30], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [3, 4, 0, 1, 1, 30], + [3, 2, 1, 1, 1, 30], + [3, 0, 2, 1, 1, 30], + [0, 5, 0, 5, 1, 30], + [0, 3, 1, 5, 1, 30], + [0, 1, 2, 5, 1, 30], + [1, 5, 0, 2, 2, 30], + [1, 3, 1, 2, 2, 30], + [1, 1, 2, 2, 2, 30], + [3, 3, 0, 6, 2, 30], + [3, 1, 1, 6, 2, 30], + [0, 4, 0, 10, 2, 30], + [0, 2, 1, 10, 2, 30], + [0, 0, 2, 10, 2, 30], + [1, 4, 0, 7, 3, 30], + [1, 2, 1, 7, 3, 30], + [1, 0, 2, 7, 3, 30], + [0, 3, 0, 15, 3, 30], + [0, 1, 1, 15, 3, 30], + [1, 3, 0, 12, 4, 30], + [1, 1, 1, 12, 4, 30], + [0, 2, 0, 20, 4, 30], + [0, 0, 1, 20, 4, 30], + [0, 1, 0, 25, 5, 30], + [2, 5, 0, 0, 0, 31], + [2, 3, 1, 0, 0, 31], + [2, 1, 2, 0, 0, 31], + [0, 4, 1, 1, 1, 31], + [0, 2, 2, 1, 1, 31], + [0, 0, 3, 1, 1, 31], + [2, 4, 0, 5, 1, 31], + [2, 2, 1, 5, 1, 31], + [2, 0, 2, 5, 1, 31], + [3, 4, 0, 2, 2, 31], + [3, 2, 1, 2, 2, 31], + [3, 0, 2, 2, 2, 31], + [0, 5, 0, 6, 2, 31], + [0, 3, 1, 6, 2, 31], + [0, 1, 2, 6, 2, 31], + [2, 3, 0, 10, 2, 31], + [2, 1, 1, 10, 2, 31], + [1, 5, 0, 3, 3, 31], + [1, 3, 1, 3, 3, 31], + [1, 1, 2, 3, 3, 31], + [0, 4, 0, 11, 3, 31], + [0, 2, 1, 11, 3, 31], + [0, 0, 2, 11, 3, 31], + [2, 2, 0, 15, 3, 31], + [2, 0, 1, 15, 3, 31], + [1, 4, 0, 8, 4, 31], + [1, 2, 1, 8, 4, 31], + [1, 0, 2, 8, 4, 31], + [0, 3, 0, 16, 4, 31], + [0, 1, 1, 16, 4, 31], + [0, 2, 0, 21, 5, 31], + [0, 0, 1, 21, 5, 31], + [0, 4, 1, 2, 2, 32], + [0, 2, 2, 2, 2, 32], + [0, 0, 3, 2, 2, 32], + [0, 5, 0, 7, 3, 32], + [0, 3, 1, 7, 3, 32], + [0, 1, 2, 7, 3, 32], + [0, 4, 0, 12, 4, 32], + [0, 2, 1, 12, 4, 32], + [0, 0, 2, 12, 4, 32], + [0, 3, 0, 17, 5, 32], + [0, 1, 1, 17, 5, 32], + [0, 4, 1, 3, 3, 33], + [0, 2, 2, 3, 3, 33], + [0, 0, 3, 3, 3, 33], + [0, 5, 0, 8, 4, 33], + [0, 3, 1, 8, 4, 33], + [0, 1, 2, 8, 4, 33], + [0, 4, 0, 13, 5, 33], + [0, 2, 1, 13, 5, 33], + [0, 0, 2, 13, 5, 33], + [0, 0, 3, 4, 4, 34], + [0, 0, 3, 5, 1, 35], + [0, 0, 2, 15, 3, 35], + [0, 0, 1, 25, 5, 35], + [0, 0, 3, 6, 2, 36], + [0, 0, 2, 16, 4, 36], + [0, 0, 3, 7, 3, 37], + [0, 0, 2, 17, 5, 37], + [0, 0, 3, 8, 4, 38], + ], + 30: [ + [5, 3, 0, 0, 0, 30], + [5, 1, 1, 0, 0, 30], + [0, 4, 1, 0, 0, 30], + [0, 2, 2, 0, 0, 30], + [0, 0, 3, 0, 0, 30], + [3, 4, 0, 1, 1, 30], + [3, 2, 1, 1, 1, 30], + [3, 0, 2, 1, 1, 30], + [0, 5, 0, 5, 1, 30], + [0, 3, 1, 5, 1, 30], + [0, 1, 2, 5, 1, 30], + [1, 5, 0, 2, 2, 30], + [1, 3, 1, 2, 2, 30], + [1, 1, 2, 2, 2, 30], + [3, 3, 0, 6, 2, 30], + [3, 1, 1, 6, 2, 30], + [0, 4, 0, 10, 2, 30], + [0, 2, 1, 10, 2, 30], + [0, 0, 2, 10, 2, 30], + [1, 4, 0, 7, 3, 30], + [1, 2, 1, 7, 3, 30], + [1, 0, 2, 7, 3, 30], + [0, 3, 0, 15, 3, 30], + [0, 1, 1, 15, 3, 30], + [1, 3, 0, 12, 4, 30], + [1, 1, 1, 12, 4, 30], + [0, 2, 0, 20, 4, 30], + [0, 0, 1, 20, 4, 30], + [0, 1, 0, 25, 5, 30], + [2, 5, 0, 0, 0, 31], + [2, 3, 1, 0, 0, 31], + [2, 1, 2, 0, 0, 31], + [0, 4, 1, 1, 1, 31], + [0, 2, 2, 1, 1, 31], + [0, 0, 3, 1, 1, 31], + [2, 4, 0, 5, 1, 31], + [2, 2, 1, 5, 1, 31], + [2, 0, 2, 5, 1, 31], + [3, 4, 0, 2, 2, 31], + [3, 2, 1, 2, 2, 31], + [3, 0, 2, 2, 2, 31], + [0, 5, 0, 6, 2, 31], + [0, 3, 1, 6, 2, 31], + [0, 1, 2, 6, 2, 31], + [2, 3, 0, 10, 2, 31], + [2, 1, 1, 10, 2, 31], + [1, 5, 0, 3, 3, 31], + [1, 3, 1, 3, 3, 31], + [1, 1, 2, 3, 3, 31], + [0, 4, 0, 11, 3, 31], + [0, 2, 1, 11, 3, 31], + [0, 0, 2, 11, 3, 31], + [2, 2, 0, 15, 3, 31], + [2, 0, 1, 15, 3, 31], + [1, 4, 0, 8, 4, 31], + [1, 2, 1, 8, 4, 31], + [1, 0, 2, 8, 4, 31], + [0, 3, 0, 16, 4, 31], + [0, 1, 1, 16, 4, 31], + [0, 2, 0, 21, 5, 31], + [0, 0, 1, 21, 5, 31], + [4, 4, 0, 0, 0, 32], + [4, 2, 1, 0, 0, 32], + [4, 0, 2, 0, 0, 32], + [2, 5, 0, 1, 1, 32], + [2, 3, 1, 1, 1, 32], + [2, 1, 2, 1, 1, 32], + [4, 3, 0, 5, 1, 32], + [4, 1, 1, 5, 1, 32], + [0, 4, 1, 2, 2, 32], + [0, 2, 2, 2, 2, 32], + [0, 0, 3, 2, 2, 32], + [2, 4, 0, 6, 2, 32], + [2, 2, 1, 6, 2, 32], + [2, 0, 2, 6, 2, 32], + [0, 5, 0, 7, 3, 32], + [0, 3, 1, 7, 3, 32], + [0, 1, 2, 7, 3, 32], + [2, 3, 0, 11, 3, 32], + [2, 1, 1, 11, 3, 32], + [1, 5, 0, 4, 4, 32], + [1, 3, 1, 4, 4, 32], + [1, 1, 2, 4, 4, 32], + [0, 4, 0, 12, 4, 32], + [0, 2, 1, 12, 4, 32], + [0, 0, 2, 12, 4, 32], + [0, 3, 0, 17, 5, 32], + [0, 1, 1, 17, 5, 32], + [0, 4, 1, 3, 3, 33], + [0, 2, 2, 3, 3, 33], + [0, 0, 3, 3, 3, 33], + [0, 5, 0, 8, 4, 33], + [0, 3, 1, 8, 4, 33], + [0, 1, 2, 8, 4, 33], + [0, 4, 0, 13, 5, 33], + [0, 2, 1, 13, 5, 33], + [0, 0, 2, 13, 5, 33], + [0, 4, 1, 4, 4, 34], + [0, 2, 2, 4, 4, 34], + [0, 0, 3, 4, 4, 34], + [0, 5, 0, 9, 5, 34], + [0, 3, 1, 9, 5, 34], + [0, 1, 2, 9, 5, 34], + [0, 0, 3, 5, 1, 35], + [0, 0, 2, 15, 3, 35], + [0, 0, 1, 25, 5, 35], + [0, 0, 3, 6, 2, 36], + [0, 0, 2, 16, 4, 36], + [0, 0, 3, 7, 3, 37], + [0, 0, 2, 17, 5, 37], + [0, 0, 3, 8, 4, 38], + [0, 0, 3, 9, 5, 39], + ], + 31: [ + [2, 5, 0, 0, 0, 31], + [2, 3, 1, 0, 0, 31], + [2, 1, 2, 0, 0, 31], + [0, 4, 1, 1, 1, 31], + [0, 2, 2, 1, 1, 31], + [0, 0, 3, 1, 1, 31], + [2, 4, 0, 5, 1, 31], + [2, 2, 1, 5, 1, 31], + [2, 0, 2, 5, 1, 31], + [3, 4, 0, 2, 2, 31], + [3, 2, 1, 2, 2, 31], + [3, 0, 2, 2, 2, 31], + [0, 5, 0, 6, 2, 31], + [0, 3, 1, 6, 2, 31], + [0, 1, 2, 6, 2, 31], + [2, 3, 0, 10, 2, 31], + [2, 1, 1, 10, 2, 31], + [1, 5, 0, 3, 3, 31], + [1, 3, 1, 3, 3, 31], + [1, 1, 2, 3, 3, 31], + [0, 4, 0, 11, 3, 31], + [0, 2, 1, 11, 3, 31], + [0, 0, 2, 11, 3, 31], + [2, 2, 0, 15, 3, 31], + [2, 0, 1, 15, 3, 31], + [1, 4, 0, 8, 4, 31], + [1, 2, 1, 8, 4, 31], + [1, 0, 2, 8, 4, 31], + [0, 3, 0, 16, 4, 31], + [0, 1, 1, 16, 4, 31], + [0, 2, 0, 21, 5, 31], + [0, 0, 1, 21, 5, 31], + [4, 4, 0, 0, 0, 32], + [4, 2, 1, 0, 0, 32], + [4, 0, 2, 0, 0, 32], + [2, 5, 0, 1, 1, 32], + [2, 3, 1, 1, 1, 32], + [2, 1, 2, 1, 1, 32], + [4, 3, 0, 5, 1, 32], + [4, 1, 1, 5, 1, 32], + [0, 4, 1, 2, 2, 32], + [0, 2, 2, 2, 2, 32], + [0, 0, 3, 2, 2, 32], + [2, 4, 0, 6, 2, 32], + [2, 2, 1, 6, 2, 32], + [2, 0, 2, 6, 2, 32], + [0, 5, 0, 7, 3, 32], + [0, 3, 1, 7, 3, 32], + [0, 1, 2, 7, 3, 32], + [2, 3, 0, 11, 3, 32], + [2, 1, 1, 11, 3, 32], + [1, 5, 0, 4, 4, 32], + [1, 3, 1, 4, 4, 32], + [1, 1, 2, 4, 4, 32], + [0, 4, 0, 12, 4, 32], + [0, 2, 1, 12, 4, 32], + [0, 0, 2, 12, 4, 32], + [0, 3, 0, 17, 5, 32], + [0, 1, 1, 17, 5, 32], + [1, 4, 1, 0, 0, 33], + [1, 2, 2, 0, 0, 33], + [1, 0, 3, 0, 0, 33], + [4, 4, 0, 1, 1, 33], + [4, 2, 1, 1, 1, 33], + [4, 0, 2, 1, 1, 33], + [1, 5, 0, 5, 1, 33], + [1, 3, 1, 5, 1, 33], + [1, 1, 2, 5, 1, 33], + [2, 5, 0, 2, 2, 33], + [2, 3, 1, 2, 2, 33], + [2, 1, 2, 2, 2, 33], + [1, 4, 0, 10, 2, 33], + [1, 2, 1, 10, 2, 33], + [1, 0, 2, 10, 2, 33], + [0, 4, 1, 3, 3, 33], + [0, 2, 2, 3, 3, 33], + [0, 0, 3, 3, 3, 33], + [2, 4, 0, 7, 3, 33], + [2, 2, 1, 7, 3, 33], + [2, 0, 2, 7, 3, 33], + [1, 3, 0, 15, 3, 33], + [1, 1, 1, 15, 3, 33], + [0, 5, 0, 8, 4, 33], + [0, 3, 1, 8, 4, 33], + [0, 1, 2, 8, 4, 33], + [1, 2, 0, 20, 4, 33], + [1, 0, 1, 20, 4, 33], + [0, 4, 0, 13, 5, 33], + [0, 2, 1, 13, 5, 33], + [0, 0, 2, 13, 5, 33], + [0, 4, 1, 4, 4, 34], + [0, 2, 2, 4, 4, 34], + [0, 0, 3, 4, 4, 34], + [0, 5, 0, 9, 5, 34], + [0, 3, 1, 9, 5, 34], + [0, 1, 2, 9, 5, 34], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [0, 4, 1, 5, 1, 35], + [0, 2, 2, 5, 1, 35], + [0, 0, 3, 5, 1, 35], + [0, 5, 0, 10, 2, 35], + [0, 3, 1, 10, 2, 35], + [0, 1, 2, 10, 2, 35], + [0, 4, 0, 15, 3, 35], + [0, 2, 1, 15, 3, 35], + [0, 0, 2, 15, 3, 35], + [0, 3, 0, 20, 4, 35], + [0, 1, 1, 20, 4, 35], + [0, 2, 0, 25, 5, 35], + [0, 0, 1, 25, 5, 35], + [0, 0, 3, 6, 2, 36], + [0, 0, 2, 16, 4, 36], + [0, 0, 3, 7, 3, 37], + [0, 0, 2, 17, 5, 37], + [0, 0, 3, 8, 4, 38], + [0, 0, 3, 9, 5, 39], + [0, 0, 4, 0, 0, 40], + [0, 0, 3, 10, 2, 40], + [0, 0, 2, 20, 4, 40], + ], + 32: [ + [4, 4, 0, 0, 0, 32], + [4, 2, 1, 0, 0, 32], + [4, 0, 2, 0, 0, 32], + [2, 5, 0, 1, 1, 32], + [2, 3, 1, 1, 1, 32], + [2, 1, 2, 1, 1, 32], + [4, 3, 0, 5, 1, 32], + [4, 1, 1, 5, 1, 32], + [0, 4, 1, 2, 2, 32], + [0, 2, 2, 2, 2, 32], + [0, 0, 3, 2, 2, 32], + [2, 4, 0, 6, 2, 32], + [2, 2, 1, 6, 2, 32], + [2, 0, 2, 6, 2, 32], + [0, 5, 0, 7, 3, 32], + [0, 3, 1, 7, 3, 32], + [0, 1, 2, 7, 3, 32], + [2, 3, 0, 11, 3, 32], + [2, 1, 1, 11, 3, 32], + [1, 5, 0, 4, 4, 32], + [1, 3, 1, 4, 4, 32], + [1, 1, 2, 4, 4, 32], + [0, 4, 0, 12, 4, 32], + [0, 2, 1, 12, 4, 32], + [0, 0, 2, 12, 4, 32], + [0, 3, 0, 17, 5, 32], + [0, 1, 1, 17, 5, 32], + [1, 4, 1, 0, 0, 33], + [1, 2, 2, 0, 0, 33], + [1, 0, 3, 0, 0, 33], + [4, 4, 0, 1, 1, 33], + [4, 2, 1, 1, 1, 33], + [4, 0, 2, 1, 1, 33], + [1, 5, 0, 5, 1, 33], + [1, 3, 1, 5, 1, 33], + [1, 1, 2, 5, 1, 33], + [2, 5, 0, 2, 2, 33], + [2, 3, 1, 2, 2, 33], + [2, 1, 2, 2, 2, 33], + [1, 4, 0, 10, 2, 33], + [1, 2, 1, 10, 2, 33], + [1, 0, 2, 10, 2, 33], + [0, 4, 1, 3, 3, 33], + [0, 2, 2, 3, 3, 33], + [0, 0, 3, 3, 3, 33], + [2, 4, 0, 7, 3, 33], + [2, 2, 1, 7, 3, 33], + [2, 0, 2, 7, 3, 33], + [1, 3, 0, 15, 3, 33], + [1, 1, 1, 15, 3, 33], + [0, 5, 0, 8, 4, 33], + [0, 3, 1, 8, 4, 33], + [0, 1, 2, 8, 4, 33], + [1, 2, 0, 20, 4, 33], + [1, 0, 1, 20, 4, 33], + [0, 4, 0, 13, 5, 33], + [0, 2, 1, 13, 5, 33], + [0, 0, 2, 13, 5, 33], + [3, 5, 0, 0, 0, 34], + [3, 3, 1, 0, 0, 34], + [3, 1, 2, 0, 0, 34], + [1, 4, 1, 1, 1, 34], + [1, 2, 2, 1, 1, 34], + [1, 0, 3, 1, 1, 34], + [3, 4, 0, 5, 1, 34], + [3, 2, 1, 5, 1, 34], + [3, 0, 2, 5, 1, 34], + [1, 5, 0, 6, 2, 34], + [1, 3, 1, 6, 2, 34], + [1, 1, 2, 6, 2, 34], + [3, 3, 0, 10, 2, 34], + [3, 1, 1, 10, 2, 34], + [2, 5, 0, 3, 3, 34], + [2, 3, 1, 3, 3, 34], + [2, 1, 2, 3, 3, 34], + [1, 4, 0, 11, 3, 34], + [1, 2, 1, 11, 3, 34], + [1, 0, 2, 11, 3, 34], + [0, 4, 1, 4, 4, 34], + [0, 2, 2, 4, 4, 34], + [0, 0, 3, 4, 4, 34], + [1, 3, 0, 16, 4, 34], + [1, 1, 1, 16, 4, 34], + [0, 5, 0, 9, 5, 34], + [0, 3, 1, 9, 5, 34], + [0, 1, 2, 9, 5, 34], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [0, 4, 1, 5, 1, 35], + [0, 2, 2, 5, 1, 35], + [0, 0, 3, 5, 1, 35], + [0, 5, 0, 10, 2, 35], + [0, 3, 1, 10, 2, 35], + [0, 1, 2, 10, 2, 35], + [0, 4, 0, 15, 3, 35], + [0, 2, 1, 15, 3, 35], + [0, 0, 2, 15, 3, 35], + [0, 3, 0, 20, 4, 35], + [0, 1, 1, 20, 4, 35], + [0, 2, 0, 25, 5, 35], + [0, 0, 1, 25, 5, 35], + [0, 3, 2, 1, 1, 36], + [0, 1, 3, 1, 1, 36], + [0, 4, 1, 6, 2, 36], + [0, 2, 2, 6, 2, 36], + [0, 0, 3, 6, 2, 36], + [0, 5, 0, 11, 3, 36], + [0, 3, 1, 11, 3, 36], + [0, 1, 2, 11, 3, 36], + [0, 4, 0, 16, 4, 36], + [0, 2, 1, 16, 4, 36], + [0, 0, 2, 16, 4, 36], + [0, 3, 0, 21, 5, 36], + [0, 1, 1, 21, 5, 36], + [0, 0, 3, 7, 3, 37], + [0, 0, 2, 17, 5, 37], + [0, 0, 3, 8, 4, 38], + [0, 0, 3, 9, 5, 39], + [0, 0, 4, 0, 0, 40], + [0, 0, 3, 10, 2, 40], + [0, 0, 2, 20, 4, 40], + [0, 0, 4, 1, 1, 41], + [0, 0, 3, 11, 3, 41], + [0, 0, 2, 21, 5, 41], + ], + 33: [ + [1, 4, 1, 0, 0, 33], + [1, 2, 2, 0, 0, 33], + [1, 0, 3, 0, 0, 33], + [4, 4, 0, 1, 1, 33], + [4, 2, 1, 1, 1, 33], + [4, 0, 2, 1, 1, 33], + [1, 5, 0, 5, 1, 33], + [1, 3, 1, 5, 1, 33], + [1, 1, 2, 5, 1, 33], + [2, 5, 0, 2, 2, 33], + [2, 3, 1, 2, 2, 33], + [2, 1, 2, 2, 2, 33], + [1, 4, 0, 10, 2, 33], + [1, 2, 1, 10, 2, 33], + [1, 0, 2, 10, 2, 33], + [0, 4, 1, 3, 3, 33], + [0, 2, 2, 3, 3, 33], + [0, 0, 3, 3, 3, 33], + [2, 4, 0, 7, 3, 33], + [2, 2, 1, 7, 3, 33], + [2, 0, 2, 7, 3, 33], + [1, 3, 0, 15, 3, 33], + [1, 1, 1, 15, 3, 33], + [0, 5, 0, 8, 4, 33], + [0, 3, 1, 8, 4, 33], + [0, 1, 2, 8, 4, 33], + [1, 2, 0, 20, 4, 33], + [1, 0, 1, 20, 4, 33], + [0, 4, 0, 13, 5, 33], + [0, 2, 1, 13, 5, 33], + [0, 0, 2, 13, 5, 33], + [3, 5, 0, 0, 0, 34], + [3, 3, 1, 0, 0, 34], + [3, 1, 2, 0, 0, 34], + [1, 4, 1, 1, 1, 34], + [1, 2, 2, 1, 1, 34], + [1, 0, 3, 1, 1, 34], + [3, 4, 0, 5, 1, 34], + [3, 2, 1, 5, 1, 34], + [3, 0, 2, 5, 1, 34], + [1, 5, 0, 6, 2, 34], + [1, 3, 1, 6, 2, 34], + [1, 1, 2, 6, 2, 34], + [3, 3, 0, 10, 2, 34], + [3, 1, 1, 10, 2, 34], + [2, 5, 0, 3, 3, 34], + [2, 3, 1, 3, 3, 34], + [2, 1, 2, 3, 3, 34], + [1, 4, 0, 11, 3, 34], + [1, 2, 1, 11, 3, 34], + [1, 0, 2, 11, 3, 34], + [0, 4, 1, 4, 4, 34], + [0, 2, 2, 4, 4, 34], + [0, 0, 3, 4, 4, 34], + [1, 3, 0, 16, 4, 34], + [1, 1, 1, 16, 4, 34], + [0, 5, 0, 9, 5, 34], + [0, 3, 1, 9, 5, 34], + [0, 1, 2, 9, 5, 34], + [5, 4, 0, 0, 0, 35], + [5, 2, 1, 0, 0, 35], + [5, 0, 2, 0, 0, 35], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [3, 5, 0, 1, 1, 35], + [3, 3, 1, 1, 1, 35], + [3, 1, 2, 1, 1, 35], + [0, 4, 1, 5, 1, 35], + [0, 2, 2, 5, 1, 35], + [0, 0, 3, 5, 1, 35], + [1, 4, 1, 2, 2, 35], + [1, 2, 2, 2, 2, 35], + [1, 0, 3, 2, 2, 35], + [3, 4, 0, 6, 2, 35], + [3, 2, 1, 6, 2, 35], + [3, 0, 2, 6, 2, 35], + [0, 5, 0, 10, 2, 35], + [0, 3, 1, 10, 2, 35], + [0, 1, 2, 10, 2, 35], + [1, 5, 0, 7, 3, 35], + [1, 3, 1, 7, 3, 35], + [1, 1, 2, 7, 3, 35], + [0, 4, 0, 15, 3, 35], + [0, 2, 1, 15, 3, 35], + [0, 0, 2, 15, 3, 35], + [1, 4, 0, 12, 4, 35], + [1, 2, 1, 12, 4, 35], + [1, 0, 2, 12, 4, 35], + [0, 3, 0, 20, 4, 35], + [0, 1, 1, 20, 4, 35], + [0, 2, 0, 25, 5, 35], + [0, 0, 1, 25, 5, 35], + [0, 3, 2, 1, 1, 36], + [0, 1, 3, 1, 1, 36], + [0, 4, 1, 6, 2, 36], + [0, 2, 2, 6, 2, 36], + [0, 0, 3, 6, 2, 36], + [0, 5, 0, 11, 3, 36], + [0, 3, 1, 11, 3, 36], + [0, 1, 2, 11, 3, 36], + [0, 4, 0, 16, 4, 36], + [0, 2, 1, 16, 4, 36], + [0, 0, 2, 16, 4, 36], + [0, 3, 0, 21, 5, 36], + [0, 1, 1, 21, 5, 36], + [0, 3, 2, 2, 2, 37], + [0, 1, 3, 2, 2, 37], + [0, 4, 1, 7, 3, 37], + [0, 2, 2, 7, 3, 37], + [0, 0, 3, 7, 3, 37], + [0, 5, 0, 12, 4, 37], + [0, 3, 1, 12, 4, 37], + [0, 1, 2, 12, 4, 37], + [0, 4, 0, 17, 5, 37], + [0, 2, 1, 17, 5, 37], + [0, 0, 2, 17, 5, 37], + [0, 0, 3, 8, 4, 38], + [0, 0, 3, 9, 5, 39], + [0, 0, 4, 0, 0, 40], + [0, 0, 3, 10, 2, 40], + [0, 0, 2, 20, 4, 40], + [0, 0, 4, 1, 1, 41], + [0, 0, 3, 11, 3, 41], + [0, 0, 2, 21, 5, 41], + [0, 0, 4, 2, 2, 42], + [0, 0, 3, 12, 4, 42], + ], + 34: [ + [3, 5, 0, 0, 0, 34], + [3, 3, 1, 0, 0, 34], + [3, 1, 2, 0, 0, 34], + [1, 4, 1, 1, 1, 34], + [1, 2, 2, 1, 1, 34], + [1, 0, 3, 1, 1, 34], + [3, 4, 0, 5, 1, 34], + [3, 2, 1, 5, 1, 34], + [3, 0, 2, 5, 1, 34], + [1, 5, 0, 6, 2, 34], + [1, 3, 1, 6, 2, 34], + [1, 1, 2, 6, 2, 34], + [3, 3, 0, 10, 2, 34], + [3, 1, 1, 10, 2, 34], + [2, 5, 0, 3, 3, 34], + [2, 3, 1, 3, 3, 34], + [2, 1, 2, 3, 3, 34], + [1, 4, 0, 11, 3, 34], + [1, 2, 1, 11, 3, 34], + [1, 0, 2, 11, 3, 34], + [0, 4, 1, 4, 4, 34], + [0, 2, 2, 4, 4, 34], + [0, 0, 3, 4, 4, 34], + [1, 3, 0, 16, 4, 34], + [1, 1, 1, 16, 4, 34], + [0, 5, 0, 9, 5, 34], + [0, 3, 1, 9, 5, 34], + [0, 1, 2, 9, 5, 34], + [5, 4, 0, 0, 0, 35], + [5, 2, 1, 0, 0, 35], + [5, 0, 2, 0, 0, 35], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [3, 5, 0, 1, 1, 35], + [3, 3, 1, 1, 1, 35], + [3, 1, 2, 1, 1, 35], + [0, 4, 1, 5, 1, 35], + [0, 2, 2, 5, 1, 35], + [0, 0, 3, 5, 1, 35], + [1, 4, 1, 2, 2, 35], + [1, 2, 2, 2, 2, 35], + [1, 0, 3, 2, 2, 35], + [3, 4, 0, 6, 2, 35], + [3, 2, 1, 6, 2, 35], + [3, 0, 2, 6, 2, 35], + [0, 5, 0, 10, 2, 35], + [0, 3, 1, 10, 2, 35], + [0, 1, 2, 10, 2, 35], + [1, 5, 0, 7, 3, 35], + [1, 3, 1, 7, 3, 35], + [1, 1, 2, 7, 3, 35], + [0, 4, 0, 15, 3, 35], + [0, 2, 1, 15, 3, 35], + [0, 0, 2, 15, 3, 35], + [1, 4, 0, 12, 4, 35], + [1, 2, 1, 12, 4, 35], + [1, 0, 2, 12, 4, 35], + [0, 3, 0, 20, 4, 35], + [0, 1, 1, 20, 4, 35], + [0, 2, 0, 25, 5, 35], + [0, 0, 1, 25, 5, 35], + [2, 4, 1, 0, 0, 36], + [2, 2, 2, 0, 0, 36], + [2, 0, 3, 0, 0, 36], + [0, 3, 2, 1, 1, 36], + [0, 1, 3, 1, 1, 36], + [2, 5, 0, 5, 1, 36], + [2, 3, 1, 5, 1, 36], + [2, 1, 2, 5, 1, 36], + [3, 5, 0, 2, 2, 36], + [3, 3, 1, 2, 2, 36], + [3, 1, 2, 2, 2, 36], + [0, 4, 1, 6, 2, 36], + [0, 2, 2, 6, 2, 36], + [0, 0, 3, 6, 2, 36], + [2, 4, 0, 10, 2, 36], + [2, 2, 1, 10, 2, 36], + [2, 0, 2, 10, 2, 36], + [1, 4, 1, 3, 3, 36], + [1, 2, 2, 3, 3, 36], + [1, 0, 3, 3, 3, 36], + [0, 5, 0, 11, 3, 36], + [0, 3, 1, 11, 3, 36], + [0, 1, 2, 11, 3, 36], + [2, 3, 0, 15, 3, 36], + [2, 1, 1, 15, 3, 36], + [1, 5, 0, 8, 4, 36], + [1, 3, 1, 8, 4, 36], + [1, 1, 2, 8, 4, 36], + [0, 4, 0, 16, 4, 36], + [0, 2, 1, 16, 4, 36], + [0, 0, 2, 16, 4, 36], + [0, 3, 0, 21, 5, 36], + [0, 1, 1, 21, 5, 36], + [0, 3, 2, 2, 2, 37], + [0, 1, 3, 2, 2, 37], + [0, 4, 1, 7, 3, 37], + [0, 2, 2, 7, 3, 37], + [0, 0, 3, 7, 3, 37], + [0, 5, 0, 12, 4, 37], + [0, 3, 1, 12, 4, 37], + [0, 1, 2, 12, 4, 37], + [0, 4, 0, 17, 5, 37], + [0, 2, 1, 17, 5, 37], + [0, 0, 2, 17, 5, 37], + [0, 3, 2, 3, 3, 38], + [0, 1, 3, 3, 3, 38], + [0, 4, 1, 8, 4, 38], + [0, 2, 2, 8, 4, 38], + [0, 0, 3, 8, 4, 38], + [0, 5, 0, 13, 5, 38], + [0, 3, 1, 13, 5, 38], + [0, 1, 2, 13, 5, 38], + [0, 0, 3, 9, 5, 39], + [0, 0, 4, 0, 0, 40], + [0, 0, 3, 10, 2, 40], + [0, 0, 2, 20, 4, 40], + [0, 0, 4, 1, 1, 41], + [0, 0, 3, 11, 3, 41], + [0, 0, 2, 21, 5, 41], + [0, 0, 4, 2, 2, 42], + [0, 0, 3, 12, 4, 42], + [0, 0, 4, 3, 3, 43], + [0, 0, 3, 13, 5, 43], + ], + 35: [ + [5, 4, 0, 0, 0, 35], + [5, 2, 1, 0, 0, 35], + [5, 0, 2, 0, 0, 35], + [0, 3, 2, 0, 0, 35], + [0, 1, 3, 0, 0, 35], + [3, 5, 0, 1, 1, 35], + [3, 3, 1, 1, 1, 35], + [3, 1, 2, 1, 1, 35], + [0, 4, 1, 5, 1, 35], + [0, 2, 2, 5, 1, 35], + [0, 0, 3, 5, 1, 35], + [1, 4, 1, 2, 2, 35], + [1, 2, 2, 2, 2, 35], + [1, 0, 3, 2, 2, 35], + [3, 4, 0, 6, 2, 35], + [3, 2, 1, 6, 2, 35], + [3, 0, 2, 6, 2, 35], + [0, 5, 0, 10, 2, 35], + [0, 3, 1, 10, 2, 35], + [0, 1, 2, 10, 2, 35], + [1, 5, 0, 7, 3, 35], + [1, 3, 1, 7, 3, 35], + [1, 1, 2, 7, 3, 35], + [0, 4, 0, 15, 3, 35], + [0, 2, 1, 15, 3, 35], + [0, 0, 2, 15, 3, 35], + [1, 4, 0, 12, 4, 35], + [1, 2, 1, 12, 4, 35], + [1, 0, 2, 12, 4, 35], + [0, 3, 0, 20, 4, 35], + [0, 1, 1, 20, 4, 35], + [0, 2, 0, 25, 5, 35], + [0, 0, 1, 25, 5, 35], + [2, 4, 1, 0, 0, 36], + [2, 2, 2, 0, 0, 36], + [2, 0, 3, 0, 0, 36], + [0, 3, 2, 1, 1, 36], + [0, 1, 3, 1, 1, 36], + [2, 5, 0, 5, 1, 36], + [2, 3, 1, 5, 1, 36], + [2, 1, 2, 5, 1, 36], + [3, 5, 0, 2, 2, 36], + [3, 3, 1, 2, 2, 36], + [3, 1, 2, 2, 2, 36], + [0, 4, 1, 6, 2, 36], + [0, 2, 2, 6, 2, 36], + [0, 0, 3, 6, 2, 36], + [2, 4, 0, 10, 2, 36], + [2, 2, 1, 10, 2, 36], + [2, 0, 2, 10, 2, 36], + [1, 4, 1, 3, 3, 36], + [1, 2, 2, 3, 3, 36], + [1, 0, 3, 3, 3, 36], + [0, 5, 0, 11, 3, 36], + [0, 3, 1, 11, 3, 36], + [0, 1, 2, 11, 3, 36], + [2, 3, 0, 15, 3, 36], + [2, 1, 1, 15, 3, 36], + [1, 5, 0, 8, 4, 36], + [1, 3, 1, 8, 4, 36], + [1, 1, 2, 8, 4, 36], + [0, 4, 0, 16, 4, 36], + [0, 2, 1, 16, 4, 36], + [0, 0, 2, 16, 4, 36], + [0, 3, 0, 21, 5, 36], + [0, 1, 1, 21, 5, 36], + [4, 5, 0, 0, 0, 37], + [4, 3, 1, 0, 0, 37], + [4, 1, 2, 0, 0, 37], + [2, 4, 1, 1, 1, 37], + [2, 2, 2, 1, 1, 37], + [2, 0, 3, 1, 1, 37], + [4, 4, 0, 5, 1, 37], + [4, 2, 1, 5, 1, 37], + [4, 0, 2, 5, 1, 37], + [0, 3, 2, 2, 2, 37], + [0, 1, 3, 2, 2, 37], + [2, 5, 0, 6, 2, 37], + [2, 3, 1, 6, 2, 37], + [2, 1, 2, 6, 2, 37], + [0, 4, 1, 7, 3, 37], + [0, 2, 2, 7, 3, 37], + [0, 0, 3, 7, 3, 37], + [2, 4, 0, 11, 3, 37], + [2, 2, 1, 11, 3, 37], + [2, 0, 2, 11, 3, 37], + [1, 4, 1, 4, 4, 37], + [1, 2, 2, 4, 4, 37], + [1, 0, 3, 4, 4, 37], + [0, 5, 0, 12, 4, 37], + [0, 3, 1, 12, 4, 37], + [0, 1, 2, 12, 4, 37], + [0, 4, 0, 17, 5, 37], + [0, 2, 1, 17, 5, 37], + [0, 0, 2, 17, 5, 37], + [0, 3, 2, 3, 3, 38], + [0, 1, 3, 3, 3, 38], + [0, 4, 1, 8, 4, 38], + [0, 2, 2, 8, 4, 38], + [0, 0, 3, 8, 4, 38], + [0, 5, 0, 13, 5, 38], + [0, 3, 1, 13, 5, 38], + [0, 1, 2, 13, 5, 38], + [0, 3, 2, 4, 4, 39], + [0, 1, 3, 4, 4, 39], + [0, 4, 1, 9, 5, 39], + [0, 2, 2, 9, 5, 39], + [0, 0, 3, 9, 5, 39], + [0, 0, 4, 0, 0, 40], + [0, 0, 3, 10, 2, 40], + [0, 0, 2, 20, 4, 40], + [0, 0, 4, 1, 1, 41], + [0, 0, 3, 11, 3, 41], + [0, 0, 2, 21, 5, 41], + [0, 0, 4, 2, 2, 42], + [0, 0, 3, 12, 4, 42], + [0, 0, 4, 3, 3, 43], + [0, 0, 3, 13, 5, 43], + [0, 0, 4, 4, 4, 44], + ], + 36: [ + [2, 4, 1, 0, 0, 36], + [2, 2, 2, 0, 0, 36], + [2, 0, 3, 0, 0, 36], + [0, 3, 2, 1, 1, 36], + [0, 1, 3, 1, 1, 36], + [2, 5, 0, 5, 1, 36], + [2, 3, 1, 5, 1, 36], + [2, 1, 2, 5, 1, 36], + [3, 5, 0, 2, 2, 36], + [3, 3, 1, 2, 2, 36], + [3, 1, 2, 2, 2, 36], + [0, 4, 1, 6, 2, 36], + [0, 2, 2, 6, 2, 36], + [0, 0, 3, 6, 2, 36], + [2, 4, 0, 10, 2, 36], + [2, 2, 1, 10, 2, 36], + [2, 0, 2, 10, 2, 36], + [1, 4, 1, 3, 3, 36], + [1, 2, 2, 3, 3, 36], + [1, 0, 3, 3, 3, 36], + [0, 5, 0, 11, 3, 36], + [0, 3, 1, 11, 3, 36], + [0, 1, 2, 11, 3, 36], + [2, 3, 0, 15, 3, 36], + [2, 1, 1, 15, 3, 36], + [1, 5, 0, 8, 4, 36], + [1, 3, 1, 8, 4, 36], + [1, 1, 2, 8, 4, 36], + [0, 4, 0, 16, 4, 36], + [0, 2, 1, 16, 4, 36], + [0, 0, 2, 16, 4, 36], + [0, 3, 0, 21, 5, 36], + [0, 1, 1, 21, 5, 36], + [4, 5, 0, 0, 0, 37], + [4, 3, 1, 0, 0, 37], + [4, 1, 2, 0, 0, 37], + [2, 4, 1, 1, 1, 37], + [2, 2, 2, 1, 1, 37], + [2, 0, 3, 1, 1, 37], + [4, 4, 0, 5, 1, 37], + [4, 2, 1, 5, 1, 37], + [4, 0, 2, 5, 1, 37], + [0, 3, 2, 2, 2, 37], + [0, 1, 3, 2, 2, 37], + [2, 5, 0, 6, 2, 37], + [2, 3, 1, 6, 2, 37], + [2, 1, 2, 6, 2, 37], + [0, 4, 1, 7, 3, 37], + [0, 2, 2, 7, 3, 37], + [0, 0, 3, 7, 3, 37], + [2, 4, 0, 11, 3, 37], + [2, 2, 1, 11, 3, 37], + [2, 0, 2, 11, 3, 37], + [1, 4, 1, 4, 4, 37], + [1, 2, 2, 4, 4, 37], + [1, 0, 3, 4, 4, 37], + [0, 5, 0, 12, 4, 37], + [0, 3, 1, 12, 4, 37], + [0, 1, 2, 12, 4, 37], + [0, 4, 0, 17, 5, 37], + [0, 2, 1, 17, 5, 37], + [0, 0, 2, 17, 5, 37], + [1, 3, 2, 0, 0, 38], + [1, 1, 3, 0, 0, 38], + [4, 5, 0, 1, 1, 38], + [4, 3, 1, 1, 1, 38], + [4, 1, 2, 1, 1, 38], + [1, 4, 1, 5, 1, 38], + [1, 2, 2, 5, 1, 38], + [1, 0, 3, 5, 1, 38], + [2, 4, 1, 2, 2, 38], + [2, 2, 2, 2, 2, 38], + [2, 0, 3, 2, 2, 38], + [1, 5, 0, 10, 2, 38], + [1, 3, 1, 10, 2, 38], + [1, 1, 2, 10, 2, 38], + [0, 3, 2, 3, 3, 38], + [0, 1, 3, 3, 3, 38], + [2, 5, 0, 7, 3, 38], + [2, 3, 1, 7, 3, 38], + [2, 1, 2, 7, 3, 38], + [1, 4, 0, 15, 3, 38], + [1, 2, 1, 15, 3, 38], + [1, 0, 2, 15, 3, 38], + [0, 4, 1, 8, 4, 38], + [0, 2, 2, 8, 4, 38], + [0, 0, 3, 8, 4, 38], + [1, 3, 0, 20, 4, 38], + [1, 1, 1, 20, 4, 38], + [0, 5, 0, 13, 5, 38], + [0, 3, 1, 13, 5, 38], + [0, 1, 2, 13, 5, 38], + [0, 3, 2, 4, 4, 39], + [0, 1, 3, 4, 4, 39], + [0, 4, 1, 9, 5, 39], + [0, 2, 2, 9, 5, 39], + [0, 0, 3, 9, 5, 39], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [0, 3, 2, 5, 1, 40], + [0, 1, 3, 5, 1, 40], + [0, 4, 1, 10, 2, 40], + [0, 2, 2, 10, 2, 40], + [0, 0, 3, 10, 2, 40], + [0, 5, 0, 15, 3, 40], + [0, 3, 1, 15, 3, 40], + [0, 1, 2, 15, 3, 40], + [0, 4, 0, 20, 4, 40], + [0, 2, 1, 20, 4, 40], + [0, 0, 2, 20, 4, 40], + [0, 3, 0, 25, 5, 40], + [0, 1, 1, 25, 5, 40], + [0, 0, 4, 1, 1, 41], + [0, 0, 3, 11, 3, 41], + [0, 0, 2, 21, 5, 41], + [0, 0, 4, 2, 2, 42], + [0, 0, 3, 12, 4, 42], + [0, 0, 4, 3, 3, 43], + [0, 0, 3, 13, 5, 43], + [0, 0, 4, 4, 4, 44], + [0, 0, 4, 5, 1, 45], + [0, 0, 3, 15, 3, 45], + [0, 0, 2, 25, 5, 45], + ], + 37: [ + [4, 5, 0, 0, 0, 37], + [4, 3, 1, 0, 0, 37], + [4, 1, 2, 0, 0, 37], + [2, 4, 1, 1, 1, 37], + [2, 2, 2, 1, 1, 37], + [2, 0, 3, 1, 1, 37], + [4, 4, 0, 5, 1, 37], + [4, 2, 1, 5, 1, 37], + [4, 0, 2, 5, 1, 37], + [0, 3, 2, 2, 2, 37], + [0, 1, 3, 2, 2, 37], + [2, 5, 0, 6, 2, 37], + [2, 3, 1, 6, 2, 37], + [2, 1, 2, 6, 2, 37], + [0, 4, 1, 7, 3, 37], + [0, 2, 2, 7, 3, 37], + [0, 0, 3, 7, 3, 37], + [2, 4, 0, 11, 3, 37], + [2, 2, 1, 11, 3, 37], + [2, 0, 2, 11, 3, 37], + [1, 4, 1, 4, 4, 37], + [1, 2, 2, 4, 4, 37], + [1, 0, 3, 4, 4, 37], + [0, 5, 0, 12, 4, 37], + [0, 3, 1, 12, 4, 37], + [0, 1, 2, 12, 4, 37], + [0, 4, 0, 17, 5, 37], + [0, 2, 1, 17, 5, 37], + [0, 0, 2, 17, 5, 37], + [1, 3, 2, 0, 0, 38], + [1, 1, 3, 0, 0, 38], + [4, 5, 0, 1, 1, 38], + [4, 3, 1, 1, 1, 38], + [4, 1, 2, 1, 1, 38], + [1, 4, 1, 5, 1, 38], + [1, 2, 2, 5, 1, 38], + [1, 0, 3, 5, 1, 38], + [2, 4, 1, 2, 2, 38], + [2, 2, 2, 2, 2, 38], + [2, 0, 3, 2, 2, 38], + [1, 5, 0, 10, 2, 38], + [1, 3, 1, 10, 2, 38], + [1, 1, 2, 10, 2, 38], + [0, 3, 2, 3, 3, 38], + [0, 1, 3, 3, 3, 38], + [2, 5, 0, 7, 3, 38], + [2, 3, 1, 7, 3, 38], + [2, 1, 2, 7, 3, 38], + [1, 4, 0, 15, 3, 38], + [1, 2, 1, 15, 3, 38], + [1, 0, 2, 15, 3, 38], + [0, 4, 1, 8, 4, 38], + [0, 2, 2, 8, 4, 38], + [0, 0, 3, 8, 4, 38], + [1, 3, 0, 20, 4, 38], + [1, 1, 1, 20, 4, 38], + [0, 5, 0, 13, 5, 38], + [0, 3, 1, 13, 5, 38], + [0, 1, 2, 13, 5, 38], + [3, 4, 1, 0, 0, 39], + [3, 2, 2, 0, 0, 39], + [3, 0, 3, 0, 0, 39], + [1, 3, 2, 1, 1, 39], + [1, 1, 3, 1, 1, 39], + [3, 5, 0, 5, 1, 39], + [3, 3, 1, 5, 1, 39], + [3, 1, 2, 5, 1, 39], + [1, 4, 1, 6, 2, 39], + [1, 2, 2, 6, 2, 39], + [1, 0, 3, 6, 2, 39], + [3, 4, 0, 10, 2, 39], + [3, 2, 1, 10, 2, 39], + [3, 0, 2, 10, 2, 39], + [2, 4, 1, 3, 3, 39], + [2, 2, 2, 3, 3, 39], + [2, 0, 3, 3, 3, 39], + [1, 5, 0, 11, 3, 39], + [1, 3, 1, 11, 3, 39], + [1, 1, 2, 11, 3, 39], + [0, 3, 2, 4, 4, 39], + [0, 1, 3, 4, 4, 39], + [1, 4, 0, 16, 4, 39], + [1, 2, 1, 16, 4, 39], + [1, 0, 2, 16, 4, 39], + [0, 4, 1, 9, 5, 39], + [0, 2, 2, 9, 5, 39], + [0, 0, 3, 9, 5, 39], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [0, 3, 2, 5, 1, 40], + [0, 1, 3, 5, 1, 40], + [0, 4, 1, 10, 2, 40], + [0, 2, 2, 10, 2, 40], + [0, 0, 3, 10, 2, 40], + [0, 5, 0, 15, 3, 40], + [0, 3, 1, 15, 3, 40], + [0, 1, 2, 15, 3, 40], + [0, 4, 0, 20, 4, 40], + [0, 2, 1, 20, 4, 40], + [0, 0, 2, 20, 4, 40], + [0, 3, 0, 25, 5, 40], + [0, 1, 1, 25, 5, 40], + [0, 2, 3, 1, 1, 41], + [0, 0, 4, 1, 1, 41], + [0, 3, 2, 6, 2, 41], + [0, 1, 3, 6, 2, 41], + [0, 4, 1, 11, 3, 41], + [0, 2, 2, 11, 3, 41], + [0, 0, 3, 11, 3, 41], + [0, 5, 0, 16, 4, 41], + [0, 3, 1, 16, 4, 41], + [0, 1, 2, 16, 4, 41], + [0, 4, 0, 21, 5, 41], + [0, 2, 1, 21, 5, 41], + [0, 0, 2, 21, 5, 41], + [0, 0, 4, 2, 2, 42], + [0, 0, 3, 12, 4, 42], + [0, 0, 4, 3, 3, 43], + [0, 0, 3, 13, 5, 43], + [0, 0, 4, 4, 4, 44], + [0, 0, 4, 5, 1, 45], + [0, 0, 3, 15, 3, 45], + [0, 0, 2, 25, 5, 45], + [0, 0, 4, 6, 2, 46], + [0, 0, 3, 16, 4, 46], + ], + 38: [ + [1, 3, 2, 0, 0, 38], + [1, 1, 3, 0, 0, 38], + [4, 5, 0, 1, 1, 38], + [4, 3, 1, 1, 1, 38], + [4, 1, 2, 1, 1, 38], + [1, 4, 1, 5, 1, 38], + [1, 2, 2, 5, 1, 38], + [1, 0, 3, 5, 1, 38], + [2, 4, 1, 2, 2, 38], + [2, 2, 2, 2, 2, 38], + [2, 0, 3, 2, 2, 38], + [1, 5, 0, 10, 2, 38], + [1, 3, 1, 10, 2, 38], + [1, 1, 2, 10, 2, 38], + [0, 3, 2, 3, 3, 38], + [0, 1, 3, 3, 3, 38], + [2, 5, 0, 7, 3, 38], + [2, 3, 1, 7, 3, 38], + [2, 1, 2, 7, 3, 38], + [1, 4, 0, 15, 3, 38], + [1, 2, 1, 15, 3, 38], + [1, 0, 2, 15, 3, 38], + [0, 4, 1, 8, 4, 38], + [0, 2, 2, 8, 4, 38], + [0, 0, 3, 8, 4, 38], + [1, 3, 0, 20, 4, 38], + [1, 1, 1, 20, 4, 38], + [0, 5, 0, 13, 5, 38], + [0, 3, 1, 13, 5, 38], + [0, 1, 2, 13, 5, 38], + [3, 4, 1, 0, 0, 39], + [3, 2, 2, 0, 0, 39], + [3, 0, 3, 0, 0, 39], + [1, 3, 2, 1, 1, 39], + [1, 1, 3, 1, 1, 39], + [3, 5, 0, 5, 1, 39], + [3, 3, 1, 5, 1, 39], + [3, 1, 2, 5, 1, 39], + [1, 4, 1, 6, 2, 39], + [1, 2, 2, 6, 2, 39], + [1, 0, 3, 6, 2, 39], + [3, 4, 0, 10, 2, 39], + [3, 2, 1, 10, 2, 39], + [3, 0, 2, 10, 2, 39], + [2, 4, 1, 3, 3, 39], + [2, 2, 2, 3, 3, 39], + [2, 0, 3, 3, 3, 39], + [1, 5, 0, 11, 3, 39], + [1, 3, 1, 11, 3, 39], + [1, 1, 2, 11, 3, 39], + [0, 3, 2, 4, 4, 39], + [0, 1, 3, 4, 4, 39], + [1, 4, 0, 16, 4, 39], + [1, 2, 1, 16, 4, 39], + [1, 0, 2, 16, 4, 39], + [0, 4, 1, 9, 5, 39], + [0, 2, 2, 9, 5, 39], + [0, 0, 3, 9, 5, 39], + [5, 5, 0, 0, 0, 40], + [5, 3, 1, 0, 0, 40], + [5, 1, 2, 0, 0, 40], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [3, 4, 1, 1, 1, 40], + [3, 2, 2, 1, 1, 40], + [3, 0, 3, 1, 1, 40], + [0, 3, 2, 5, 1, 40], + [0, 1, 3, 5, 1, 40], + [1, 3, 2, 2, 2, 40], + [1, 1, 3, 2, 2, 40], + [3, 5, 0, 6, 2, 40], + [3, 3, 1, 6, 2, 40], + [3, 1, 2, 6, 2, 40], + [0, 4, 1, 10, 2, 40], + [0, 2, 2, 10, 2, 40], + [0, 0, 3, 10, 2, 40], + [1, 4, 1, 7, 3, 40], + [1, 2, 2, 7, 3, 40], + [1, 0, 3, 7, 3, 40], + [0, 5, 0, 15, 3, 40], + [0, 3, 1, 15, 3, 40], + [0, 1, 2, 15, 3, 40], + [1, 5, 0, 12, 4, 40], + [1, 3, 1, 12, 4, 40], + [1, 1, 2, 12, 4, 40], + [0, 4, 0, 20, 4, 40], + [0, 2, 1, 20, 4, 40], + [0, 0, 2, 20, 4, 40], + [0, 3, 0, 25, 5, 40], + [0, 1, 1, 25, 5, 40], + [0, 2, 3, 1, 1, 41], + [0, 0, 4, 1, 1, 41], + [0, 3, 2, 6, 2, 41], + [0, 1, 3, 6, 2, 41], + [0, 4, 1, 11, 3, 41], + [0, 2, 2, 11, 3, 41], + [0, 0, 3, 11, 3, 41], + [0, 5, 0, 16, 4, 41], + [0, 3, 1, 16, 4, 41], + [0, 1, 2, 16, 4, 41], + [0, 4, 0, 21, 5, 41], + [0, 2, 1, 21, 5, 41], + [0, 0, 2, 21, 5, 41], + [0, 2, 3, 2, 2, 42], + [0, 0, 4, 2, 2, 42], + [0, 3, 2, 7, 3, 42], + [0, 1, 3, 7, 3, 42], + [0, 4, 1, 12, 4, 42], + [0, 2, 2, 12, 4, 42], + [0, 0, 3, 12, 4, 42], + [0, 5, 0, 17, 5, 42], + [0, 3, 1, 17, 5, 42], + [0, 1, 2, 17, 5, 42], + [0, 0, 4, 3, 3, 43], + [0, 0, 3, 13, 5, 43], + [0, 0, 4, 4, 4, 44], + [0, 0, 4, 5, 1, 45], + [0, 0, 3, 15, 3, 45], + [0, 0, 2, 25, 5, 45], + [0, 0, 4, 6, 2, 46], + [0, 0, 3, 16, 4, 46], + [0, 0, 4, 7, 3, 47], + [0, 0, 3, 17, 5, 47], + ], + 39: [ + [3, 4, 1, 0, 0, 39], + [3, 2, 2, 0, 0, 39], + [3, 0, 3, 0, 0, 39], + [1, 3, 2, 1, 1, 39], + [1, 1, 3, 1, 1, 39], + [3, 5, 0, 5, 1, 39], + [3, 3, 1, 5, 1, 39], + [3, 1, 2, 5, 1, 39], + [1, 4, 1, 6, 2, 39], + [1, 2, 2, 6, 2, 39], + [1, 0, 3, 6, 2, 39], + [3, 4, 0, 10, 2, 39], + [3, 2, 1, 10, 2, 39], + [3, 0, 2, 10, 2, 39], + [2, 4, 1, 3, 3, 39], + [2, 2, 2, 3, 3, 39], + [2, 0, 3, 3, 3, 39], + [1, 5, 0, 11, 3, 39], + [1, 3, 1, 11, 3, 39], + [1, 1, 2, 11, 3, 39], + [0, 3, 2, 4, 4, 39], + [0, 1, 3, 4, 4, 39], + [1, 4, 0, 16, 4, 39], + [1, 2, 1, 16, 4, 39], + [1, 0, 2, 16, 4, 39], + [0, 4, 1, 9, 5, 39], + [0, 2, 2, 9, 5, 39], + [0, 0, 3, 9, 5, 39], + [5, 5, 0, 0, 0, 40], + [5, 3, 1, 0, 0, 40], + [5, 1, 2, 0, 0, 40], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [3, 4, 1, 1, 1, 40], + [3, 2, 2, 1, 1, 40], + [3, 0, 3, 1, 1, 40], + [0, 3, 2, 5, 1, 40], + [0, 1, 3, 5, 1, 40], + [1, 3, 2, 2, 2, 40], + [1, 1, 3, 2, 2, 40], + [3, 5, 0, 6, 2, 40], + [3, 3, 1, 6, 2, 40], + [3, 1, 2, 6, 2, 40], + [0, 4, 1, 10, 2, 40], + [0, 2, 2, 10, 2, 40], + [0, 0, 3, 10, 2, 40], + [1, 4, 1, 7, 3, 40], + [1, 2, 2, 7, 3, 40], + [1, 0, 3, 7, 3, 40], + [0, 5, 0, 15, 3, 40], + [0, 3, 1, 15, 3, 40], + [0, 1, 2, 15, 3, 40], + [1, 5, 0, 12, 4, 40], + [1, 3, 1, 12, 4, 40], + [1, 1, 2, 12, 4, 40], + [0, 4, 0, 20, 4, 40], + [0, 2, 1, 20, 4, 40], + [0, 0, 2, 20, 4, 40], + [0, 3, 0, 25, 5, 40], + [0, 1, 1, 25, 5, 40], + [2, 3, 2, 0, 0, 41], + [2, 1, 3, 0, 0, 41], + [0, 2, 3, 1, 1, 41], + [0, 0, 4, 1, 1, 41], + [2, 4, 1, 5, 1, 41], + [2, 2, 2, 5, 1, 41], + [2, 0, 3, 5, 1, 41], + [3, 4, 1, 2, 2, 41], + [3, 2, 2, 2, 2, 41], + [3, 0, 3, 2, 2, 41], + [0, 3, 2, 6, 2, 41], + [0, 1, 3, 6, 2, 41], + [2, 5, 0, 10, 2, 41], + [2, 3, 1, 10, 2, 41], + [2, 1, 2, 10, 2, 41], + [1, 3, 2, 3, 3, 41], + [1, 1, 3, 3, 3, 41], + [0, 4, 1, 11, 3, 41], + [0, 2, 2, 11, 3, 41], + [0, 0, 3, 11, 3, 41], + [2, 4, 0, 15, 3, 41], + [2, 2, 1, 15, 3, 41], + [2, 0, 2, 15, 3, 41], + [1, 4, 1, 8, 4, 41], + [1, 2, 2, 8, 4, 41], + [1, 0, 3, 8, 4, 41], + [0, 5, 0, 16, 4, 41], + [0, 3, 1, 16, 4, 41], + [0, 1, 2, 16, 4, 41], + [0, 4, 0, 21, 5, 41], + [0, 2, 1, 21, 5, 41], + [0, 0, 2, 21, 5, 41], + [0, 2, 3, 2, 2, 42], + [0, 0, 4, 2, 2, 42], + [0, 3, 2, 7, 3, 42], + [0, 1, 3, 7, 3, 42], + [0, 4, 1, 12, 4, 42], + [0, 2, 2, 12, 4, 42], + [0, 0, 3, 12, 4, 42], + [0, 5, 0, 17, 5, 42], + [0, 3, 1, 17, 5, 42], + [0, 1, 2, 17, 5, 42], + [0, 2, 3, 3, 3, 43], + [0, 0, 4, 3, 3, 43], + [0, 3, 2, 8, 4, 43], + [0, 1, 3, 8, 4, 43], + [0, 4, 1, 13, 5, 43], + [0, 2, 2, 13, 5, 43], + [0, 0, 3, 13, 5, 43], + [0, 0, 4, 4, 4, 44], + [0, 0, 4, 5, 1, 45], + [0, 0, 3, 15, 3, 45], + [0, 0, 2, 25, 5, 45], + [0, 0, 4, 6, 2, 46], + [0, 0, 3, 16, 4, 46], + [0, 0, 4, 7, 3, 47], + [0, 0, 3, 17, 5, 47], + [0, 0, 4, 8, 4, 48], + ], + 40: [ + [5, 5, 0, 0, 0, 40], + [5, 3, 1, 0, 0, 40], + [5, 1, 2, 0, 0, 40], + [0, 2, 3, 0, 0, 40], + [0, 0, 4, 0, 0, 40], + [3, 4, 1, 1, 1, 40], + [3, 2, 2, 1, 1, 40], + [3, 0, 3, 1, 1, 40], + [0, 3, 2, 5, 1, 40], + [0, 1, 3, 5, 1, 40], + [1, 3, 2, 2, 2, 40], + [1, 1, 3, 2, 2, 40], + [3, 5, 0, 6, 2, 40], + [3, 3, 1, 6, 2, 40], + [3, 1, 2, 6, 2, 40], + [0, 4, 1, 10, 2, 40], + [0, 2, 2, 10, 2, 40], + [0, 0, 3, 10, 2, 40], + [1, 4, 1, 7, 3, 40], + [1, 2, 2, 7, 3, 40], + [1, 0, 3, 7, 3, 40], + [0, 5, 0, 15, 3, 40], + [0, 3, 1, 15, 3, 40], + [0, 1, 2, 15, 3, 40], + [1, 5, 0, 12, 4, 40], + [1, 3, 1, 12, 4, 40], + [1, 1, 2, 12, 4, 40], + [0, 4, 0, 20, 4, 40], + [0, 2, 1, 20, 4, 40], + [0, 0, 2, 20, 4, 40], + [0, 3, 0, 25, 5, 40], + [0, 1, 1, 25, 5, 40], + [2, 3, 2, 0, 0, 41], + [2, 1, 3, 0, 0, 41], + [0, 2, 3, 1, 1, 41], + [0, 0, 4, 1, 1, 41], + [2, 4, 1, 5, 1, 41], + [2, 2, 2, 5, 1, 41], + [2, 0, 3, 5, 1, 41], + [3, 4, 1, 2, 2, 41], + [3, 2, 2, 2, 2, 41], + [3, 0, 3, 2, 2, 41], + [0, 3, 2, 6, 2, 41], + [0, 1, 3, 6, 2, 41], + [2, 5, 0, 10, 2, 41], + [2, 3, 1, 10, 2, 41], + [2, 1, 2, 10, 2, 41], + [1, 3, 2, 3, 3, 41], + [1, 1, 3, 3, 3, 41], + [0, 4, 1, 11, 3, 41], + [0, 2, 2, 11, 3, 41], + [0, 0, 3, 11, 3, 41], + [2, 4, 0, 15, 3, 41], + [2, 2, 1, 15, 3, 41], + [2, 0, 2, 15, 3, 41], + [1, 4, 1, 8, 4, 41], + [1, 2, 2, 8, 4, 41], + [1, 0, 3, 8, 4, 41], + [0, 5, 0, 16, 4, 41], + [0, 3, 1, 16, 4, 41], + [0, 1, 2, 16, 4, 41], + [0, 4, 0, 21, 5, 41], + [0, 2, 1, 21, 5, 41], + [0, 0, 2, 21, 5, 41], + [4, 4, 1, 0, 0, 42], + [4, 2, 2, 0, 0, 42], + [4, 0, 3, 0, 0, 42], + [2, 3, 2, 1, 1, 42], + [2, 1, 3, 1, 1, 42], + [4, 5, 0, 5, 1, 42], + [4, 3, 1, 5, 1, 42], + [4, 1, 2, 5, 1, 42], + [0, 2, 3, 2, 2, 42], + [0, 0, 4, 2, 2, 42], + [2, 4, 1, 6, 2, 42], + [2, 2, 2, 6, 2, 42], + [2, 0, 3, 6, 2, 42], + [0, 3, 2, 7, 3, 42], + [0, 1, 3, 7, 3, 42], + [2, 5, 0, 11, 3, 42], + [2, 3, 1, 11, 3, 42], + [2, 1, 2, 11, 3, 42], + [1, 3, 2, 4, 4, 42], + [1, 1, 3, 4, 4, 42], + [0, 4, 1, 12, 4, 42], + [0, 2, 2, 12, 4, 42], + [0, 0, 3, 12, 4, 42], + [0, 5, 0, 17, 5, 42], + [0, 3, 1, 17, 5, 42], + [0, 1, 2, 17, 5, 42], + [0, 2, 3, 3, 3, 43], + [0, 0, 4, 3, 3, 43], + [0, 3, 2, 8, 4, 43], + [0, 1, 3, 8, 4, 43], + [0, 4, 1, 13, 5, 43], + [0, 2, 2, 13, 5, 43], + [0, 0, 3, 13, 5, 43], + [0, 2, 3, 4, 4, 44], + [0, 0, 4, 4, 4, 44], + [0, 3, 2, 9, 5, 44], + [0, 1, 3, 9, 5, 44], + [0, 0, 4, 5, 1, 45], + [0, 0, 3, 15, 3, 45], + [0, 0, 2, 25, 5, 45], + [0, 0, 4, 6, 2, 46], + [0, 0, 3, 16, 4, 46], + [0, 0, 4, 7, 3, 47], + [0, 0, 3, 17, 5, 47], + [0, 0, 4, 8, 4, 48], + [0, 0, 4, 9, 5, 49], + ], + 41: [ + [2, 3, 2, 0, 0, 41], + [2, 1, 3, 0, 0, 41], + [0, 2, 3, 1, 1, 41], + [0, 0, 4, 1, 1, 41], + [2, 4, 1, 5, 1, 41], + [2, 2, 2, 5, 1, 41], + [2, 0, 3, 5, 1, 41], + [3, 4, 1, 2, 2, 41], + [3, 2, 2, 2, 2, 41], + [3, 0, 3, 2, 2, 41], + [0, 3, 2, 6, 2, 41], + [0, 1, 3, 6, 2, 41], + [2, 5, 0, 10, 2, 41], + [2, 3, 1, 10, 2, 41], + [2, 1, 2, 10, 2, 41], + [1, 3, 2, 3, 3, 41], + [1, 1, 3, 3, 3, 41], + [0, 4, 1, 11, 3, 41], + [0, 2, 2, 11, 3, 41], + [0, 0, 3, 11, 3, 41], + [2, 4, 0, 15, 3, 41], + [2, 2, 1, 15, 3, 41], + [2, 0, 2, 15, 3, 41], + [1, 4, 1, 8, 4, 41], + [1, 2, 2, 8, 4, 41], + [1, 0, 3, 8, 4, 41], + [0, 5, 0, 16, 4, 41], + [0, 3, 1, 16, 4, 41], + [0, 1, 2, 16, 4, 41], + [0, 4, 0, 21, 5, 41], + [0, 2, 1, 21, 5, 41], + [0, 0, 2, 21, 5, 41], + [4, 4, 1, 0, 0, 42], + [4, 2, 2, 0, 0, 42], + [4, 0, 3, 0, 0, 42], + [2, 3, 2, 1, 1, 42], + [2, 1, 3, 1, 1, 42], + [4, 5, 0, 5, 1, 42], + [4, 3, 1, 5, 1, 42], + [4, 1, 2, 5, 1, 42], + [0, 2, 3, 2, 2, 42], + [0, 0, 4, 2, 2, 42], + [2, 4, 1, 6, 2, 42], + [2, 2, 2, 6, 2, 42], + [2, 0, 3, 6, 2, 42], + [0, 3, 2, 7, 3, 42], + [0, 1, 3, 7, 3, 42], + [2, 5, 0, 11, 3, 42], + [2, 3, 1, 11, 3, 42], + [2, 1, 2, 11, 3, 42], + [1, 3, 2, 4, 4, 42], + [1, 1, 3, 4, 4, 42], + [0, 4, 1, 12, 4, 42], + [0, 2, 2, 12, 4, 42], + [0, 0, 3, 12, 4, 42], + [0, 5, 0, 17, 5, 42], + [0, 3, 1, 17, 5, 42], + [0, 1, 2, 17, 5, 42], + [1, 2, 3, 0, 0, 43], + [1, 0, 4, 0, 0, 43], + [4, 4, 1, 1, 1, 43], + [4, 2, 2, 1, 1, 43], + [4, 0, 3, 1, 1, 43], + [1, 3, 2, 5, 1, 43], + [1, 1, 3, 5, 1, 43], + [2, 3, 2, 2, 2, 43], + [2, 1, 3, 2, 2, 43], + [1, 4, 1, 10, 2, 43], + [1, 2, 2, 10, 2, 43], + [1, 0, 3, 10, 2, 43], + [0, 2, 3, 3, 3, 43], + [0, 0, 4, 3, 3, 43], + [2, 4, 1, 7, 3, 43], + [2, 2, 2, 7, 3, 43], + [2, 0, 3, 7, 3, 43], + [1, 5, 0, 15, 3, 43], + [1, 3, 1, 15, 3, 43], + [1, 1, 2, 15, 3, 43], + [0, 3, 2, 8, 4, 43], + [0, 1, 3, 8, 4, 43], + [1, 4, 0, 20, 4, 43], + [1, 2, 1, 20, 4, 43], + [1, 0, 2, 20, 4, 43], + [0, 4, 1, 13, 5, 43], + [0, 2, 2, 13, 5, 43], + [0, 0, 3, 13, 5, 43], + [0, 2, 3, 4, 4, 44], + [0, 0, 4, 4, 4, 44], + [0, 3, 2, 9, 5, 44], + [0, 1, 3, 9, 5, 44], + [0, 1, 4, 0, 0, 45], + [0, 2, 3, 5, 1, 45], + [0, 0, 4, 5, 1, 45], + [0, 3, 2, 10, 2, 45], + [0, 1, 3, 10, 2, 45], + [0, 4, 1, 15, 3, 45], + [0, 2, 2, 15, 3, 45], + [0, 0, 3, 15, 3, 45], + [0, 5, 0, 20, 4, 45], + [0, 3, 1, 20, 4, 45], + [0, 1, 2, 20, 4, 45], + [0, 4, 0, 25, 5, 45], + [0, 2, 1, 25, 5, 45], + [0, 0, 2, 25, 5, 45], + [0, 0, 4, 6, 2, 46], + [0, 0, 3, 16, 4, 46], + [0, 0, 4, 7, 3, 47], + [0, 0, 3, 17, 5, 47], + [0, 0, 4, 8, 4, 48], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 0, 4, 10, 2, 50], + [0, 0, 3, 20, 4, 50], + ], + 42: [ + [4, 4, 1, 0, 0, 42], + [4, 2, 2, 0, 0, 42], + [4, 0, 3, 0, 0, 42], + [2, 3, 2, 1, 1, 42], + [2, 1, 3, 1, 1, 42], + [4, 5, 0, 5, 1, 42], + [4, 3, 1, 5, 1, 42], + [4, 1, 2, 5, 1, 42], + [0, 2, 3, 2, 2, 42], + [0, 0, 4, 2, 2, 42], + [2, 4, 1, 6, 2, 42], + [2, 2, 2, 6, 2, 42], + [2, 0, 3, 6, 2, 42], + [0, 3, 2, 7, 3, 42], + [0, 1, 3, 7, 3, 42], + [2, 5, 0, 11, 3, 42], + [2, 3, 1, 11, 3, 42], + [2, 1, 2, 11, 3, 42], + [1, 3, 2, 4, 4, 42], + [1, 1, 3, 4, 4, 42], + [0, 4, 1, 12, 4, 42], + [0, 2, 2, 12, 4, 42], + [0, 0, 3, 12, 4, 42], + [0, 5, 0, 17, 5, 42], + [0, 3, 1, 17, 5, 42], + [0, 1, 2, 17, 5, 42], + [1, 2, 3, 0, 0, 43], + [1, 0, 4, 0, 0, 43], + [4, 4, 1, 1, 1, 43], + [4, 2, 2, 1, 1, 43], + [4, 0, 3, 1, 1, 43], + [1, 3, 2, 5, 1, 43], + [1, 1, 3, 5, 1, 43], + [2, 3, 2, 2, 2, 43], + [2, 1, 3, 2, 2, 43], + [1, 4, 1, 10, 2, 43], + [1, 2, 2, 10, 2, 43], + [1, 0, 3, 10, 2, 43], + [0, 2, 3, 3, 3, 43], + [0, 0, 4, 3, 3, 43], + [2, 4, 1, 7, 3, 43], + [2, 2, 2, 7, 3, 43], + [2, 0, 3, 7, 3, 43], + [1, 5, 0, 15, 3, 43], + [1, 3, 1, 15, 3, 43], + [1, 1, 2, 15, 3, 43], + [0, 3, 2, 8, 4, 43], + [0, 1, 3, 8, 4, 43], + [1, 4, 0, 20, 4, 43], + [1, 2, 1, 20, 4, 43], + [1, 0, 2, 20, 4, 43], + [0, 4, 1, 13, 5, 43], + [0, 2, 2, 13, 5, 43], + [0, 0, 3, 13, 5, 43], + [3, 3, 2, 0, 0, 44], + [3, 1, 3, 0, 0, 44], + [1, 2, 3, 1, 1, 44], + [1, 0, 4, 1, 1, 44], + [3, 4, 1, 5, 1, 44], + [3, 2, 2, 5, 1, 44], + [3, 0, 3, 5, 1, 44], + [1, 3, 2, 6, 2, 44], + [1, 1, 3, 6, 2, 44], + [3, 5, 0, 10, 2, 44], + [3, 3, 1, 10, 2, 44], + [3, 1, 2, 10, 2, 44], + [2, 3, 2, 3, 3, 44], + [2, 1, 3, 3, 3, 44], + [1, 4, 1, 11, 3, 44], + [1, 2, 2, 11, 3, 44], + [1, 0, 3, 11, 3, 44], + [0, 2, 3, 4, 4, 44], + [0, 0, 4, 4, 4, 44], + [1, 5, 0, 16, 4, 44], + [1, 3, 1, 16, 4, 44], + [1, 1, 2, 16, 4, 44], + [0, 3, 2, 9, 5, 44], + [0, 1, 3, 9, 5, 44], + [0, 1, 4, 0, 0, 45], + [0, 2, 3, 5, 1, 45], + [0, 0, 4, 5, 1, 45], + [0, 3, 2, 10, 2, 45], + [0, 1, 3, 10, 2, 45], + [0, 4, 1, 15, 3, 45], + [0, 2, 2, 15, 3, 45], + [0, 0, 3, 15, 3, 45], + [0, 5, 0, 20, 4, 45], + [0, 3, 1, 20, 4, 45], + [0, 1, 2, 20, 4, 45], + [0, 4, 0, 25, 5, 45], + [0, 2, 1, 25, 5, 45], + [0, 0, 2, 25, 5, 45], + [0, 1, 4, 1, 1, 46], + [0, 2, 3, 6, 2, 46], + [0, 0, 4, 6, 2, 46], + [0, 3, 2, 11, 3, 46], + [0, 1, 3, 11, 3, 46], + [0, 4, 1, 16, 4, 46], + [0, 2, 2, 16, 4, 46], + [0, 0, 3, 16, 4, 46], + [0, 5, 0, 21, 5, 46], + [0, 3, 1, 21, 5, 46], + [0, 1, 2, 21, 5, 46], + [0, 0, 4, 7, 3, 47], + [0, 0, 3, 17, 5, 47], + [0, 0, 4, 8, 4, 48], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 0, 4, 10, 2, 50], + [0, 0, 3, 20, 4, 50], + [0, 0, 5, 1, 1, 51], + [0, 0, 4, 11, 3, 51], + [0, 0, 3, 21, 5, 51], + ], + 43: [ + [1, 2, 3, 0, 0, 43], + [1, 0, 4, 0, 0, 43], + [4, 4, 1, 1, 1, 43], + [4, 2, 2, 1, 1, 43], + [4, 0, 3, 1, 1, 43], + [1, 3, 2, 5, 1, 43], + [1, 1, 3, 5, 1, 43], + [2, 3, 2, 2, 2, 43], + [2, 1, 3, 2, 2, 43], + [1, 4, 1, 10, 2, 43], + [1, 2, 2, 10, 2, 43], + [1, 0, 3, 10, 2, 43], + [0, 2, 3, 3, 3, 43], + [0, 0, 4, 3, 3, 43], + [2, 4, 1, 7, 3, 43], + [2, 2, 2, 7, 3, 43], + [2, 0, 3, 7, 3, 43], + [1, 5, 0, 15, 3, 43], + [1, 3, 1, 15, 3, 43], + [1, 1, 2, 15, 3, 43], + [0, 3, 2, 8, 4, 43], + [0, 1, 3, 8, 4, 43], + [1, 4, 0, 20, 4, 43], + [1, 2, 1, 20, 4, 43], + [1, 0, 2, 20, 4, 43], + [0, 4, 1, 13, 5, 43], + [0, 2, 2, 13, 5, 43], + [0, 0, 3, 13, 5, 43], + [3, 3, 2, 0, 0, 44], + [3, 1, 3, 0, 0, 44], + [1, 2, 3, 1, 1, 44], + [1, 0, 4, 1, 1, 44], + [3, 4, 1, 5, 1, 44], + [3, 2, 2, 5, 1, 44], + [3, 0, 3, 5, 1, 44], + [1, 3, 2, 6, 2, 44], + [1, 1, 3, 6, 2, 44], + [3, 5, 0, 10, 2, 44], + [3, 3, 1, 10, 2, 44], + [3, 1, 2, 10, 2, 44], + [2, 3, 2, 3, 3, 44], + [2, 1, 3, 3, 3, 44], + [1, 4, 1, 11, 3, 44], + [1, 2, 2, 11, 3, 44], + [1, 0, 3, 11, 3, 44], + [0, 2, 3, 4, 4, 44], + [0, 0, 4, 4, 4, 44], + [1, 5, 0, 16, 4, 44], + [1, 3, 1, 16, 4, 44], + [1, 1, 2, 16, 4, 44], + [0, 3, 2, 9, 5, 44], + [0, 1, 3, 9, 5, 44], + [5, 4, 1, 0, 0, 45], + [5, 2, 2, 0, 0, 45], + [5, 0, 3, 0, 0, 45], + [0, 1, 4, 0, 0, 45], + [3, 3, 2, 1, 1, 45], + [3, 1, 3, 1, 1, 45], + [0, 2, 3, 5, 1, 45], + [0, 0, 4, 5, 1, 45], + [1, 2, 3, 2, 2, 45], + [1, 0, 4, 2, 2, 45], + [3, 4, 1, 6, 2, 45], + [3, 2, 2, 6, 2, 45], + [3, 0, 3, 6, 2, 45], + [0, 3, 2, 10, 2, 45], + [0, 1, 3, 10, 2, 45], + [1, 3, 2, 7, 3, 45], + [1, 1, 3, 7, 3, 45], + [0, 4, 1, 15, 3, 45], + [0, 2, 2, 15, 3, 45], + [0, 0, 3, 15, 3, 45], + [1, 4, 1, 12, 4, 45], + [1, 2, 2, 12, 4, 45], + [1, 0, 3, 12, 4, 45], + [0, 5, 0, 20, 4, 45], + [0, 3, 1, 20, 4, 45], + [0, 1, 2, 20, 4, 45], + [0, 4, 0, 25, 5, 45], + [0, 2, 1, 25, 5, 45], + [0, 0, 2, 25, 5, 45], + [0, 1, 4, 1, 1, 46], + [0, 2, 3, 6, 2, 46], + [0, 0, 4, 6, 2, 46], + [0, 3, 2, 11, 3, 46], + [0, 1, 3, 11, 3, 46], + [0, 4, 1, 16, 4, 46], + [0, 2, 2, 16, 4, 46], + [0, 0, 3, 16, 4, 46], + [0, 5, 0, 21, 5, 46], + [0, 3, 1, 21, 5, 46], + [0, 1, 2, 21, 5, 46], + [0, 1, 4, 2, 2, 47], + [0, 2, 3, 7, 3, 47], + [0, 0, 4, 7, 3, 47], + [0, 3, 2, 12, 4, 47], + [0, 1, 3, 12, 4, 47], + [0, 4, 1, 17, 5, 47], + [0, 2, 2, 17, 5, 47], + [0, 0, 3, 17, 5, 47], + [0, 0, 4, 8, 4, 48], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 0, 4, 10, 2, 50], + [0, 0, 3, 20, 4, 50], + [0, 0, 5, 1, 1, 51], + [0, 0, 4, 11, 3, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 0, 4, 12, 4, 52], + ], + 44: [ + [3, 3, 2, 0, 0, 44], + [3, 1, 3, 0, 0, 44], + [1, 2, 3, 1, 1, 44], + [1, 0, 4, 1, 1, 44], + [3, 4, 1, 5, 1, 44], + [3, 2, 2, 5, 1, 44], + [3, 0, 3, 5, 1, 44], + [1, 3, 2, 6, 2, 44], + [1, 1, 3, 6, 2, 44], + [3, 5, 0, 10, 2, 44], + [3, 3, 1, 10, 2, 44], + [3, 1, 2, 10, 2, 44], + [2, 3, 2, 3, 3, 44], + [2, 1, 3, 3, 3, 44], + [1, 4, 1, 11, 3, 44], + [1, 2, 2, 11, 3, 44], + [1, 0, 3, 11, 3, 44], + [0, 2, 3, 4, 4, 44], + [0, 0, 4, 4, 4, 44], + [1, 5, 0, 16, 4, 44], + [1, 3, 1, 16, 4, 44], + [1, 1, 2, 16, 4, 44], + [0, 3, 2, 9, 5, 44], + [0, 1, 3, 9, 5, 44], + [5, 4, 1, 0, 0, 45], + [5, 2, 2, 0, 0, 45], + [5, 0, 3, 0, 0, 45], + [0, 1, 4, 0, 0, 45], + [3, 3, 2, 1, 1, 45], + [3, 1, 3, 1, 1, 45], + [0, 2, 3, 5, 1, 45], + [0, 0, 4, 5, 1, 45], + [1, 2, 3, 2, 2, 45], + [1, 0, 4, 2, 2, 45], + [3, 4, 1, 6, 2, 45], + [3, 2, 2, 6, 2, 45], + [3, 0, 3, 6, 2, 45], + [0, 3, 2, 10, 2, 45], + [0, 1, 3, 10, 2, 45], + [1, 3, 2, 7, 3, 45], + [1, 1, 3, 7, 3, 45], + [0, 4, 1, 15, 3, 45], + [0, 2, 2, 15, 3, 45], + [0, 0, 3, 15, 3, 45], + [1, 4, 1, 12, 4, 45], + [1, 2, 2, 12, 4, 45], + [1, 0, 3, 12, 4, 45], + [0, 5, 0, 20, 4, 45], + [0, 3, 1, 20, 4, 45], + [0, 1, 2, 20, 4, 45], + [0, 4, 0, 25, 5, 45], + [0, 2, 1, 25, 5, 45], + [0, 0, 2, 25, 5, 45], + [2, 2, 3, 0, 0, 46], + [2, 0, 4, 0, 0, 46], + [0, 1, 4, 1, 1, 46], + [2, 3, 2, 5, 1, 46], + [2, 1, 3, 5, 1, 46], + [3, 3, 2, 2, 2, 46], + [3, 1, 3, 2, 2, 46], + [0, 2, 3, 6, 2, 46], + [0, 0, 4, 6, 2, 46], + [2, 4, 1, 10, 2, 46], + [2, 2, 2, 10, 2, 46], + [2, 0, 3, 10, 2, 46], + [1, 2, 3, 3, 3, 46], + [1, 0, 4, 3, 3, 46], + [0, 3, 2, 11, 3, 46], + [0, 1, 3, 11, 3, 46], + [2, 5, 0, 15, 3, 46], + [2, 3, 1, 15, 3, 46], + [2, 1, 2, 15, 3, 46], + [1, 3, 2, 8, 4, 46], + [1, 1, 3, 8, 4, 46], + [0, 4, 1, 16, 4, 46], + [0, 2, 2, 16, 4, 46], + [0, 0, 3, 16, 4, 46], + [0, 5, 0, 21, 5, 46], + [0, 3, 1, 21, 5, 46], + [0, 1, 2, 21, 5, 46], + [0, 1, 4, 2, 2, 47], + [0, 2, 3, 7, 3, 47], + [0, 0, 4, 7, 3, 47], + [0, 3, 2, 12, 4, 47], + [0, 1, 3, 12, 4, 47], + [0, 4, 1, 17, 5, 47], + [0, 2, 2, 17, 5, 47], + [0, 0, 3, 17, 5, 47], + [0, 1, 4, 3, 3, 48], + [0, 2, 3, 8, 4, 48], + [0, 0, 4, 8, 4, 48], + [0, 3, 2, 13, 5, 48], + [0, 1, 3, 13, 5, 48], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 0, 4, 10, 2, 50], + [0, 0, 3, 20, 4, 50], + [0, 0, 5, 1, 1, 51], + [0, 0, 4, 11, 3, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 0, 4, 12, 4, 52], + [0, 0, 5, 3, 3, 53], + [0, 0, 4, 13, 5, 53], + ], + 45: [ + [5, 4, 1, 0, 0, 45], + [5, 2, 2, 0, 0, 45], + [5, 0, 3, 0, 0, 45], + [0, 1, 4, 0, 0, 45], + [3, 3, 2, 1, 1, 45], + [3, 1, 3, 1, 1, 45], + [0, 2, 3, 5, 1, 45], + [0, 0, 4, 5, 1, 45], + [1, 2, 3, 2, 2, 45], + [1, 0, 4, 2, 2, 45], + [3, 4, 1, 6, 2, 45], + [3, 2, 2, 6, 2, 45], + [3, 0, 3, 6, 2, 45], + [0, 3, 2, 10, 2, 45], + [0, 1, 3, 10, 2, 45], + [1, 3, 2, 7, 3, 45], + [1, 1, 3, 7, 3, 45], + [0, 4, 1, 15, 3, 45], + [0, 2, 2, 15, 3, 45], + [0, 0, 3, 15, 3, 45], + [1, 4, 1, 12, 4, 45], + [1, 2, 2, 12, 4, 45], + [1, 0, 3, 12, 4, 45], + [0, 5, 0, 20, 4, 45], + [0, 3, 1, 20, 4, 45], + [0, 1, 2, 20, 4, 45], + [0, 4, 0, 25, 5, 45], + [0, 2, 1, 25, 5, 45], + [0, 0, 2, 25, 5, 45], + [2, 2, 3, 0, 0, 46], + [2, 0, 4, 0, 0, 46], + [0, 1, 4, 1, 1, 46], + [2, 3, 2, 5, 1, 46], + [2, 1, 3, 5, 1, 46], + [3, 3, 2, 2, 2, 46], + [3, 1, 3, 2, 2, 46], + [0, 2, 3, 6, 2, 46], + [0, 0, 4, 6, 2, 46], + [2, 4, 1, 10, 2, 46], + [2, 2, 2, 10, 2, 46], + [2, 0, 3, 10, 2, 46], + [1, 2, 3, 3, 3, 46], + [1, 0, 4, 3, 3, 46], + [0, 3, 2, 11, 3, 46], + [0, 1, 3, 11, 3, 46], + [2, 5, 0, 15, 3, 46], + [2, 3, 1, 15, 3, 46], + [2, 1, 2, 15, 3, 46], + [1, 3, 2, 8, 4, 46], + [1, 1, 3, 8, 4, 46], + [0, 4, 1, 16, 4, 46], + [0, 2, 2, 16, 4, 46], + [0, 0, 3, 16, 4, 46], + [0, 5, 0, 21, 5, 46], + [0, 3, 1, 21, 5, 46], + [0, 1, 2, 21, 5, 46], + [4, 3, 2, 0, 0, 47], + [4, 1, 3, 0, 0, 47], + [2, 2, 3, 1, 1, 47], + [2, 0, 4, 1, 1, 47], + [4, 4, 1, 5, 1, 47], + [4, 2, 2, 5, 1, 47], + [4, 0, 3, 5, 1, 47], + [0, 1, 4, 2, 2, 47], + [2, 3, 2, 6, 2, 47], + [2, 1, 3, 6, 2, 47], + [0, 2, 3, 7, 3, 47], + [0, 0, 4, 7, 3, 47], + [2, 4, 1, 11, 3, 47], + [2, 2, 2, 11, 3, 47], + [2, 0, 3, 11, 3, 47], + [1, 2, 3, 4, 4, 47], + [1, 0, 4, 4, 4, 47], + [0, 3, 2, 12, 4, 47], + [0, 1, 3, 12, 4, 47], + [0, 4, 1, 17, 5, 47], + [0, 2, 2, 17, 5, 47], + [0, 0, 3, 17, 5, 47], + [0, 1, 4, 3, 3, 48], + [0, 2, 3, 8, 4, 48], + [0, 0, 4, 8, 4, 48], + [0, 3, 2, 13, 5, 48], + [0, 1, 3, 13, 5, 48], + [0, 1, 4, 4, 4, 49], + [0, 2, 3, 9, 5, 49], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 0, 4, 10, 2, 50], + [0, 0, 3, 20, 4, 50], + [0, 0, 5, 1, 1, 51], + [0, 0, 4, 11, 3, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 0, 4, 12, 4, 52], + [0, 0, 5, 3, 3, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + ], + 46: [ + [2, 2, 3, 0, 0, 46], + [2, 0, 4, 0, 0, 46], + [0, 1, 4, 1, 1, 46], + [2, 3, 2, 5, 1, 46], + [2, 1, 3, 5, 1, 46], + [3, 3, 2, 2, 2, 46], + [3, 1, 3, 2, 2, 46], + [0, 2, 3, 6, 2, 46], + [0, 0, 4, 6, 2, 46], + [2, 4, 1, 10, 2, 46], + [2, 2, 2, 10, 2, 46], + [2, 0, 3, 10, 2, 46], + [1, 2, 3, 3, 3, 46], + [1, 0, 4, 3, 3, 46], + [0, 3, 2, 11, 3, 46], + [0, 1, 3, 11, 3, 46], + [2, 5, 0, 15, 3, 46], + [2, 3, 1, 15, 3, 46], + [2, 1, 2, 15, 3, 46], + [1, 3, 2, 8, 4, 46], + [1, 1, 3, 8, 4, 46], + [0, 4, 1, 16, 4, 46], + [0, 2, 2, 16, 4, 46], + [0, 0, 3, 16, 4, 46], + [0, 5, 0, 21, 5, 46], + [0, 3, 1, 21, 5, 46], + [0, 1, 2, 21, 5, 46], + [4, 3, 2, 0, 0, 47], + [4, 1, 3, 0, 0, 47], + [2, 2, 3, 1, 1, 47], + [2, 0, 4, 1, 1, 47], + [4, 4, 1, 5, 1, 47], + [4, 2, 2, 5, 1, 47], + [4, 0, 3, 5, 1, 47], + [0, 1, 4, 2, 2, 47], + [2, 3, 2, 6, 2, 47], + [2, 1, 3, 6, 2, 47], + [0, 2, 3, 7, 3, 47], + [0, 0, 4, 7, 3, 47], + [2, 4, 1, 11, 3, 47], + [2, 2, 2, 11, 3, 47], + [2, 0, 3, 11, 3, 47], + [1, 2, 3, 4, 4, 47], + [1, 0, 4, 4, 4, 47], + [0, 3, 2, 12, 4, 47], + [0, 1, 3, 12, 4, 47], + [0, 4, 1, 17, 5, 47], + [0, 2, 2, 17, 5, 47], + [0, 0, 3, 17, 5, 47], + [1, 1, 4, 0, 0, 48], + [4, 3, 2, 1, 1, 48], + [4, 1, 3, 1, 1, 48], + [1, 2, 3, 5, 1, 48], + [1, 0, 4, 5, 1, 48], + [2, 2, 3, 2, 2, 48], + [2, 0, 4, 2, 2, 48], + [1, 3, 2, 10, 2, 48], + [1, 1, 3, 10, 2, 48], + [0, 1, 4, 3, 3, 48], + [2, 3, 2, 7, 3, 48], + [2, 1, 3, 7, 3, 48], + [1, 4, 1, 15, 3, 48], + [1, 2, 2, 15, 3, 48], + [1, 0, 3, 15, 3, 48], + [0, 2, 3, 8, 4, 48], + [0, 0, 4, 8, 4, 48], + [1, 5, 0, 20, 4, 48], + [1, 3, 1, 20, 4, 48], + [1, 1, 2, 20, 4, 48], + [0, 3, 2, 13, 5, 48], + [0, 1, 3, 13, 5, 48], + [0, 1, 4, 4, 4, 49], + [0, 2, 3, 9, 5, 49], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 1, 4, 5, 1, 50], + [0, 2, 3, 10, 2, 50], + [0, 0, 4, 10, 2, 50], + [0, 3, 2, 15, 3, 50], + [0, 1, 3, 15, 3, 50], + [0, 4, 1, 20, 4, 50], + [0, 2, 2, 20, 4, 50], + [0, 0, 3, 20, 4, 50], + [0, 5, 0, 25, 5, 50], + [0, 3, 1, 25, 5, 50], + [0, 1, 2, 25, 5, 50], + [0, 0, 5, 1, 1, 51], + [0, 0, 4, 11, 3, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 0, 4, 12, 4, 52], + [0, 0, 5, 3, 3, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + [0, 0, 5, 5, 1, 55], + [0, 0, 4, 15, 3, 55], + [0, 0, 3, 25, 5, 55], + ], + 47: [ + [4, 3, 2, 0, 0, 47], + [4, 1, 3, 0, 0, 47], + [2, 2, 3, 1, 1, 47], + [2, 0, 4, 1, 1, 47], + [4, 4, 1, 5, 1, 47], + [4, 2, 2, 5, 1, 47], + [4, 0, 3, 5, 1, 47], + [0, 1, 4, 2, 2, 47], + [2, 3, 2, 6, 2, 47], + [2, 1, 3, 6, 2, 47], + [0, 2, 3, 7, 3, 47], + [0, 0, 4, 7, 3, 47], + [2, 4, 1, 11, 3, 47], + [2, 2, 2, 11, 3, 47], + [2, 0, 3, 11, 3, 47], + [1, 2, 3, 4, 4, 47], + [1, 0, 4, 4, 4, 47], + [0, 3, 2, 12, 4, 47], + [0, 1, 3, 12, 4, 47], + [0, 4, 1, 17, 5, 47], + [0, 2, 2, 17, 5, 47], + [0, 0, 3, 17, 5, 47], + [1, 1, 4, 0, 0, 48], + [4, 3, 2, 1, 1, 48], + [4, 1, 3, 1, 1, 48], + [1, 2, 3, 5, 1, 48], + [1, 0, 4, 5, 1, 48], + [2, 2, 3, 2, 2, 48], + [2, 0, 4, 2, 2, 48], + [1, 3, 2, 10, 2, 48], + [1, 1, 3, 10, 2, 48], + [0, 1, 4, 3, 3, 48], + [2, 3, 2, 7, 3, 48], + [2, 1, 3, 7, 3, 48], + [1, 4, 1, 15, 3, 48], + [1, 2, 2, 15, 3, 48], + [1, 0, 3, 15, 3, 48], + [0, 2, 3, 8, 4, 48], + [0, 0, 4, 8, 4, 48], + [1, 5, 0, 20, 4, 48], + [1, 3, 1, 20, 4, 48], + [1, 1, 2, 20, 4, 48], + [0, 3, 2, 13, 5, 48], + [0, 1, 3, 13, 5, 48], + [3, 2, 3, 0, 0, 49], + [3, 0, 4, 0, 0, 49], + [1, 1, 4, 1, 1, 49], + [3, 3, 2, 5, 1, 49], + [3, 1, 3, 5, 1, 49], + [1, 2, 3, 6, 2, 49], + [1, 0, 4, 6, 2, 49], + [3, 4, 1, 10, 2, 49], + [3, 2, 2, 10, 2, 49], + [3, 0, 3, 10, 2, 49], + [2, 2, 3, 3, 3, 49], + [2, 0, 4, 3, 3, 49], + [1, 3, 2, 11, 3, 49], + [1, 1, 3, 11, 3, 49], + [0, 1, 4, 4, 4, 49], + [1, 4, 1, 16, 4, 49], + [1, 2, 2, 16, 4, 49], + [1, 0, 3, 16, 4, 49], + [0, 2, 3, 9, 5, 49], + [0, 0, 4, 9, 5, 49], + [0, 0, 5, 0, 0, 50], + [0, 1, 4, 5, 1, 50], + [0, 2, 3, 10, 2, 50], + [0, 0, 4, 10, 2, 50], + [0, 3, 2, 15, 3, 50], + [0, 1, 3, 15, 3, 50], + [0, 4, 1, 20, 4, 50], + [0, 2, 2, 20, 4, 50], + [0, 0, 3, 20, 4, 50], + [0, 5, 0, 25, 5, 50], + [0, 3, 1, 25, 5, 50], + [0, 1, 2, 25, 5, 50], + [0, 0, 5, 1, 1, 51], + [0, 1, 4, 6, 2, 51], + [0, 2, 3, 11, 3, 51], + [0, 0, 4, 11, 3, 51], + [0, 3, 2, 16, 4, 51], + [0, 1, 3, 16, 4, 51], + [0, 4, 1, 21, 5, 51], + [0, 2, 2, 21, 5, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 0, 4, 12, 4, 52], + [0, 0, 5, 3, 3, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + [0, 0, 5, 5, 1, 55], + [0, 0, 4, 15, 3, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 0, 4, 16, 4, 56], + ], + 48: [ + [1, 1, 4, 0, 0, 48], + [4, 3, 2, 1, 1, 48], + [4, 1, 3, 1, 1, 48], + [1, 2, 3, 5, 1, 48], + [1, 0, 4, 5, 1, 48], + [2, 2, 3, 2, 2, 48], + [2, 0, 4, 2, 2, 48], + [1, 3, 2, 10, 2, 48], + [1, 1, 3, 10, 2, 48], + [0, 1, 4, 3, 3, 48], + [2, 3, 2, 7, 3, 48], + [2, 1, 3, 7, 3, 48], + [1, 4, 1, 15, 3, 48], + [1, 2, 2, 15, 3, 48], + [1, 0, 3, 15, 3, 48], + [0, 2, 3, 8, 4, 48], + [0, 0, 4, 8, 4, 48], + [1, 5, 0, 20, 4, 48], + [1, 3, 1, 20, 4, 48], + [1, 1, 2, 20, 4, 48], + [0, 3, 2, 13, 5, 48], + [0, 1, 3, 13, 5, 48], + [3, 2, 3, 0, 0, 49], + [3, 0, 4, 0, 0, 49], + [1, 1, 4, 1, 1, 49], + [3, 3, 2, 5, 1, 49], + [3, 1, 3, 5, 1, 49], + [1, 2, 3, 6, 2, 49], + [1, 0, 4, 6, 2, 49], + [3, 4, 1, 10, 2, 49], + [3, 2, 2, 10, 2, 49], + [3, 0, 3, 10, 2, 49], + [2, 2, 3, 3, 3, 49], + [2, 0, 4, 3, 3, 49], + [1, 3, 2, 11, 3, 49], + [1, 1, 3, 11, 3, 49], + [0, 1, 4, 4, 4, 49], + [1, 4, 1, 16, 4, 49], + [1, 2, 2, 16, 4, 49], + [1, 0, 3, 16, 4, 49], + [0, 2, 3, 9, 5, 49], + [0, 0, 4, 9, 5, 49], + [5, 3, 2, 0, 0, 50], + [5, 1, 3, 0, 0, 50], + [0, 0, 5, 0, 0, 50], + [3, 2, 3, 1, 1, 50], + [3, 0, 4, 1, 1, 50], + [0, 1, 4, 5, 1, 50], + [1, 1, 4, 2, 2, 50], + [3, 3, 2, 6, 2, 50], + [3, 1, 3, 6, 2, 50], + [0, 2, 3, 10, 2, 50], + [0, 0, 4, 10, 2, 50], + [1, 2, 3, 7, 3, 50], + [1, 0, 4, 7, 3, 50], + [0, 3, 2, 15, 3, 50], + [0, 1, 3, 15, 3, 50], + [1, 3, 2, 12, 4, 50], + [1, 1, 3, 12, 4, 50], + [0, 4, 1, 20, 4, 50], + [0, 2, 2, 20, 4, 50], + [0, 0, 3, 20, 4, 50], + [0, 5, 0, 25, 5, 50], + [0, 3, 1, 25, 5, 50], + [0, 1, 2, 25, 5, 50], + [0, 0, 5, 1, 1, 51], + [0, 1, 4, 6, 2, 51], + [0, 2, 3, 11, 3, 51], + [0, 0, 4, 11, 3, 51], + [0, 3, 2, 16, 4, 51], + [0, 1, 3, 16, 4, 51], + [0, 4, 1, 21, 5, 51], + [0, 2, 2, 21, 5, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 1, 4, 7, 3, 52], + [0, 2, 3, 12, 4, 52], + [0, 0, 4, 12, 4, 52], + [0, 3, 2, 17, 5, 52], + [0, 1, 3, 17, 5, 52], + [0, 0, 5, 3, 3, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + [0, 0, 5, 5, 1, 55], + [0, 0, 4, 15, 3, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 0, 4, 16, 4, 56], + [0, 0, 5, 7, 3, 57], + [0, 0, 4, 17, 5, 57], + ], + 49: [ + [3, 2, 3, 0, 0, 49], + [3, 0, 4, 0, 0, 49], + [1, 1, 4, 1, 1, 49], + [3, 3, 2, 5, 1, 49], + [3, 1, 3, 5, 1, 49], + [1, 2, 3, 6, 2, 49], + [1, 0, 4, 6, 2, 49], + [3, 4, 1, 10, 2, 49], + [3, 2, 2, 10, 2, 49], + [3, 0, 3, 10, 2, 49], + [2, 2, 3, 3, 3, 49], + [2, 0, 4, 3, 3, 49], + [1, 3, 2, 11, 3, 49], + [1, 1, 3, 11, 3, 49], + [0, 1, 4, 4, 4, 49], + [1, 4, 1, 16, 4, 49], + [1, 2, 2, 16, 4, 49], + [1, 0, 3, 16, 4, 49], + [0, 2, 3, 9, 5, 49], + [0, 0, 4, 9, 5, 49], + [5, 3, 2, 0, 0, 50], + [5, 1, 3, 0, 0, 50], + [0, 0, 5, 0, 0, 50], + [3, 2, 3, 1, 1, 50], + [3, 0, 4, 1, 1, 50], + [0, 1, 4, 5, 1, 50], + [1, 1, 4, 2, 2, 50], + [3, 3, 2, 6, 2, 50], + [3, 1, 3, 6, 2, 50], + [0, 2, 3, 10, 2, 50], + [0, 0, 4, 10, 2, 50], + [1, 2, 3, 7, 3, 50], + [1, 0, 4, 7, 3, 50], + [0, 3, 2, 15, 3, 50], + [0, 1, 3, 15, 3, 50], + [1, 3, 2, 12, 4, 50], + [1, 1, 3, 12, 4, 50], + [0, 4, 1, 20, 4, 50], + [0, 2, 2, 20, 4, 50], + [0, 0, 3, 20, 4, 50], + [0, 5, 0, 25, 5, 50], + [0, 3, 1, 25, 5, 50], + [0, 1, 2, 25, 5, 50], + [2, 1, 4, 0, 0, 51], + [0, 0, 5, 1, 1, 51], + [2, 2, 3, 5, 1, 51], + [2, 0, 4, 5, 1, 51], + [3, 2, 3, 2, 2, 51], + [3, 0, 4, 2, 2, 51], + [0, 1, 4, 6, 2, 51], + [2, 3, 2, 10, 2, 51], + [2, 1, 3, 10, 2, 51], + [1, 1, 4, 3, 3, 51], + [0, 2, 3, 11, 3, 51], + [0, 0, 4, 11, 3, 51], + [2, 4, 1, 15, 3, 51], + [2, 2, 2, 15, 3, 51], + [2, 0, 3, 15, 3, 51], + [1, 2, 3, 8, 4, 51], + [1, 0, 4, 8, 4, 51], + [0, 3, 2, 16, 4, 51], + [0, 1, 3, 16, 4, 51], + [0, 4, 1, 21, 5, 51], + [0, 2, 2, 21, 5, 51], + [0, 0, 3, 21, 5, 51], + [0, 0, 5, 2, 2, 52], + [0, 1, 4, 7, 3, 52], + [0, 2, 3, 12, 4, 52], + [0, 0, 4, 12, 4, 52], + [0, 3, 2, 17, 5, 52], + [0, 1, 3, 17, 5, 52], + [0, 0, 5, 3, 3, 53], + [0, 1, 4, 8, 4, 53], + [0, 2, 3, 13, 5, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + [0, 0, 5, 5, 1, 55], + [0, 0, 4, 15, 3, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 0, 4, 16, 4, 56], + [0, 0, 5, 7, 3, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + ], + 50: [ + [5, 3, 2, 0, 0, 50], + [5, 1, 3, 0, 0, 50], + [0, 0, 5, 0, 0, 50], + [3, 2, 3, 1, 1, 50], + [3, 0, 4, 1, 1, 50], + [0, 1, 4, 5, 1, 50], + [1, 1, 4, 2, 2, 50], + [3, 3, 2, 6, 2, 50], + [3, 1, 3, 6, 2, 50], + [0, 2, 3, 10, 2, 50], + [0, 0, 4, 10, 2, 50], + [1, 2, 3, 7, 3, 50], + [1, 0, 4, 7, 3, 50], + [0, 3, 2, 15, 3, 50], + [0, 1, 3, 15, 3, 50], + [1, 3, 2, 12, 4, 50], + [1, 1, 3, 12, 4, 50], + [0, 4, 1, 20, 4, 50], + [0, 2, 2, 20, 4, 50], + [0, 0, 3, 20, 4, 50], + [0, 5, 0, 25, 5, 50], + [0, 3, 1, 25, 5, 50], + [0, 1, 2, 25, 5, 50], + [2, 1, 4, 0, 0, 51], + [0, 0, 5, 1, 1, 51], + [2, 2, 3, 5, 1, 51], + [2, 0, 4, 5, 1, 51], + [3, 2, 3, 2, 2, 51], + [3, 0, 4, 2, 2, 51], + [0, 1, 4, 6, 2, 51], + [2, 3, 2, 10, 2, 51], + [2, 1, 3, 10, 2, 51], + [1, 1, 4, 3, 3, 51], + [0, 2, 3, 11, 3, 51], + [0, 0, 4, 11, 3, 51], + [2, 4, 1, 15, 3, 51], + [2, 2, 2, 15, 3, 51], + [2, 0, 3, 15, 3, 51], + [1, 2, 3, 8, 4, 51], + [1, 0, 4, 8, 4, 51], + [0, 3, 2, 16, 4, 51], + [0, 1, 3, 16, 4, 51], + [0, 4, 1, 21, 5, 51], + [0, 2, 2, 21, 5, 51], + [0, 0, 3, 21, 5, 51], + [4, 2, 3, 0, 0, 52], + [4, 0, 4, 0, 0, 52], + [2, 1, 4, 1, 1, 52], + [4, 3, 2, 5, 1, 52], + [4, 1, 3, 5, 1, 52], + [0, 0, 5, 2, 2, 52], + [2, 2, 3, 6, 2, 52], + [2, 0, 4, 6, 2, 52], + [0, 1, 4, 7, 3, 52], + [2, 3, 2, 11, 3, 52], + [2, 1, 3, 11, 3, 52], + [1, 1, 4, 4, 4, 52], + [0, 2, 3, 12, 4, 52], + [0, 0, 4, 12, 4, 52], + [0, 3, 2, 17, 5, 52], + [0, 1, 3, 17, 5, 52], + [0, 0, 5, 3, 3, 53], + [0, 1, 4, 8, 4, 53], + [0, 2, 3, 13, 5, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + [0, 1, 4, 9, 5, 54], + [0, 0, 5, 5, 1, 55], + [0, 0, 4, 15, 3, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 0, 4, 16, 4, 56], + [0, 0, 5, 7, 3, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + [0, 0, 5, 9, 5, 59], + ], + 51: [ + [2, 1, 4, 0, 0, 51], + [0, 0, 5, 1, 1, 51], + [2, 2, 3, 5, 1, 51], + [2, 0, 4, 5, 1, 51], + [3, 2, 3, 2, 2, 51], + [3, 0, 4, 2, 2, 51], + [0, 1, 4, 6, 2, 51], + [2, 3, 2, 10, 2, 51], + [2, 1, 3, 10, 2, 51], + [1, 1, 4, 3, 3, 51], + [0, 2, 3, 11, 3, 51], + [0, 0, 4, 11, 3, 51], + [2, 4, 1, 15, 3, 51], + [2, 2, 2, 15, 3, 51], + [2, 0, 3, 15, 3, 51], + [1, 2, 3, 8, 4, 51], + [1, 0, 4, 8, 4, 51], + [0, 3, 2, 16, 4, 51], + [0, 1, 3, 16, 4, 51], + [0, 4, 1, 21, 5, 51], + [0, 2, 2, 21, 5, 51], + [0, 0, 3, 21, 5, 51], + [4, 2, 3, 0, 0, 52], + [4, 0, 4, 0, 0, 52], + [2, 1, 4, 1, 1, 52], + [4, 3, 2, 5, 1, 52], + [4, 1, 3, 5, 1, 52], + [0, 0, 5, 2, 2, 52], + [2, 2, 3, 6, 2, 52], + [2, 0, 4, 6, 2, 52], + [0, 1, 4, 7, 3, 52], + [2, 3, 2, 11, 3, 52], + [2, 1, 3, 11, 3, 52], + [1, 1, 4, 4, 4, 52], + [0, 2, 3, 12, 4, 52], + [0, 0, 4, 12, 4, 52], + [0, 3, 2, 17, 5, 52], + [0, 1, 3, 17, 5, 52], + [1, 0, 5, 0, 0, 53], + [4, 2, 3, 1, 1, 53], + [4, 0, 4, 1, 1, 53], + [1, 1, 4, 5, 1, 53], + [2, 1, 4, 2, 2, 53], + [1, 2, 3, 10, 2, 53], + [1, 0, 4, 10, 2, 53], + [0, 0, 5, 3, 3, 53], + [2, 2, 3, 7, 3, 53], + [2, 0, 4, 7, 3, 53], + [1, 3, 2, 15, 3, 53], + [1, 1, 3, 15, 3, 53], + [0, 1, 4, 8, 4, 53], + [1, 4, 1, 20, 4, 53], + [1, 2, 2, 20, 4, 53], + [1, 0, 3, 20, 4, 53], + [0, 2, 3, 13, 5, 53], + [0, 0, 4, 13, 5, 53], + [0, 0, 5, 4, 4, 54], + [0, 1, 4, 9, 5, 54], + [0, 0, 5, 5, 1, 55], + [0, 1, 4, 10, 2, 55], + [0, 2, 3, 15, 3, 55], + [0, 0, 4, 15, 3, 55], + [0, 3, 2, 20, 4, 55], + [0, 1, 3, 20, 4, 55], + [0, 4, 1, 25, 5, 55], + [0, 2, 2, 25, 5, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 0, 4, 16, 4, 56], + [0, 0, 5, 7, 3, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 0, 4, 20, 4, 60], + ], + 52: [ + [4, 2, 3, 0, 0, 52], + [4, 0, 4, 0, 0, 52], + [2, 1, 4, 1, 1, 52], + [4, 3, 2, 5, 1, 52], + [4, 1, 3, 5, 1, 52], + [0, 0, 5, 2, 2, 52], + [2, 2, 3, 6, 2, 52], + [2, 0, 4, 6, 2, 52], + [0, 1, 4, 7, 3, 52], + [2, 3, 2, 11, 3, 52], + [2, 1, 3, 11, 3, 52], + [1, 1, 4, 4, 4, 52], + [0, 2, 3, 12, 4, 52], + [0, 0, 4, 12, 4, 52], + [0, 3, 2, 17, 5, 52], + [0, 1, 3, 17, 5, 52], + [1, 0, 5, 0, 0, 53], + [4, 2, 3, 1, 1, 53], + [4, 0, 4, 1, 1, 53], + [1, 1, 4, 5, 1, 53], + [2, 1, 4, 2, 2, 53], + [1, 2, 3, 10, 2, 53], + [1, 0, 4, 10, 2, 53], + [0, 0, 5, 3, 3, 53], + [2, 2, 3, 7, 3, 53], + [2, 0, 4, 7, 3, 53], + [1, 3, 2, 15, 3, 53], + [1, 1, 3, 15, 3, 53], + [0, 1, 4, 8, 4, 53], + [1, 4, 1, 20, 4, 53], + [1, 2, 2, 20, 4, 53], + [1, 0, 3, 20, 4, 53], + [0, 2, 3, 13, 5, 53], + [0, 0, 4, 13, 5, 53], + [3, 1, 4, 0, 0, 54], + [1, 0, 5, 1, 1, 54], + [3, 2, 3, 5, 1, 54], + [3, 0, 4, 5, 1, 54], + [1, 1, 4, 6, 2, 54], + [3, 3, 2, 10, 2, 54], + [3, 1, 3, 10, 2, 54], + [2, 1, 4, 3, 3, 54], + [1, 2, 3, 11, 3, 54], + [1, 0, 4, 11, 3, 54], + [0, 0, 5, 4, 4, 54], + [1, 3, 2, 16, 4, 54], + [1, 1, 3, 16, 4, 54], + [0, 1, 4, 9, 5, 54], + [0, 0, 5, 5, 1, 55], + [0, 1, 4, 10, 2, 55], + [0, 2, 3, 15, 3, 55], + [0, 0, 4, 15, 3, 55], + [0, 3, 2, 20, 4, 55], + [0, 1, 3, 20, 4, 55], + [0, 4, 1, 25, 5, 55], + [0, 2, 2, 25, 5, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 1, 4, 11, 3, 56], + [0, 2, 3, 16, 4, 56], + [0, 0, 4, 16, 4, 56], + [0, 3, 2, 21, 5, 56], + [0, 1, 3, 21, 5, 56], + [0, 0, 5, 7, 3, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 0, 4, 20, 4, 60], + [0, 0, 5, 11, 3, 61], + [0, 0, 4, 21, 5, 61], + ], + 53: [ + [1, 0, 5, 0, 0, 53], + [4, 2, 3, 1, 1, 53], + [4, 0, 4, 1, 1, 53], + [1, 1, 4, 5, 1, 53], + [2, 1, 4, 2, 2, 53], + [1, 2, 3, 10, 2, 53], + [1, 0, 4, 10, 2, 53], + [0, 0, 5, 3, 3, 53], + [2, 2, 3, 7, 3, 53], + [2, 0, 4, 7, 3, 53], + [1, 3, 2, 15, 3, 53], + [1, 1, 3, 15, 3, 53], + [0, 1, 4, 8, 4, 53], + [1, 4, 1, 20, 4, 53], + [1, 2, 2, 20, 4, 53], + [1, 0, 3, 20, 4, 53], + [0, 2, 3, 13, 5, 53], + [0, 0, 4, 13, 5, 53], + [3, 1, 4, 0, 0, 54], + [1, 0, 5, 1, 1, 54], + [3, 2, 3, 5, 1, 54], + [3, 0, 4, 5, 1, 54], + [1, 1, 4, 6, 2, 54], + [3, 3, 2, 10, 2, 54], + [3, 1, 3, 10, 2, 54], + [2, 1, 4, 3, 3, 54], + [1, 2, 3, 11, 3, 54], + [1, 0, 4, 11, 3, 54], + [0, 0, 5, 4, 4, 54], + [1, 3, 2, 16, 4, 54], + [1, 1, 3, 16, 4, 54], + [0, 1, 4, 9, 5, 54], + [5, 2, 3, 0, 0, 55], + [5, 0, 4, 0, 0, 55], + [3, 1, 4, 1, 1, 55], + [0, 0, 5, 5, 1, 55], + [1, 0, 5, 2, 2, 55], + [3, 2, 3, 6, 2, 55], + [3, 0, 4, 6, 2, 55], + [0, 1, 4, 10, 2, 55], + [1, 1, 4, 7, 3, 55], + [0, 2, 3, 15, 3, 55], + [0, 0, 4, 15, 3, 55], + [1, 2, 3, 12, 4, 55], + [1, 0, 4, 12, 4, 55], + [0, 3, 2, 20, 4, 55], + [0, 1, 3, 20, 4, 55], + [0, 4, 1, 25, 5, 55], + [0, 2, 2, 25, 5, 55], + [0, 0, 3, 25, 5, 55], + [0, 0, 5, 6, 2, 56], + [0, 1, 4, 11, 3, 56], + [0, 2, 3, 16, 4, 56], + [0, 0, 4, 16, 4, 56], + [0, 3, 2, 21, 5, 56], + [0, 1, 3, 21, 5, 56], + [0, 0, 5, 7, 3, 57], + [0, 1, 4, 12, 4, 57], + [0, 2, 3, 17, 5, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 0, 4, 20, 4, 60], + [0, 0, 5, 11, 3, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + ], + 54: [ + [3, 1, 4, 0, 0, 54], + [1, 0, 5, 1, 1, 54], + [3, 2, 3, 5, 1, 54], + [3, 0, 4, 5, 1, 54], + [1, 1, 4, 6, 2, 54], + [3, 3, 2, 10, 2, 54], + [3, 1, 3, 10, 2, 54], + [2, 1, 4, 3, 3, 54], + [1, 2, 3, 11, 3, 54], + [1, 0, 4, 11, 3, 54], + [0, 0, 5, 4, 4, 54], + [1, 3, 2, 16, 4, 54], + [1, 1, 3, 16, 4, 54], + [0, 1, 4, 9, 5, 54], + [5, 2, 3, 0, 0, 55], + [5, 0, 4, 0, 0, 55], + [3, 1, 4, 1, 1, 55], + [0, 0, 5, 5, 1, 55], + [1, 0, 5, 2, 2, 55], + [3, 2, 3, 6, 2, 55], + [3, 0, 4, 6, 2, 55], + [0, 1, 4, 10, 2, 55], + [1, 1, 4, 7, 3, 55], + [0, 2, 3, 15, 3, 55], + [0, 0, 4, 15, 3, 55], + [1, 2, 3, 12, 4, 55], + [1, 0, 4, 12, 4, 55], + [0, 3, 2, 20, 4, 55], + [0, 1, 3, 20, 4, 55], + [0, 4, 1, 25, 5, 55], + [0, 2, 2, 25, 5, 55], + [0, 0, 3, 25, 5, 55], + [2, 0, 5, 0, 0, 56], + [2, 1, 4, 5, 1, 56], + [3, 1, 4, 2, 2, 56], + [0, 0, 5, 6, 2, 56], + [2, 2, 3, 10, 2, 56], + [2, 0, 4, 10, 2, 56], + [1, 0, 5, 3, 3, 56], + [0, 1, 4, 11, 3, 56], + [2, 3, 2, 15, 3, 56], + [2, 1, 3, 15, 3, 56], + [1, 1, 4, 8, 4, 56], + [0, 2, 3, 16, 4, 56], + [0, 0, 4, 16, 4, 56], + [0, 3, 2, 21, 5, 56], + [0, 1, 3, 21, 5, 56], + [0, 0, 5, 7, 3, 57], + [0, 1, 4, 12, 4, 57], + [0, 2, 3, 17, 5, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + [0, 1, 4, 13, 5, 58], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 0, 4, 20, 4, 60], + [0, 0, 5, 11, 3, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + [0, 0, 5, 13, 5, 63], + ], + 55: [ + [5, 2, 3, 0, 0, 55], + [5, 0, 4, 0, 0, 55], + [3, 1, 4, 1, 1, 55], + [0, 0, 5, 5, 1, 55], + [1, 0, 5, 2, 2, 55], + [3, 2, 3, 6, 2, 55], + [3, 0, 4, 6, 2, 55], + [0, 1, 4, 10, 2, 55], + [1, 1, 4, 7, 3, 55], + [0, 2, 3, 15, 3, 55], + [0, 0, 4, 15, 3, 55], + [1, 2, 3, 12, 4, 55], + [1, 0, 4, 12, 4, 55], + [0, 3, 2, 20, 4, 55], + [0, 1, 3, 20, 4, 55], + [0, 4, 1, 25, 5, 55], + [0, 2, 2, 25, 5, 55], + [0, 0, 3, 25, 5, 55], + [2, 0, 5, 0, 0, 56], + [2, 1, 4, 5, 1, 56], + [3, 1, 4, 2, 2, 56], + [0, 0, 5, 6, 2, 56], + [2, 2, 3, 10, 2, 56], + [2, 0, 4, 10, 2, 56], + [1, 0, 5, 3, 3, 56], + [0, 1, 4, 11, 3, 56], + [2, 3, 2, 15, 3, 56], + [2, 1, 3, 15, 3, 56], + [1, 1, 4, 8, 4, 56], + [0, 2, 3, 16, 4, 56], + [0, 0, 4, 16, 4, 56], + [0, 3, 2, 21, 5, 56], + [0, 1, 3, 21, 5, 56], + [4, 1, 4, 0, 0, 57], + [2, 0, 5, 1, 1, 57], + [4, 2, 3, 5, 1, 57], + [4, 0, 4, 5, 1, 57], + [2, 1, 4, 6, 2, 57], + [0, 0, 5, 7, 3, 57], + [2, 2, 3, 11, 3, 57], + [2, 0, 4, 11, 3, 57], + [1, 0, 5, 4, 4, 57], + [0, 1, 4, 12, 4, 57], + [0, 2, 3, 17, 5, 57], + [0, 0, 4, 17, 5, 57], + [0, 0, 5, 8, 4, 58], + [0, 1, 4, 13, 5, 58], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 0, 4, 20, 4, 60], + [0, 0, 5, 11, 3, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + [0, 0, 5, 13, 5, 63], + ], + 56: [ + [2, 0, 5, 0, 0, 56], + [2, 1, 4, 5, 1, 56], + [3, 1, 4, 2, 2, 56], + [0, 0, 5, 6, 2, 56], + [2, 2, 3, 10, 2, 56], + [2, 0, 4, 10, 2, 56], + [1, 0, 5, 3, 3, 56], + [0, 1, 4, 11, 3, 56], + [2, 3, 2, 15, 3, 56], + [2, 1, 3, 15, 3, 56], + [1, 1, 4, 8, 4, 56], + [0, 2, 3, 16, 4, 56], + [0, 0, 4, 16, 4, 56], + [0, 3, 2, 21, 5, 56], + [0, 1, 3, 21, 5, 56], + [4, 1, 4, 0, 0, 57], + [2, 0, 5, 1, 1, 57], + [4, 2, 3, 5, 1, 57], + [4, 0, 4, 5, 1, 57], + [2, 1, 4, 6, 2, 57], + [0, 0, 5, 7, 3, 57], + [2, 2, 3, 11, 3, 57], + [2, 0, 4, 11, 3, 57], + [1, 0, 5, 4, 4, 57], + [0, 1, 4, 12, 4, 57], + [0, 2, 3, 17, 5, 57], + [0, 0, 4, 17, 5, 57], + [4, 1, 4, 1, 1, 58], + [1, 0, 5, 5, 1, 58], + [2, 0, 5, 2, 2, 58], + [1, 1, 4, 10, 2, 58], + [2, 1, 4, 7, 3, 58], + [1, 2, 3, 15, 3, 58], + [1, 0, 4, 15, 3, 58], + [0, 0, 5, 8, 4, 58], + [1, 3, 2, 20, 4, 58], + [1, 1, 3, 20, 4, 58], + [0, 1, 4, 13, 5, 58], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 1, 4, 15, 3, 60], + [0, 2, 3, 20, 4, 60], + [0, 0, 4, 20, 4, 60], + [0, 3, 2, 25, 5, 60], + [0, 1, 3, 25, 5, 60], + [0, 0, 5, 11, 3, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + [0, 0, 5, 13, 5, 63], + [0, 0, 5, 15, 3, 65], + [0, 0, 4, 25, 5, 65], + ], + 57: [ + [4, 1, 4, 0, 0, 57], + [2, 0, 5, 1, 1, 57], + [4, 2, 3, 5, 1, 57], + [4, 0, 4, 5, 1, 57], + [2, 1, 4, 6, 2, 57], + [0, 0, 5, 7, 3, 57], + [2, 2, 3, 11, 3, 57], + [2, 0, 4, 11, 3, 57], + [1, 0, 5, 4, 4, 57], + [0, 1, 4, 12, 4, 57], + [0, 2, 3, 17, 5, 57], + [0, 0, 4, 17, 5, 57], + [4, 1, 4, 1, 1, 58], + [1, 0, 5, 5, 1, 58], + [2, 0, 5, 2, 2, 58], + [1, 1, 4, 10, 2, 58], + [2, 1, 4, 7, 3, 58], + [1, 2, 3, 15, 3, 58], + [1, 0, 4, 15, 3, 58], + [0, 0, 5, 8, 4, 58], + [1, 3, 2, 20, 4, 58], + [1, 1, 3, 20, 4, 58], + [0, 1, 4, 13, 5, 58], + [3, 0, 5, 0, 0, 59], + [3, 1, 4, 5, 1, 59], + [1, 0, 5, 6, 2, 59], + [3, 2, 3, 10, 2, 59], + [3, 0, 4, 10, 2, 59], + [2, 0, 5, 3, 3, 59], + [1, 1, 4, 11, 3, 59], + [1, 2, 3, 16, 4, 59], + [1, 0, 4, 16, 4, 59], + [0, 0, 5, 9, 5, 59], + [0, 0, 5, 10, 2, 60], + [0, 1, 4, 15, 3, 60], + [0, 2, 3, 20, 4, 60], + [0, 0, 4, 20, 4, 60], + [0, 3, 2, 25, 5, 60], + [0, 1, 3, 25, 5, 60], + [0, 0, 5, 11, 3, 61], + [0, 1, 4, 16, 4, 61], + [0, 2, 3, 21, 5, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + [0, 0, 5, 13, 5, 63], + [0, 0, 5, 15, 3, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + ], + 58: [ + [4, 1, 4, 1, 1, 58], + [1, 0, 5, 5, 1, 58], + [2, 0, 5, 2, 2, 58], + [1, 1, 4, 10, 2, 58], + [2, 1, 4, 7, 3, 58], + [1, 2, 3, 15, 3, 58], + [1, 0, 4, 15, 3, 58], + [0, 0, 5, 8, 4, 58], + [1, 3, 2, 20, 4, 58], + [1, 1, 3, 20, 4, 58], + [0, 1, 4, 13, 5, 58], + [3, 0, 5, 0, 0, 59], + [3, 1, 4, 5, 1, 59], + [1, 0, 5, 6, 2, 59], + [3, 2, 3, 10, 2, 59], + [3, 0, 4, 10, 2, 59], + [2, 0, 5, 3, 3, 59], + [1, 1, 4, 11, 3, 59], + [1, 2, 3, 16, 4, 59], + [1, 0, 4, 16, 4, 59], + [0, 0, 5, 9, 5, 59], + [5, 1, 4, 0, 0, 60], + [3, 0, 5, 1, 1, 60], + [3, 1, 4, 6, 2, 60], + [0, 0, 5, 10, 2, 60], + [1, 0, 5, 7, 3, 60], + [0, 1, 4, 15, 3, 60], + [1, 1, 4, 12, 4, 60], + [0, 2, 3, 20, 4, 60], + [0, 0, 4, 20, 4, 60], + [0, 3, 2, 25, 5, 60], + [0, 1, 3, 25, 5, 60], + [0, 0, 5, 11, 3, 61], + [0, 1, 4, 16, 4, 61], + [0, 2, 3, 21, 5, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + [0, 1, 4, 17, 5, 62], + [0, 0, 5, 13, 5, 63], + [0, 0, 5, 15, 3, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + [0, 0, 5, 17, 5, 67], + ], + 59: [ + [3, 0, 5, 0, 0, 59], + [3, 1, 4, 5, 1, 59], + [1, 0, 5, 6, 2, 59], + [3, 2, 3, 10, 2, 59], + [3, 0, 4, 10, 2, 59], + [2, 0, 5, 3, 3, 59], + [1, 1, 4, 11, 3, 59], + [1, 2, 3, 16, 4, 59], + [1, 0, 4, 16, 4, 59], + [0, 0, 5, 9, 5, 59], + [5, 1, 4, 0, 0, 60], + [3, 0, 5, 1, 1, 60], + [3, 1, 4, 6, 2, 60], + [0, 0, 5, 10, 2, 60], + [1, 0, 5, 7, 3, 60], + [0, 1, 4, 15, 3, 60], + [1, 1, 4, 12, 4, 60], + [0, 2, 3, 20, 4, 60], + [0, 0, 4, 20, 4, 60], + [0, 3, 2, 25, 5, 60], + [0, 1, 3, 25, 5, 60], + [2, 0, 5, 5, 1, 61], + [3, 0, 5, 2, 2, 61], + [2, 1, 4, 10, 2, 61], + [0, 0, 5, 11, 3, 61], + [2, 2, 3, 15, 3, 61], + [2, 0, 4, 15, 3, 61], + [1, 0, 5, 8, 4, 61], + [0, 1, 4, 16, 4, 61], + [0, 2, 3, 21, 5, 61], + [0, 0, 4, 21, 5, 61], + [0, 0, 5, 12, 4, 62], + [0, 1, 4, 17, 5, 62], + [0, 0, 5, 13, 5, 63], + [0, 0, 5, 15, 3, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + [0, 0, 5, 17, 5, 67], + ], + 60: [ + [5, 1, 4, 0, 0, 60], + [3, 0, 5, 1, 1, 60], + [3, 1, 4, 6, 2, 60], + [0, 0, 5, 10, 2, 60], + [1, 0, 5, 7, 3, 60], + [0, 1, 4, 15, 3, 60], + [1, 1, 4, 12, 4, 60], + [0, 2, 3, 20, 4, 60], + [0, 0, 4, 20, 4, 60], + [0, 3, 2, 25, 5, 60], + [0, 1, 3, 25, 5, 60], + [2, 0, 5, 5, 1, 61], + [3, 0, 5, 2, 2, 61], + [2, 1, 4, 10, 2, 61], + [0, 0, 5, 11, 3, 61], + [2, 2, 3, 15, 3, 61], + [2, 0, 4, 15, 3, 61], + [1, 0, 5, 8, 4, 61], + [0, 1, 4, 16, 4, 61], + [0, 2, 3, 21, 5, 61], + [0, 0, 4, 21, 5, 61], + [4, 0, 5, 0, 0, 62], + [4, 1, 4, 5, 1, 62], + [2, 0, 5, 6, 2, 62], + [2, 1, 4, 11, 3, 62], + [0, 0, 5, 12, 4, 62], + [0, 1, 4, 17, 5, 62], + [0, 0, 5, 13, 5, 63], + [0, 0, 5, 15, 3, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + [0, 0, 5, 17, 5, 67], + ], + 61: [ + [2, 0, 5, 5, 1, 61], + [3, 0, 5, 2, 2, 61], + [2, 1, 4, 10, 2, 61], + [0, 0, 5, 11, 3, 61], + [2, 2, 3, 15, 3, 61], + [2, 0, 4, 15, 3, 61], + [1, 0, 5, 8, 4, 61], + [0, 1, 4, 16, 4, 61], + [0, 2, 3, 21, 5, 61], + [0, 0, 4, 21, 5, 61], + [4, 0, 5, 0, 0, 62], + [4, 1, 4, 5, 1, 62], + [2, 0, 5, 6, 2, 62], + [2, 1, 4, 11, 3, 62], + [0, 0, 5, 12, 4, 62], + [0, 1, 4, 17, 5, 62], + [4, 0, 5, 1, 1, 63], + [1, 0, 5, 10, 2, 63], + [2, 0, 5, 7, 3, 63], + [1, 1, 4, 15, 3, 63], + [1, 2, 3, 20, 4, 63], + [1, 0, 4, 20, 4, 63], + [0, 0, 5, 13, 5, 63], + [0, 0, 5, 15, 3, 65], + [0, 1, 4, 20, 4, 65], + [0, 2, 3, 25, 5, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + [0, 0, 5, 17, 5, 67], + [0, 0, 5, 20, 4, 70], + ], + 62: [ + [4, 0, 5, 0, 0, 62], + [4, 1, 4, 5, 1, 62], + [2, 0, 5, 6, 2, 62], + [2, 1, 4, 11, 3, 62], + [0, 0, 5, 12, 4, 62], + [0, 1, 4, 17, 5, 62], + [4, 0, 5, 1, 1, 63], + [1, 0, 5, 10, 2, 63], + [2, 0, 5, 7, 3, 63], + [1, 1, 4, 15, 3, 63], + [1, 2, 3, 20, 4, 63], + [1, 0, 4, 20, 4, 63], + [0, 0, 5, 13, 5, 63], + [3, 0, 5, 5, 1, 64], + [3, 1, 4, 10, 2, 64], + [1, 0, 5, 11, 3, 64], + [1, 1, 4, 16, 4, 64], + [0, 0, 5, 15, 3, 65], + [0, 1, 4, 20, 4, 65], + [0, 2, 3, 25, 5, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + [0, 1, 4, 21, 5, 66], + [0, 0, 5, 17, 5, 67], + [0, 0, 5, 20, 4, 70], + [0, 0, 5, 21, 5, 71], + ], + 63: [ + [4, 0, 5, 1, 1, 63], + [1, 0, 5, 10, 2, 63], + [2, 0, 5, 7, 3, 63], + [1, 1, 4, 15, 3, 63], + [1, 2, 3, 20, 4, 63], + [1, 0, 4, 20, 4, 63], + [0, 0, 5, 13, 5, 63], + [3, 0, 5, 5, 1, 64], + [3, 1, 4, 10, 2, 64], + [1, 0, 5, 11, 3, 64], + [1, 1, 4, 16, 4, 64], + [5, 0, 5, 0, 0, 65], + [3, 0, 5, 6, 2, 65], + [0, 0, 5, 15, 3, 65], + [1, 0, 5, 12, 4, 65], + [0, 1, 4, 20, 4, 65], + [0, 2, 3, 25, 5, 65], + [0, 0, 4, 25, 5, 65], + [0, 0, 5, 16, 4, 66], + [0, 1, 4, 21, 5, 66], + [0, 0, 5, 17, 5, 67], + [0, 0, 5, 20, 4, 70], + [0, 0, 5, 21, 5, 71], + ], + 64: [ + [3, 0, 5, 5, 1, 64], + [3, 1, 4, 10, 2, 64], + [1, 0, 5, 11, 3, 64], + [1, 1, 4, 16, 4, 64], + [5, 0, 5, 0, 0, 65], + [3, 0, 5, 6, 2, 65], + [0, 0, 5, 15, 3, 65], + [1, 0, 5, 12, 4, 65], + [0, 1, 4, 20, 4, 65], + [0, 2, 3, 25, 5, 65], + [0, 0, 4, 25, 5, 65], + [2, 0, 5, 10, 2, 66], + [2, 1, 4, 15, 3, 66], + [0, 0, 5, 16, 4, 66], + [0, 1, 4, 21, 5, 66], + [0, 0, 5, 17, 5, 67], + [0, 0, 5, 20, 4, 70], + [0, 0, 5, 21, 5, 71], + ], + 65: [ + [5, 0, 5, 0, 0, 65], + [3, 0, 5, 6, 2, 65], + [0, 0, 5, 15, 3, 65], + [1, 0, 5, 12, 4, 65], + [0, 1, 4, 20, 4, 65], + [0, 2, 3, 25, 5, 65], + [0, 0, 4, 25, 5, 65], + [2, 0, 5, 10, 2, 66], + [2, 1, 4, 15, 3, 66], + [0, 0, 5, 16, 4, 66], + [0, 1, 4, 21, 5, 66], + [4, 0, 5, 5, 1, 67], + [2, 0, 5, 11, 3, 67], + [0, 0, 5, 17, 5, 67], + [0, 0, 5, 20, 4, 70], + [0, 0, 5, 21, 5, 71], + ], + 66: [ + [2, 0, 5, 10, 2, 66], + [2, 1, 4, 15, 3, 66], + [0, 0, 5, 16, 4, 66], + [0, 1, 4, 21, 5, 66], + [4, 0, 5, 5, 1, 67], + [2, 0, 5, 11, 3, 67], + [0, 0, 5, 17, 5, 67], + [1, 0, 5, 15, 3, 68], + [1, 1, 4, 20, 4, 68], + [0, 0, 5, 20, 4, 70], + [0, 1, 4, 25, 5, 70], + [0, 0, 5, 21, 5, 71], + [0, 0, 5, 25, 5, 75], + ], + 67: [ + [4, 0, 5, 5, 1, 67], + [2, 0, 5, 11, 3, 67], + [0, 0, 5, 17, 5, 67], + [1, 0, 5, 15, 3, 68], + [1, 1, 4, 20, 4, 68], + [3, 0, 5, 10, 2, 69], + [1, 0, 5, 16, 4, 69], + [0, 0, 5, 20, 4, 70], + [0, 1, 4, 25, 5, 70], + [0, 0, 5, 21, 5, 71], + [0, 0, 5, 25, 5, 75], + ], + 68: [ + [1, 0, 5, 15, 3, 68], + [1, 1, 4, 20, 4, 68], + [3, 0, 5, 10, 2, 69], + [1, 0, 5, 16, 4, 69], + [0, 0, 5, 20, 4, 70], + [0, 1, 4, 25, 5, 70], + [0, 0, 5, 21, 5, 71], + [0, 0, 5, 25, 5, 75], + ], + 69: [ + [3, 0, 5, 10, 2, 69], + [1, 0, 5, 16, 4, 69], + [0, 0, 5, 20, 4, 70], + [0, 1, 4, 25, 5, 70], + [2, 0, 5, 15, 3, 71], + [0, 0, 5, 21, 5, 71], + [0, 0, 5, 25, 5, 75], + ], + 70: [ + [0, 0, 5, 20, 4, 70], + [0, 1, 4, 25, 5, 70], + [2, 0, 5, 15, 3, 71], + [0, 0, 5, 21, 5, 71], + [0, 0, 5, 25, 5, 75], + ], + 71: [ + [2, 0, 5, 15, 3, 71], + [0, 0, 5, 21, 5, 71], + [1, 0, 5, 20, 4, 73], + [0, 0, 5, 25, 5, 75], + ], + 72: [ + [1, 0, 5, 20, 4, 73], + [0, 0, 5, 25, 5, 75], + ], + 73: [ + [1, 0, 5, 20, 4, 73], + [0, 0, 5, 25, 5, 75], + ], + 74: [[0, 0, 5, 25, 5, 75]], + 75: [[0, 0, 5, 25, 5, 75]], +}; diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 9d49116a..88d68f74 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -32,8 +32,6 @@ import { import { environment } from "../../environments/environment"; -import { precalculatedZeroWasteModCombinations } from "../data/generated/precalculatedZeroWasteModCombinations"; -import { precalculatedModCombinations } from "../data/generated/precalculatedModCombinations"; import { ModOptimizationStrategy } from "../data/enum/mod-optimization-strategy"; import { IPermutatorArmor } from "../data/types/IPermutatorArmor"; import { @@ -42,6 +40,7 @@ import { isIPermutatorArmorSet, } from "../data/types/IPermutatorArmorSet"; import { ArmorSystem } from "../data/types/IManifestArmor"; +import { precalculatedModCombinations } from "../data/generated/precalculatedModCombinations"; // endregion Imports // region Validation and Preparation Functions @@ -899,6 +898,46 @@ function tryCreateArmorSetWithClassItem( } // region Mod Calculation Functions +function get_mods_recursive( + distances_to_check: number[], + availableArtificeCount: number, + availableMajorMods: number, + availableMods: number +): number[][] | null { + if (distances_to_check.length == 0) { + return []; + } + const distance = distances_to_check[0]; + let precalculatedMods = precalculatedModCombinations[distance] || [[0, 0, 0, 0, 0, 0]]; + // let precalculatedMods = precalculatedTuningModCombinations[0] + precalculatedMods = precalculatedMods.filter( + (mod) => + mod[0] <= availableArtificeCount && + mod[2] <= availableMajorMods && + mod[2] + mod[1] <= availableMods + ); + + if (precalculatedMods.length == 0) { + return null; + } + + for (const pickedMod of precalculatedMods) { + const totalMods = Math.max(0, availableMods - pickedMod[1] - pickedMod[2]); + const majorMods = Math.min(totalMods, Math.max(0, availableMajorMods - pickedMod[2])); + const artifice = Math.max(0, availableArtificeCount - pickedMod[0]); + const otherMods = get_mods_recursive( + distances_to_check.slice(1), + artifice, + majorMods, + totalMods + ); + if (otherMods !== null) { + return [pickedMod, ...otherMods]; + } + } + return null; +} + function get_mods_precalc( config: BuildConfiguration, distances: number[], @@ -916,140 +955,20 @@ function get_mods_precalc( return []; } - const modCombinations = config.onlyShowResultsWithNoWastedStats - ? precalculatedZeroWasteModCombinations - : precalculatedModCombinations; - - // grab the precalculated mods for the distances - const precalculatedMods = [ - modCombinations[distances[0]] || [[0, 0, 0, 0]], // mobility - modCombinations[distances[1]] || [[0, 0, 0, 0]], // resilience - modCombinations[distances[2]] || [[0, 0, 0, 0]], // recovery - modCombinations[distances[3]] || [[0, 0, 0, 0]], // discipline - modCombinations[distances[4]] || [[0, 0, 0, 0]], // intellect - modCombinations[distances[5]] || [[0, 0, 0, 0]], // strength - ]; - - // we handle locked exact stats as zero-waste in terms of the mod selection - for (let i = 0; i < 6; i++) { - if (config.minimumStatTiers[i as ArmorStat].fixed && distances[i] > 0) { - precalculatedMods[i] = precalculatedZeroWasteModCombinations[distances[i]] || [[0, 0, 0, 0]]; - // and now also remove every solution with >= 10 points of "overshoot" - precalculatedMods[i] = precalculatedMods[i].filter((d) => d[3] - distances[i] < 10); - } - } - - // add optional distances to the precalculated mods - const limit = 3; - for (let i = 0; i < optionalDistances.length; i++) { - if (optionalDistances[i] > 0) { - const additionalCombosA = modCombinations[optionalDistances[i]].filter( - (d) => - d[2] == 0 && // disallow major mods - d[3] % 10 > 0 && // we do not want to add exact stat tiers - (optionalDistances[i] + d[3]) % 10 < optionalDistances[i] // and the changes must have less waste than before - ); - //(d) => d[3] % 10 > 0); - if (additionalCombosA != null) { - precalculatedMods[i] = additionalCombosA.slice(0, limit).concat(precalculatedMods[i]); - } - } - } - - for (let i = 0; i < 6; i++) { - precalculatedMods[i] = precalculatedMods[i].filter( - (d) => - d[2] <= config.statModLimits.maxMajorMods && d[1] + d[2] <= config.statModLimits.maxMods - ); - - if (precalculatedMods[i] == null || precalculatedMods[i].length == 0) { - // if there are no mods for this distance, we can not calculate anything - return null; - } - } - - let bestMods: any = null; - let bestScore = 1000; - - function score(entries: [number, number, number, number][]) { - if (optimize == ModOptimizationStrategy.ReduceUsedModSockets) { - const n1 = entries.reduce((a, b) => a + b[1] + b[2], 0); - return n1; - } else if (optimize == ModOptimizationStrategy.ReduceUsedModPoints) { - return entries.reduce((a, b, currentIndex) => a + 1 * b[1] + 3 * b[2], 0); - } - return entries.reduce((a, b) => a + b[3], 0); - } - - function validate(entries: [number, number, number, number][]): boolean { - // sum up the stats - const sum = entries.reduce( - (a, b, i) => [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3] - distances[i]], - [0, 0, 0, 0] - ); - - if (score(entries) > bestScore) return false; - if (sum[0] > availableArtificeCount) return false; - if (sum[1] + sum[2] > config.statModLimits.maxMods) return false; - if (sum[2] > config.statModLimits.maxMajorMods) return false; - if (sum[3] < 0) return false; - - return true; - } + let pickedMods = get_mods_recursive( + distances, + availableArtificeCount, + config.statModLimits.maxMajorMods, + config.statModLimits.maxMods + ); - const mustExecuteOptimization = totalDistance > 0 && optimize != ModOptimizationStrategy.None; - root: for (let mobility of precalculatedMods[0]) { - if (!validate([mobility])) continue; - for (let resilience of precalculatedMods[1]) { - if (!validate([mobility, resilience])) continue; - for (let recovery of precalculatedMods[2]) { - if (!validate([mobility, resilience, recovery])) continue; - if (mustExecuteOptimization && score([mobility, resilience, recovery]) >= bestScore) - continue; - for (let discipline of precalculatedMods[3]) { - if (!validate([mobility, resilience, recovery, discipline])) continue; - if ( - mustExecuteOptimization && - score([mobility, resilience, recovery, discipline]) >= bestScore - ) - continue; - for (let intellect of precalculatedMods[4]) { - if (!validate([mobility, resilience, recovery, discipline, intellect])) continue; - if ( - mustExecuteOptimization && - score([mobility, resilience, recovery, discipline, intellect]) >= bestScore - ) - continue; - inner: for (let strength of precalculatedMods[5]) { - let mods = [mobility, resilience, recovery, discipline, intellect, strength]; - - if (!validate(mods)) continue; - - // Fill optional distances - for (let m = 0; m < 6; m++) - if (optionalDistances[m] > 0 && mods[m][3] == 0 && bestMods != null) continue inner; - - let scoreVal = score(mods); - if (scoreVal < bestScore) { - bestScore = scoreVal; - bestMods = mods; - if (!mustExecuteOptimization) { - break root; - } - } - } - } - } - } - } - } - if (bestMods === null) return null; + if (pickedMods === null) return null; const usedMods = []; - for (let i = 0; i < bestMods.length; i++) { - for (let n = 0; n < bestMods[i][0]; n++) usedMods.push(3 + 3 * i); - for (let n = 0; n < bestMods[i][1]; n++) usedMods.push(1 + 3 * i); - for (let n = 0; n < bestMods[i][2]; n++) usedMods.push(2 + 3 * i); + for (let i = 0; i < pickedMods.length; i++) { + for (let n = 0; n < pickedMods[i][0]; n++) usedMods.push(3 + 3 * i); + for (let n = 0; n < pickedMods[i][1]; n++) usedMods.push(1 + 3 * i); + for (let n = 0; n < pickedMods[i][2]; n++) usedMods.push(2 + 3 * i); } return usedMods; From 3d11076db5835a35c12c732c594baeda93e9f6b0 Mon Sep 17 00:00:00 2001 From: Mijago Date: Thu, 7 Aug 2025 20:48:23 +0200 Subject: [PATCH 02/84] feat: retrieve tuningStatHash in BungieApiService --- src/app/data/types/IInventoryArmor.ts | 1 + src/app/data/types/IPermutatorArmor.ts | 1 + src/app/services/bungie-api.service.ts | 23 +++++++++++++++++++++++ src/app/services/inventory.service.ts | 1 + 4 files changed, 26 insertions(+) diff --git a/src/app/data/types/IInventoryArmor.ts b/src/app/data/types/IInventoryArmor.ts index 3e13a1dc..262bb2c0 100644 --- a/src/app/data/types/IInventoryArmor.ts +++ b/src/app/data/types/IInventoryArmor.ts @@ -63,6 +63,7 @@ export interface IInventoryArmor ITimestampedEntry { // Note: this will be empty for vendor items statPlugHashes?: (number | undefined)[]; + tuningStatHash?: number; // for armor 3.0, this is the tuning stat hash // exoticPerkHash is now inherited as number[] from IManifestArmor } diff --git a/src/app/data/types/IPermutatorArmor.ts b/src/app/data/types/IPermutatorArmor.ts index 01e20545..a5a20f4a 100644 --- a/src/app/data/types/IPermutatorArmor.ts +++ b/src/app/data/types/IPermutatorArmor.ts @@ -9,4 +9,5 @@ export interface IPermutatorArmor extends IDestinyArmor { rarity: TierType; isSunset: boolean; exoticPerkHash: number[]; + tuningStatHash?: number; // for armor 3.0, this is the tuning stat hash } diff --git a/src/app/services/bungie-api.service.ts b/src/app/services/bungie-api.service.ts index 3911ccec..029bce45 100644 --- a/src/app/services/bungie-api.service.ts +++ b/src/app/services/bungie-api.service.ts @@ -320,6 +320,7 @@ export class BungieApiService { DestinyComponentType.ItemPerks, DestinyComponentType.ItemSockets, DestinyComponentType.ItemPlugStates, + DestinyComponentType.ItemReusablePlugs, DestinyComponentType.Collectibles, ], membershipType: destinyMembership.membershipType, @@ -410,6 +411,28 @@ export class BungieApiService { if (!!(instance as any).gearTier) { armorItem.armorSystem = ArmorSystem.Armor3; armorItem.tier = (instance as any).gearTier; + + // Grab the tuning stat from the reusable plugs + try { + const plugs = + profile.Response.itemComponents.reusablePlugs.data?.[d.itemInstanceId!]?.plugs; + if (plugs) { + // TODO: remove the hardcoding of 11 and 2 + const modCheck = plugs[11][2].plugItemHash; + plugs[11][2].plugItemHash; + // Find the index of the first investment stat with value > 0 + const tuningStat = modsMap[modCheck].investmentStats.find( + (p) => p.value > 0 + )?.statTypeHash; + armorItem.tuningStatHash = tuningStat; + } + } catch (e) { + this.logger.error( + "BungieApiService", + "updateArmorItems", + `Error while getting tuning stat for item ${d.itemInstanceId}: ${e}` + ); + } } else if (armorItem.isExotic && armorItem.slot === ArmorSlot.ArmorSlotClass) { armorItem.armorSystem = ArmorSystem.Armor3; } else { diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index 0d968a0e..e4bcd8ef 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -462,6 +462,7 @@ export class InventoryService { exoticPerkHash: armor.exoticPerkHash, gearSetHash: armor.gearSetHash ?? null, + tuningStatHash: armor.tuningStatHash, icon: armor.icon, watermarkIcon: armor.watermarkIcon, From 9554a8311962e52e91b9b3235951630412c2b1c2 Mon Sep 17 00:00:00 2001 From: Markus Date: Wed, 20 Aug 2025 14:45:49 +0000 Subject: [PATCH 03/84] feat: initial progress for the new tuning --- src/app/data/types/IPermutatorArmorSet.ts | 7 +- src/app/services/results-builder.worker.ts | 296 ++++++++++++++++++--- 2 files changed, 262 insertions(+), 41 deletions(-) diff --git a/src/app/data/types/IPermutatorArmorSet.ts b/src/app/data/types/IPermutatorArmorSet.ts index f86e2cea..4fba8efe 100644 --- a/src/app/data/types/IPermutatorArmorSet.ts +++ b/src/app/data/types/IPermutatorArmorSet.ts @@ -1,6 +1,6 @@ import { StatModifier } from "../enum/armor-stat"; import { IPermutatorArmor } from "./IPermutatorArmor"; - +export type Tuning = [number, number, number, number, number, number]; export interface IPermutatorArmorSet { armor: number[]; useExoticClassItem: boolean; @@ -8,6 +8,7 @@ export interface IPermutatorArmorSet { usedMods: StatModifier[]; statsWithMods: number[]; statsWithoutMods: number[]; + tuning: Tuning; } export function createArmorSet( @@ -19,7 +20,8 @@ export function createArmorSet( usedArtifice: StatModifier[], usedMods: StatModifier[], statsWithMods: number[], - statsWithoutMods: number[] + statsWithoutMods: number[], + tuning: Tuning ): IPermutatorArmorSet { return { armor: [helmet.id, gauntlet.id, chest.id, leg.id, classItem.id], @@ -28,6 +30,7 @@ export function createArmorSet( usedMods, statsWithMods, statsWithoutMods, + tuning: tuning, }; } diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 88d68f74..200ed460 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -36,13 +36,21 @@ import { ModOptimizationStrategy } from "../data/enum/mod-optimization-strategy" import { IPermutatorArmor } from "../data/types/IPermutatorArmor"; import { IPermutatorArmorSet, + Tuning, createArmorSet, isIPermutatorArmorSet, } from "../data/types/IPermutatorArmorSet"; import { ArmorSystem } from "../data/types/IManifestArmor"; -import { precalculatedModCombinations } from "../data/generated/precalculatedModCombinations"; + +import { precalculatedTuningModCombinations } from "../data/generated/precalculatedModCombinationsWithTunings"; + // endregion Imports +type t5Improvement = { + tuningStatHash: ArmorStat; + archetypeStats: ArmorStat[]; +}; + // region Validation and Preparation Functions function checkSlots( config: BuildConfiguration, @@ -544,6 +552,61 @@ function applyMasterworkStats( } } +function generate_tunings(possibleImprovements: t5Improvement[]): Tuning[] { + const impValues = possibleImprovements.map((imp) => { + let l = [[0, 0, 0, 0, 0, 0]]; + + let ooo = [0, 0, 0, 0, 0, 0]; + for (let n = 0; n < 6; n++) { + if (!imp.archetypeStats.includes(n)) ooo[n] = 1; + + if (n == imp.tuningStatHash) continue; + let p = [0, 0, 0, 0, 0, 0]; + p[imp.tuningStatHash] = 5; + p[n] = -5; + l.push(p); + } + + return l; + }); + const tunings: Tuning[] = []; + + const seen = new Set(); + + function addUniqueTuning(tuning: Tuning) { + const key = tuning.join(","); + if (!seen.has(key)) { + tunings.push(tuning as Tuning); + seen.add(key); + } + } + + if (impValues.length === 0) { + addUniqueTuning([0, 0, 0, 0, 0, 0]); + } else { + function recurse(idx: number, acc: number[]) { + if (idx === impValues.length) { + addUniqueTuning(acc as Tuning); + return; + } + for (const v of impValues[idx]) { + const next = [ + acc[0] + v[0], + acc[1] + v[1], + acc[2] + v[2], + acc[3] + v[3], + acc[4] + v[4], + acc[5] + v[5], + ]; + recurse(idx + 1, next); + } + } + recurse(0, [0, 0, 0, 0, 0, 0]); + } + + return tunings; +} + export function handlePermutation( runtime: any, config: BuildConfiguration, @@ -561,6 +624,15 @@ export function handlePermutation( for (let item of items) applyMasterworkStats(item, config, stats); + const targetStats = [ + config.minimumStatTiers[0].value * 10, + config.minimumStatTiers[1].value * 10, + config.minimumStatTiers[2].value * 10, + config.minimumStatTiers[3].value * 10, + config.minimumStatTiers[4].value * 10, + config.minimumStatTiers[5].value * 10, + ]; + const statsWithoutMods = [stats[0], stats[1], stats[2], stats[3], stats[4], stats[5]]; stats[0] += constantBonus[0]; stats[1] += constantBonus[1]; @@ -691,6 +763,22 @@ export function handlePermutation( ]; applyMasterworkStats(classItem, config, adjustedStatsWithoutMods); + const possibleT5Improvements: t5Improvement[] = [helmet, gauntlet, chest, leg, classItem] + //.filter((i) => i.armorSystem == ArmorSystem.Armor3 && i.tier > 0 && i.archetypeStats) + .filter( + (i) => + i.armorSystem == ArmorSystem.Armor3 && + i.tier == 5 && + i.archetypeStats && + i.tuningStatHash !== undefined + ) + .map((i) => ({ + tuningStatHash: i.tuningStatHash!, + archetypeStats: i.archetypeStats, + })); + + const availableTunings: Tuning[] = generate_tunings(possibleT5Improvements); + // Recalculate distances with class item included const newDistances = [ Math.max(0, config.minimumStatTiers[0].value * 10 - adjustedStats[0]), @@ -731,17 +819,21 @@ export function handlePermutation( newDistances[5]; const newTotalOptionalDistances = newOptionalDistances.reduce((a, b) => a + b, 0); - if (newDistanceSum > 10 * 5 + 3 * tmpArtificeCount) continue; + if (newDistanceSum > 10 * 5 + 5 * availableTunings.length) continue; - let result: StatModifier[] | null; - if (newDistanceSum == 0 && newTotalOptionalDistances == 0) result = []; + let result: StatModifierPrecalc | null; + if (newDistanceSum == 0 && newTotalOptionalDistances == 0) + result = { mods: [], tuning: [0, 0, 0, 0, 0, 0] }; else result = get_mods_precalc( + adjustedStats, + targetStats, config, newDistances, newOptionalDistances, tmpArtificeCount, - config.modOptimizationStrategy + config.modOptimizationStrategy, + availableTunings ); if (result !== null) { @@ -750,8 +842,10 @@ export function handlePermutation( runtime, config, adjustedStats, + targetStats, newDistances, - tmpArtificeCount + tmpArtificeCount, + availableTunings ); // This may lead to issues later. @@ -785,15 +879,37 @@ function performTierAvailabilityTesting( runtime: any, config: BuildConfiguration, stats: number[], + targetStats: number[], distances: number[], - availableArtificeCount: number + availableArtificeCount: number, + availableTunings: Tuning[] ): void { for (let stat = 0; stat < 6; stat++) { - if (runtime.maximumPossibleTiers[stat] < stats[stat]) { - runtime.maximumPossibleTiers[stat] = stats[stat]; + const minimumTuning = availableTunings.map((t) => t[stat]).reduce((a, b) => Math.min(a, b), 0); + const minStat = stats[stat]; + + const tmpTunings = availableTunings.slice().sort((a, b) => { + const aVal = a[stat]; + const bVal = b[stat]; + const aNeg = aVal < 0; + const bNeg = bVal < 0; + if (aNeg && bNeg) { + // Both negative: sort descending + return bVal - aVal; + } else if (!aNeg && !bNeg) { + // Both zero or positive: sort ascending + return aVal - bVal; + } else { + // Zero/positive first, then negative + return aNeg ? 1 : -1; + } + }); + + if (runtime.maximumPossibleTiers[stat] < stats[stat] + minimumTuning) { + runtime.maximumPossibleTiers[stat] = stats[stat] + minimumTuning; } - if (stats[stat] >= 200) continue; // Already at max value, no need to test + if (minStat >= 200) continue; // Already at max value, no need to test const minTier = config.minimumStatTiers[stat as ArmorStat].value * 10; @@ -801,34 +917,44 @@ function performTierAvailabilityTesting( let low = Math.max(runtime.maximumPossibleTiers[stat], minTier); let high = 200; - while (low < high) { + while (low <= high) { // Try middle value, rounded to nearest 10 for tier optimization const mid = Math.min(200, Math.ceil((low + high) / 2)); - if (stats[stat] >= mid) { + if (minStat >= mid && minimumTuning == 0) { // We can already reach this value naturally low = mid + 1; continue; } // Calculate distance needed to reach this value - const v = 10 - (stats[stat] % 10); const testDistances = [...distances]; - testDistances[stat] = Math.max(v < 10 ? v : 0, mid - stats[stat]); + testDistances[stat] = Math.max(0, mid - minStat); // Check if this value is achievable with mods const mods = get_mods_precalc( + stats, + targetStats, config, testDistances, [0, 0, 0, 0, 0, 0], availableArtificeCount, - ModOptimizationStrategy.None + ModOptimizationStrategy.None, + tmpTunings ); if (mods != null) { // This value is achievable, try higher - low = mid + 1; - runtime.maximumPossibleTiers[stat] = mid; + if (mods?.tuning[stat] >= 0) { + low = mid + 1; + runtime.maximumPossibleTiers[stat] = mid; + } else { + runtime.maximumPossibleTiers[stat] = Math.max( + mid + mods?.tuning[stat], + runtime.maximumPossibleTiers[stat] + ); + low = Math.max(mid + mods?.tuning[stat], low) + 1; + } } else { // This value is not achievable, try lower high = mid - 1; @@ -837,15 +963,17 @@ function performTierAvailabilityTesting( // Verify the final value if (low > runtime.maximumPossibleTiers[stat] && low <= 200) { - const v = 10 - (stats[stat] % 10); const testDistances = [...distances]; - testDistances[stat] = Math.max(v < 10 ? v : 0, low - stats[stat]); + testDistances[stat] = Math.max(low - minStat, 0); const mods = get_mods_precalc( + stats, + targetStats, config, testDistances, [0, 0, 0, 0, 0, 0], availableArtificeCount, - ModOptimizationStrategy.None + ModOptimizationStrategy.None, + tmpTunings ); if (mods != null) { runtime.maximumPossibleTiers[stat] = low; @@ -862,7 +990,7 @@ function tryCreateArmorSetWithClassItem( chest: IPermutatorArmor, leg: IPermutatorArmor, classItem: IPermutatorArmor, - result: StatModifier[], + result: StatModifierPrecalc, adjustedStats: number[], statsWithoutMods: number[], newDistances: number[], @@ -871,16 +999,18 @@ function tryCreateArmorSetWithClassItem( ): IPermutatorArmorSet | never[] { if (doNotOutput) return []; - const usedArtifice = result.filter((d: StatModifier) => 0 == d % 3); - const usedMods = result.filter((d: StatModifier) => 0 != d % 3); + const usedArtifice = result.mods.filter((d: StatModifier) => 0 == d % 3); + const usedMods = result.mods.filter((d: StatModifier) => 0 != d % 3); // Apply mods to stats for final calculation const finalStats = [...adjustedStats]; - for (let statModifier of result) { + for (let statModifier of result.mods) { const stat = Math.floor((statModifier - 1) / 3); finalStats[stat] += STAT_MOD_VALUES[statModifier][1]; } + for (let n = 0; n < 6; n++) finalStats[n] += result.tuning[n]; + const waste1 = getWaste(finalStats); if (config.onlyShowResultsWithNoWastedStats && waste1 > 0) return []; @@ -893,28 +1023,83 @@ function tryCreateArmorSetWithClassItem( usedArtifice, usedMods, finalStats, - statsWithoutMods + statsWithoutMods, + result.tuning ); } // region Mod Calculation Functions function get_mods_recursive( + currentStats: number[], + targetStats: number[], + distances_to_check: number[], + availableTunings: Tuning[], + statIdx: number, availableArtificeCount: number, availableMajorMods: number, availableMods: number ): number[][] | null { - if (distances_to_check.length == 0) { - return []; + if (statIdx > 5) { + // Now we have a valid set of mods and tunings, but we still have to check -5 values. This will happen in innermost loop + // statIdx is no longer useful here + + // 1. If there is any tuning with no negative in any value, then return [] + // if (availableTunings.some(tuning => tuning.every(v => v >= 0))) { + // return []; + // } + + // Now there are only tunings with negative values left. + // 2.1 If there is any stat where (currentStat - tuningValue) >= target value, then return + outer: for (let tuning of availableTunings) { + for (let i = 0; i < 6; i++) { + if (tuning[i] >= 0) continue; + if (currentStats[i] + tuning[i] < targetStats[i]) continue outer; + } + return [tuning]; + } + + // 2.2 if we still have a few mods left, we can simply call the recursion again, but with the new "temp" stats + if (availableMods > 0) { + for (let tuning of availableTunings) { + const newStats = currentStats.map((s, i) => s + tuning[i]); + const newDists = distances_to_check.map((d, i) => + Math.max(0, targetStats[i] - newStats[i]) + ); + const otherMods = get_mods_recursive( + newStats, + targetStats, + newDists, + [], + 0, + availableArtificeCount, + availableMajorMods, + availableMods + ); + if (otherMods !== null) { + return [...otherMods, tuning]; + } + } + } + + return null; } - const distance = distances_to_check[0]; - let precalculatedMods = precalculatedModCombinations[distance] || [[0, 0, 0, 0, 0, 0]]; - // let precalculatedMods = precalculatedTuningModCombinations[0] + + const maxValueOfAvailableTunings = availableTunings.reduce( + (max, tuning) => Math.max(max, tuning[statIdx]), + 0 + ); + + const distance = distances_to_check[statIdx]; + + //let precalculatedMods = precalculatedModCombinations[distance] || [[0, 0, 0, 0, 0, 0]]; + let precalculatedMods = precalculatedTuningModCombinations[distance] || [[0, 0, 0, 0, 0, 0]]; precalculatedMods = precalculatedMods.filter( (mod) => mod[0] <= availableArtificeCount && mod[2] <= availableMajorMods && - mod[2] + mod[1] <= availableMods + mod[2] + mod[1] <= availableMods && + mod[3] <= maxValueOfAvailableTunings ); if (precalculatedMods.length == 0) { @@ -925,8 +1110,26 @@ function get_mods_recursive( const totalMods = Math.max(0, availableMods - pickedMod[1] - pickedMod[2]); const majorMods = Math.min(totalMods, Math.max(0, availableMajorMods - pickedMod[2])); const artifice = Math.max(0, availableArtificeCount - pickedMod[0]); + + let selectedTuningsInner = availableTunings; + const requiredTuningCount = pickedMod[4]; + const requiredTuningValue = pickedMod[3]; + if (requiredTuningCount > 0) { + selectedTuningsInner = availableTunings.filter( + (tuning) => tuning[statIdx] >= requiredTuningValue + ); + if (selectedTuningsInner.length == 0) { + continue; + // return null; // we could also return, if the table is sorted ascending to tuningCount + } + } + const otherMods = get_mods_recursive( - distances_to_check.slice(1), + currentStats, + targetStats, + distances_to_check, //.slice(1), + selectedTuningsInner, + statIdx + 1, artifice, majorMods, totalMods @@ -938,25 +1141,36 @@ function get_mods_recursive( return null; } +type StatModifierPrecalc = { + mods: StatModifier[]; + tuning: Tuning; +}; + function get_mods_precalc( + currentStats: number[], + targetStats: number[], config: BuildConfiguration, distances: number[], optionalDistances: number[], availableArtificeCount: number, - optimize: ModOptimizationStrategy = ModOptimizationStrategy.None -): StatModifier[] | null { - // check distances <= 65 + optimize: ModOptimizationStrategy = ModOptimizationStrategy.None, + availableTunings: Tuning[] +): StatModifierPrecalc | null { const totalDistance = distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; - if (totalDistance > 65) return null; + if (totalDistance > 75) return null; if (totalDistance == 0 && optionalDistances.every((d) => d == 0)) { // no mods needed, return empty array - return []; + return { mods: [], tuning: [0, 0, 0, 0, 0, 0] }; } let pickedMods = get_mods_recursive( + currentStats, + targetStats, distances, + availableTunings, + 0, availableArtificeCount, config.statModLimits.maxMajorMods, config.statModLimits.maxMods @@ -965,13 +1179,17 @@ function get_mods_precalc( if (pickedMods === null) return null; const usedMods = []; - for (let i = 0; i < pickedMods.length; i++) { + // The last entry is always the tuning + for (let i = 0; i < pickedMods.length - 1; i++) { for (let n = 0; n < pickedMods[i][0]; n++) usedMods.push(3 + 3 * i); for (let n = 0; n < pickedMods[i][1]; n++) usedMods.push(1 + 3 * i); for (let n = 0; n < pickedMods[i][2]; n++) usedMods.push(2 + 3 * i); } - return usedMods; + return { + mods: usedMods, + tuning: pickedMods[pickedMods.length - 1] as Tuning, + }; } export function getSkillTier(stats: number[]) { From fb030aa71da8280a09e1f6de5ee34e0e1bb2cad5 Mon Sep 17 00:00:00 2001 From: Mijago Date: Fri, 8 Aug 2025 18:36:53 +0200 Subject: [PATCH 04/84] fix: exotic artifice slot --- src/app/services/bungie-api.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/services/bungie-api.service.ts b/src/app/services/bungie-api.service.ts index 029bce45..bec5f92b 100644 --- a/src/app/services/bungie-api.service.ts +++ b/src/app/services/bungie-api.service.ts @@ -492,7 +492,6 @@ export class BungieApiService { ); const investmentStat = getInvestmentStats(armorItem); - // TODO: This must be tiered investmentStat[thirdHighestStatHash] += 13; applyInvestmentStats(armorItem, investmentStat); } @@ -521,12 +520,13 @@ export class BungieApiService { let perks = (statData[d.itemInstanceId || ""] || {})["perks"] || []; const hasPerk = perks.filter((p) => p.perkHash == 229248542).length > 0; if (!hasPerk) armorItem.perk = ArmorPerkOrSlot.None; - } else if (armorItem.isExotic && armorItem.slot !== ArmorSlot.ArmorSlotClass) { - // 720825311 is "UNLOCKED exotic artifice slot" - // 1656746282 is "LOCKED exotic artifice slot" - const hasPerk = socketsList.filter((d) => d == 720825311).length > 0; - if (hasPerk) { - armorItem.perk = ArmorPerkOrSlot.SlotArtifice; + if (armorItem.isExotic && armorItem.slot !== ArmorSlot.ArmorSlotClass) { + // 720825311 is "UNLOCKED exotic artifice slot" + // 1656746282 is "LOCKED exotic artifice slot" + const hasPerk = socketsList.filter((d) => d == 720825311).length > 0; + if (hasPerk) { + armorItem.perk = ArmorPerkOrSlot.SlotArtifice; + } } } From 32c54d671a9580ec6cd8282cac4b7486373f0bcb Mon Sep 17 00:00:00 2001 From: Markus Date: Wed, 20 Aug 2025 15:07:37 +0000 Subject: [PATCH 05/84] feat: update armor tuning stat handling to use ArmorStat type --- src/app/data/enum/armor-stat.ts | 9 ++++++++ src/app/data/types/IInventoryArmor.ts | 2 +- src/app/data/types/IPermutatorArmor.ts | 4 ++-- src/app/services/bungie-api.service.ts | 24 ++++++++++++++-------- src/app/services/inventory.service.ts | 2 +- src/app/services/results-builder.worker.ts | 10 ++++----- 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/app/data/enum/armor-stat.ts b/src/app/data/enum/armor-stat.ts index 4252ae18..a5e16cf2 100644 --- a/src/app/data/enum/armor-stat.ts +++ b/src/app/data/enum/armor-stat.ts @@ -244,6 +244,15 @@ export const MapAlternativeToArmorPerkOrSlot: EnumDictionary = { + [ArmorStatHashes[ArmorStat.StatWeapon]]: ArmorStat.StatWeapon, + [ArmorStatHashes[ArmorStat.StatHealth]]: ArmorStat.StatHealth, + [ArmorStatHashes[ArmorStat.StatClass]]: ArmorStat.StatClass, + [ArmorStatHashes[ArmorStat.StatGrenade]]: ArmorStat.StatGrenade, + [ArmorStatHashes[ArmorStat.StatSuper]]: ArmorStat.StatSuper, + [ArmorStatHashes[ArmorStat.StatMelee]]: ArmorStat.StatMelee, +}; + export const MapAlternativeSocketTypeToArmorPerkOrSlot: EnumDictionary = { [1719555937]: ArmorPerkOrSlot.SlotArtifice, [2770223926]: ArmorPerkOrSlot.SlotArtifice, diff --git a/src/app/data/types/IInventoryArmor.ts b/src/app/data/types/IInventoryArmor.ts index 262bb2c0..8a6be06e 100644 --- a/src/app/data/types/IInventoryArmor.ts +++ b/src/app/data/types/IInventoryArmor.ts @@ -63,7 +63,7 @@ export interface IInventoryArmor ITimestampedEntry { // Note: this will be empty for vendor items statPlugHashes?: (number | undefined)[]; - tuningStatHash?: number; // for armor 3.0, this is the tuning stat hash + tuningStat?: ArmorStat; // for armor 3.0, this is the tuning stat hash // exoticPerkHash is now inherited as number[] from IManifestArmor } diff --git a/src/app/data/types/IPermutatorArmor.ts b/src/app/data/types/IPermutatorArmor.ts index a5a20f4a..d71af71c 100644 --- a/src/app/data/types/IPermutatorArmor.ts +++ b/src/app/data/types/IPermutatorArmor.ts @@ -1,5 +1,5 @@ import { DestinyClass, TierType } from "bungie-api-ts/destiny2"; -import { ArmorPerkOrSlot } from "../enum/armor-stat"; +import { ArmorPerkOrSlot, ArmorStat } from "../enum/armor-stat"; import { IDestinyArmor } from "./IInventoryArmor"; export interface IPermutatorArmor extends IDestinyArmor { @@ -9,5 +9,5 @@ export interface IPermutatorArmor extends IDestinyArmor { rarity: TierType; isSunset: boolean; exoticPerkHash: number[]; - tuningStatHash?: number; // for armor 3.0, this is the tuning stat hash + tuningStat?: ArmorStat; // for armor 3.0, this is the tuning stat hash } diff --git a/src/app/services/bungie-api.service.ts b/src/app/services/bungie-api.service.ts index bec5f92b..4a684002 100644 --- a/src/app/services/bungie-api.service.ts +++ b/src/app/services/bungie-api.service.ts @@ -51,6 +51,7 @@ import { ArmorPerkOrSlot, ArmorPerkSocketHashes, ArmorStat, + ArmorStatFromHash, ArmorStatHashes, MapAlternativeSocketTypeToArmorPerkOrSlot, MapAlternativeToArmorPerkOrSlot, @@ -417,14 +418,21 @@ export class BungieApiService { const plugs = profile.Response.itemComponents.reusablePlugs.data?.[d.itemInstanceId!]?.plugs; if (plugs) { - // TODO: remove the hardcoding of 11 and 2 - const modCheck = plugs[11][2].plugItemHash; - plugs[11][2].plugItemHash; - // Find the index of the first investment stat with value > 0 - const tuningStat = modsMap[modCheck].investmentStats.find( - (p) => p.value > 0 - )?.statTypeHash; - armorItem.tuningStatHash = tuningStat; + const availablePlugs = Object.values(plugs).find((value) => { + return value.length > 1 && value.some((p) => p.plugItemHash == 3122197216); // 3122197216 is the balanced tuning stat + }); + + if (availablePlugs && availablePlugs.length > 1) { + const pickedPlug = availablePlugs.find((p) => p.plugItemHash != 3122197216); + if (pickedPlug) { + const statCheckHash = pickedPlug.plugItemHash; + const mod = modsMap[statCheckHash]; + const tuningStatHash = mod?.investmentStats.find( + (p) => p.value > 0 + )?.statTypeHash; + if (tuningStatHash) armorItem.tuningStat = ArmorStatFromHash[tuningStatHash]; + } + } } } catch (e) { this.logger.error( diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index e4bcd8ef..23584401 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -462,7 +462,7 @@ export class InventoryService { exoticPerkHash: armor.exoticPerkHash, gearSetHash: armor.gearSetHash ?? null, - tuningStatHash: armor.tuningStatHash, + tuningStat: armor.tuningStat, icon: armor.icon, watermarkIcon: armor.watermarkIcon, diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 200ed460..918e3609 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -47,7 +47,7 @@ import { precalculatedTuningModCombinations } from "../data/generated/precalcula // endregion Imports type t5Improvement = { - tuningStatHash: ArmorStat; + tuningStat: ArmorStat; archetypeStats: ArmorStat[]; }; @@ -560,9 +560,9 @@ function generate_tunings(possibleImprovements: t5Improvement[]): Tuning[] { for (let n = 0; n < 6; n++) { if (!imp.archetypeStats.includes(n)) ooo[n] = 1; - if (n == imp.tuningStatHash) continue; + if (n == imp.tuningStat) continue; let p = [0, 0, 0, 0, 0, 0]; - p[imp.tuningStatHash] = 5; + p[imp.tuningStat] = 5; p[n] = -5; l.push(p); } @@ -770,10 +770,10 @@ export function handlePermutation( i.armorSystem == ArmorSystem.Armor3 && i.tier == 5 && i.archetypeStats && - i.tuningStatHash !== undefined + i.tuningStat !== undefined ) .map((i) => ({ - tuningStatHash: i.tuningStatHash!, + tuningStat: i.tuningStat!, archetypeStats: i.archetypeStats, })); From 4fe0a78e4e8cc1ac9399eb9927f3ddb85c5b74a9 Mon Sep 17 00:00:00 2001 From: Mijago Date: Fri, 8 Aug 2025 18:08:41 +0200 Subject: [PATCH 06/84] feat: add tuning affinity display to result table --- .../expanded-result-content.component.html | 8 ++++++++ .../authenticated-v2/results/results.component.ts | 1 + src/app/services/inventory.service.ts | 1 + 3 files changed, 10 insertions(+) diff --git a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html index f6a81c4b..bf11b0e7 100644 --- a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html +++ b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html @@ -102,6 +102,14 @@ {{ i.masterworkLevel }} + + + + + + + + + + + + + + - If you experience issues with the local database - for example, exotics not being found, try - this:
- - Delete Database
- If you really want to reset EVERYTHING, use this:
- - Reset the whole application - + +
+

Database Management

+

+ Use these options if you experience database issues such as missing exotics or corrupted + data. +

+
+ +
+
+ + +
+

Complete Reset

+

+ Warning: This will completely reset the application, removing all + settings, builds, and data. +

+
+ +
+
diff --git a/src/app/components/authenticated-v2/subpages/account-config-page/account-config-page.component.ts b/src/app/components/authenticated-v2/subpages/account-config-page/account-config-page.component.ts index f04eb965..49e190f9 100644 --- a/src/app/components/authenticated-v2/subpages/account-config-page/account-config-page.component.ts +++ b/src/app/components/authenticated-v2/subpages/account-config-page/account-config-page.component.ts @@ -20,6 +20,7 @@ import { DatabaseService } from "../../../../services/database.service"; import { UserInformationService } from "src/app/services/user-information.service"; import { AuthService } from "../../../../services/auth.service"; import { Router } from "@angular/router"; +import { environment } from "../../../../../environments/environment"; @Component({ selector: "app-account-config-page", @@ -27,6 +28,8 @@ import { Router } from "@angular/router"; styleUrls: ["./account-config-page.component.css"], }) export class AccountConfigPageComponent { + isDevEnvironment = !environment.production && !environment.beta && !environment.canary; + constructor( private router: Router, private db: DatabaseService, @@ -61,6 +64,82 @@ export class AccountConfigPageComponent { await this.inv.refreshManifestAndInventory(true, true); } + async downloadSystemInformation() { + // Get localStorage key names + const localStorageKeys = Object.keys(localStorage); + + // Get database table counts + const tableCounts = { + manifestArmor: await this.db.manifestArmor.count(), + inventoryArmor: await this.db.inventoryArmor.count(), + equipableItemSetDefinition: await this.db.equipableItemSetDefinition.count(), + sandboxPerkDefinition: await this.db.sandboxPerkDefinition.count(), + sandboxAbilities: await this.db.sandboxAbilities.count(), + manifestCollectibles: await this.db.manifestCollectibles.count(), + vendorNames: await this.db.vendorNames.count(), + vendorItemSubscreen: await this.db.vendorItemSubscreen.count(), + }; + + const systemInfo = { + timestamp: new Date().toISOString(), + localStorage: { + keyCount: localStorageKeys.length, + keys: localStorageKeys.sort(), + }, + database: { + name: this.db.name, + tables: tableCounts, + }, + }; + + const url = window.URL.createObjectURL(new Blob([JSON.stringify(systemInfo, null, 2)])); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", "d2ap_system_info.json"); + document.body.appendChild(link); + link.click(); + } + + async importArmorData(event: Event) { + const input = event.target as HTMLInputElement; + if (!input.files || input.files.length === 0) { + return; + } + + const file = input.files[0]; + if (!file.name.endsWith(".json")) { + alert("Please select a JSON file."); + return; + } + + try { + const text = await file.text(); + const armorData = JSON.parse(text); + + if (!Array.isArray(armorData)) { + alert("Invalid file format. Expected an array of armor items."); + return; + } + + // Clear existing armor data and replace with imported data + await this.db.inventoryArmor.clear(); + await this.db.inventoryArmor.bulkPut(armorData); + + alert(`Successfully imported ${armorData.length} armor items.`); + + // Clear the input so the same file can be selected again + input.value = ""; + } catch (error) { + console.error("Error importing armor data:", error); + alert("Error importing file. Please check that it's a valid d2ap_armor.json file."); + } + } + + triggerFileInput() { + const fileInput = document.getElementById("armorFileInput") as HTMLInputElement; + fileInput?.click(); + } + async resetEverything() { localStorage.clear(); await this.db.resetDatabase(); From a5aecfbc448d9a6fbbc591a28aed38ebae33ad06 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 21 Feb 2026 03:53:25 -0600 Subject: [PATCH 75/84] feat: better calculations for number of threads --- src/app/data/commonFunctions.ts | 21 +++++++++++++++ src/app/services/armor-calculator.service.ts | 15 +++++------ src/app/services/results-builder.worker.ts | 28 ++++++++++++-------- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/app/data/commonFunctions.ts b/src/app/data/commonFunctions.ts index ddec7e94..683eed5c 100644 --- a/src/app/data/commonFunctions.ts +++ b/src/app/data/commonFunctions.ts @@ -1,5 +1,26 @@ import { isEqual as _isEqual } from "lodash"; +/** + * Detects whether the current device is likely a mobile / tablet. + * Uses the User-Agent Client Hints API when available, then falls + * back to the legacy `userAgent` string. + */ +function isMobileDevice(): boolean { + // Modern: UA Client Hints (Chromium 89+) + if ((navigator as any).userAgentData?.mobile) { + return true; + } + // Legacy fallback + return /Android|iPhone|iPad|iPod|Mobile|Tablet/i.test(navigator.userAgent); +} + +export function calculateCPUConcurrency(): number { + const logicalCores = navigator?.hardwareConcurrency || 4; + const estimatedPhysicalCores = isMobileDevice() ? logicalCores : Math.ceil(logicalCores / 2); + // Reserve at least 1 core for the main thread, but ensure we use at least 3 cores for calculations on desktop + return Math.max(3, estimatedPhysicalCores - 1); +} + export function getDifferences( object1: T, object2: T diff --git a/src/app/services/armor-calculator.service.ts b/src/app/services/armor-calculator.service.ts index fe4a2e3c..188a9e1a 100644 --- a/src/app/services/armor-calculator.service.ts +++ b/src/app/services/armor-calculator.service.ts @@ -42,6 +42,7 @@ import { IPermutatorArmorSet } from "../data/types/IPermutatorArmorSet"; import { getSkillTier, getWaste } from "./results-builder.worker"; import { IPermutatorArmor } from "../data/types/IPermutatorArmor"; import { FORCE_USE_NO_EXOTIC, MAXIMUM_MASTERWORK_LEVEL } from "../data/constants"; +import { calculateCPUConcurrency } from "../data/commonFunctions"; import { ModOptimizationStrategy } from "../data/enum/mod-optimization-strategy"; import { ArmorSystem } from "../data/types/IManifestArmor"; import { combineLatest, Subscription } from "rxjs"; @@ -372,15 +373,11 @@ export class ArmorCalculatorService implements OnDestroy { let minimumCalculationPerThread = calculationMultiplier * 5e4; let maximumCalculationPerThread = calculationMultiplier * 2.5e5; - const nthreads = Math.max( - 3, // Enforce a minimum of 3 threads - Math.min( - Math.max(1, Math.ceil(estimatedCalculations / minimumCalculationPerThread)), - Math.ceil(estimatedCalculations / maximumCalculationPerThread), - Math.floor((navigator.hardwareConcurrency || 2) * 0.75), // limit it to the amount of cores, and only use 75% - 20, // limit it to a maximum of 20 threads - largestArmorBucket // limit it to the largest armor bucket, as we will split the work by this value - ) + const nthreads = Math.min( + Math.max(1, Math.ceil(estimatedCalculations / minimumCalculationPerThread)), + Math.ceil(estimatedCalculations / maximumCalculationPerThread), + calculateCPUConcurrency(), // estimated physical cores minus 1, minimum of 3 for desktop + largestArmorBucket // limit it to the largest armor bucket, as we will split the work by this value ); return nthreads; diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index dc6d4c66..73dddbc2 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -539,7 +539,7 @@ addEventListener("message", async ({ data }) => { let lastProgressReportTime = 0; // define the delay; it can be 75ms if the estimated calculations are low // if the estimated calculations >= 1e6, then we will use 125ms - let progressBarDelay = estimatedCalculations >= 1e6 ? 125 : 75; + let progressBarDelay = estimatedCalculations >= 1e6 ? 500 : 125; for (let [helmet, gauntlet, chest, leg] of generateArmorCombinations( helmets, @@ -620,8 +620,11 @@ addEventListener("message", async ({ data }) => { postMessage({ runtime, results, done: false, checkedCalculations, estimatedCalculations }); results = []; resultsLength = 0; - } else if (lastProgressReportTime + progressBarDelay < Date.now()) { - lastProgressReportTime = Date.now(); + } else if ( + resultsLength > 100 && + lastProgressReportTime + progressBarDelay < performance.now() + ) { + lastProgressReportTime = performance.now(); postMessage({ checkedCalculations, estimatedCalculations, @@ -885,7 +888,7 @@ export function handlePermutation( let finalResult: IPermutatorArmorSet | never[] = []; let checkedClassItems = 0; - classItemLoop: for (const classItem of sortedClassItems) { + for (const classItem of sortedClassItems) { checkedClassItems++; // compute adjustedStats @@ -970,8 +973,8 @@ export function handlePermutation( newDistanceSum > 10 * 5 + 3 * availableArtificeCount + 5 * (baseT5Improvements.length + (classItemT5 ? 1 : 0)) ) { - if (config.earlyAbortClassItems && checkedClassItems >= 3) break classItemLoop; - else continue classItemLoop; + if (config.earlyAbortClassItems && checkedClassItems >= 3) break; + else continue; } let availableTunings: Tuning[] = [[0, 0, 0, 0, 0, 0]]; @@ -989,8 +992,8 @@ export function handlePermutation( } } if (!passesPerStat) { - if (config.earlyAbortClassItems && checkedClassItems >= 3) break classItemLoop; - else continue classItemLoop; + if (config.earlyAbortClassItems && checkedClassItems >= 3) break; + else continue; } if (config.calculateTierFiveTuning) { // lazy: only generate full tunings when cheaper checks pass @@ -1232,12 +1235,15 @@ function get_mods_recursive( // Now there are only tunings with negative values left. // 2.1 If there is any stat where (currentStat - tuningValue) >= target value, then return - outer: for (let tuning of availableTunings) { + const validTuning = availableTunings.find((tuning) => { for (let i = 0; i < 6; i++) { if (tuning[i] >= 0) continue; - if (currentStats[i] + tuning[i] < targetStats[i]) continue outer; + if (currentStats[i] + tuning[i] < targetStats[i]) return false; } - return [tuning]; + return true; + }); + if (validTuning) { + return [validTuning]; } // 2.2 if we still have a few mods left, we can simply call the recursion again, but with the new "temp" stats From 60059fc53af0605e18ac0c18568e77af6cc50258 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sun, 22 Feb 2026 17:31:15 -0600 Subject: [PATCH 76/84] feat: fix main page checks for calculator --- src/app/services/armor-calculator.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/services/armor-calculator.service.ts b/src/app/services/armor-calculator.service.ts index 188a9e1a..7138c52b 100644 --- a/src/app/services/armor-calculator.service.ts +++ b/src/app/services/armor-calculator.service.ts @@ -278,8 +278,13 @@ export class ArmorCalculatorService implements OnDestroy { private isMainPage(): boolean { const currentUrl = this.router.url; - // Main page is either empty path or just '/' - return currentUrl === "/" || currentUrl === "" || currentUrl.split("?")[0] === "/"; + // Main page is either empty path or just '/', check for parametrized (?) routes and (#) fragments + return ( + currentUrl === "/" || + currentUrl === "" || + currentUrl.split("?")[0] === "/" || + currentUrl.split("#")[0] === "/" + ); } private clearResults() { From 42003c0616ebeed7a0037db8a531b24cd3c2063d Mon Sep 17 00:00:00 2001 From: nznaza Date: Sun, 22 Feb 2026 17:55:38 -0600 Subject: [PATCH 77/84] feat: simplify generator logic --- .../results/results.component.html | 4 + .../results/results.component.ts | 9 + src/app/services/armor-calculator.service.ts | 18 + .../services/results-builder.worker.spec.ts | 20 +- src/app/services/results-builder.worker.ts | 724 ++++++++---------- 5 files changed, 364 insertions(+), 411 deletions(-) diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index 39b74c1b..4efdc15f 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -52,6 +52,10 @@ +
+ Combinations possible + {{ this.totalPossibleCombinations | number }} +
Time required {{ this.totalTime | number }}ms diff --git a/src/app/components/authenticated-v2/results/results.component.ts b/src/app/components/authenticated-v2/results/results.component.ts index b7a6ebf9..20aafd9e 100644 --- a/src/app/components/authenticated-v2/results/results.component.ts +++ b/src/app/components/authenticated-v2/results/results.component.ts @@ -113,6 +113,7 @@ export class ResultsComponent implements OnInit, OnDestroy { totalTime: number = 0; itemCount: number = 0; totalResults: number = 0; + totalPossibleCombinations: number = 0; parsedResults: number = 0; viewMode: "table" | "cards" = "table"; _config_legacyArmor: any; @@ -153,6 +154,14 @@ export class ResultsComponent implements OnInit, OnDestroy { this.computationProgress = progress; }); }); + + this.armorCalculator.totalPossibleCombinations + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((value) => { + this.ngZone.run(() => { + this.totalPossibleCombinations = value; + }); + }); // this.configService.configuration .pipe(takeUntil(this.ngUnsubscribe)) diff --git a/src/app/services/armor-calculator.service.ts b/src/app/services/armor-calculator.service.ts index 7138c52b..0a74d9d0 100644 --- a/src/app/services/armor-calculator.service.ts +++ b/src/app/services/armor-calculator.service.ts @@ -91,6 +91,9 @@ export class ArmorCalculatorService implements OnDestroy { private _calculationProgress: Subject = new Subject(); public readonly calculationProgress: Observable = this._calculationProgress.asObservable(); + private _totalPossibleCombinations: BehaviorSubject = new BehaviorSubject(0); + public readonly totalPossibleCombinations: Observable = + this._totalPossibleCombinations.asObservable(); private calculationSubscription?: Subscription; @@ -111,6 +114,7 @@ export class ArmorCalculatorService implements OnDestroy { // Static progress and worker state private static doneWorkerCount = 0; private static lastProgressUpdateTime = 0; + private static emittedPossibleCombinations = false; constructor( private db: DatabaseService, @@ -341,6 +345,7 @@ export class ArmorCalculatorService implements OnDestroy { this.status.modifyStatus((s) => (s.cancelledCalculation = true)); this._calculationProgress.next(0); + this._totalPossibleCombinations.next(0); this.clearResults(); } @@ -402,6 +407,17 @@ export class ArmorCalculatorService implements OnDestroy { const sumDone = ArmorCalculatorService.threadCalculationDoneArr.reduce((a, b) => a + b, 0); const sumTotal = ArmorCalculatorService.threadCalculationAmountArr.reduce((a, b) => a + b, 0); + + // Emit total possible combinations once all workers have reported their estimates + if ( + !ArmorCalculatorService.emittedPossibleCombinations && + ArmorCalculatorService.threadCalculationAmountArr + .slice(0, totalThreads) + .every((val) => val > 0) + ) { + ArmorCalculatorService.emittedPossibleCombinations = true; + this._totalPossibleCombinations.next(sumTotal); + } const minReachableTiers = ArmorCalculatorService.threadCalculationReachableTiers .reduce((minArr, currArr) => { // Using MAX would be more accurate, but using min is more visually appealing as it leads to larger jumps @@ -688,6 +704,7 @@ export class ArmorCalculatorService implements OnDestroy { return; } this.clearResults(); + this._totalPossibleCombinations.next(0); this.killWorkers(); try { @@ -741,6 +758,7 @@ export class ArmorCalculatorService implements OnDestroy { this.logger.info("ArmorCalculatorService", "updateResults", "Estimated threads: " + nthreads); // Initialize static thread tracking arrays + ArmorCalculatorService.emittedPossibleCombinations = false; ArmorCalculatorService.threadCalculationAmountArr = [...Array(nthreads).keys()].map(() => 0); ArmorCalculatorService.threadCalculationDoneArr = [...Array(nthreads).keys()].map(() => 0); ArmorCalculatorService.threadCalculationReachableTiers = [...Array(nthreads).keys()].map(() => diff --git a/src/app/services/results-builder.worker.spec.ts b/src/app/services/results-builder.worker.spec.ts index 273a6139..6612aa63 100644 --- a/src/app/services/results-builder.worker.spec.ts +++ b/src/app/services/results-builder.worker.spec.ts @@ -375,7 +375,7 @@ describe("Results Worker", () => { config.minimumStatTiers[ArmorStat.StatSuper].value = 0; config.minimumStatTiers[ArmorStat.StatMelee].value = 0; - const constantBonus = [-10, 0, 10, 0, 0, -10]; + const enabledModBonuses = [-10, 0, 10, 0, 0, -10]; let presult = handlePermutation( runtime, config, @@ -383,7 +383,7 @@ describe("Results Worker", () => { mockItems[1] as IPermutatorArmor, mockItems[2] as IPermutatorArmor, mockItems[3] as IPermutatorArmor, - constantBonus, // constant bonus + enabledModBonuses, // enabled mod bonuses [5, 5, 5, 5, 5], // availableModCost false, // doNotOutput true, // hasArtificeClassItem @@ -421,7 +421,7 @@ describe("Results Worker", () => { result.artifice.filter((mod: number) => Math.floor(mod / 3) - 1 == n && mod % 3 == 0) .length; expect(result.stats[n]).toEqual( - result.statsNoMods[n] + 5 * minor + 10 * major + 3 * artif + constantBonus[n] + result.statsNoMods[n] + 5 * minor + 10 * major + 3 * artif + enabledModBonuses[n] ); } }); @@ -523,7 +523,7 @@ describe("Results Worker", () => { config.tryLimitWastedStats = true; //config.onlyShowResultsWithNoWastedStats = true - const constantBonus1 = [0, 0, 0, 0, 0, 0]; + const enabledModBonuses1 = [0, 0, 0, 0, 0, 0]; let availableModCost = [ // random 0-5 Math.floor(Math.random() * 6), @@ -540,7 +540,7 @@ describe("Results Worker", () => { mockItems[1] as IPermutatorArmor, mockItems[2] as IPermutatorArmor, mockItems[3] as IPermutatorArmor, - constantBonus1, + enabledModBonuses1, availableModCost, false, true, // hasArtificeClassItem @@ -563,7 +563,7 @@ describe("Results Worker", () => { mockItems[1] as IPermutatorArmor, mockItems[2] as IPermutatorArmor, mockItems[3] as IPermutatorArmor, - constantBonus1, + enabledModBonuses1, availableModCost, false, true, // hasArtificeClassItem @@ -673,8 +673,8 @@ describe("Results Worker", () => { ]; console.log("statSum", statSum); - //const constantBonus = [-10, -10, -10, -10, -10, -10]; - const constantBonus = [0, 0, 0, 0, 0, 0]; + //const enabledModBonuses = [-10, -10, -10, -10, -10, -10]; + const enabledModBonuses = [0, 0, 0, 0, 0, 0]; let presult = handlePermutation( runtime, config, @@ -682,7 +682,7 @@ describe("Results Worker", () => { mockItems[1] as IPermutatorArmor, mockItems[2] as IPermutatorArmor, mockItems[3] as IPermutatorArmor, - constantBonus, // constant bonus + enabledModBonuses, // enabled mod bonuses [5, 5, 5, 5, 5], // availableModCost false, // doNotOutput true, // hasArtificeClassItem @@ -721,7 +721,7 @@ describe("Results Worker", () => { result.artifice.filter((mod: number) => Math.floor(mod / 3) - 1 == n && mod % 3 == 0) .length; expect(result.stats[n]).toEqual( - result.statsNoMods[n] + 5 * minor + 10 * major + 3 * artif + constantBonus[n] + result.statsNoMods[n] + 5 * minor + 10 * major + 3 * artif + enabledModBonuses[n] ); } }); diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 73dddbc2..3985e3cd 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -17,9 +17,13 @@ // region Imports import { BuildConfiguration, FixableSelection } from "../data/buildConfiguration"; -import { IDestinyArmor, InventoryArmorSource } from "../data/types/IInventoryArmor"; +import { IDestinyArmor } from "../data/types/IInventoryArmor"; import { ArmorSlot } from "../data/enum/armor-slot"; -import { FORCE_USE_ANY_EXOTIC, MAXIMUM_MASTERWORK_LEVEL } from "../data/constants"; +import { + FORCE_USE_ANY_EXOTIC, + FORCE_USE_NO_EXOTIC, + MAXIMUM_MASTERWORK_LEVEL, +} from "../data/constants"; import { ModInformation } from "../data/ModInformation"; import { ArmorPerkOrSlot, @@ -67,102 +71,20 @@ function mapItemToTuning(i: IPermutatorArmor): t5Improvement { }; } -function sortClassItemsForGaps( - classItems: IPermutatorArmor[], - config: BuildConfiguration, - distances: number[], - stats: number[] -): IPermutatorArmor[] { - return [...classItems].sort((a, b) => { - let scoreA = 0, - scoreB = 0; - - // Base tier and system bonuses - if (a.tier == 5) scoreA += 15; - if (b.tier == 5) scoreB += 15; - - // Armor 3.0 bonus (higher for newer system) - if (a.armorSystem == ArmorSystem.Armor3) scoreA += 12; - if (b.armorSystem == ArmorSystem.Armor3) scoreB += 12; - - // Artifice slot bonus - if (a.perk == ArmorPerkOrSlot.SlotArtifice) scoreA += 10; - if (b.perk == ArmorPerkOrSlot.SlotArtifice) scoreB += 10; - - if (config.calculateTierFiveTuning) { - // Tuning potential bonus - if (isT5WithTuning(a)) scoreA += 8; - if (isT5WithTuning(b)) scoreB += 8; - } - - // Masterwork potential bonus - const aMasterworkBonus = - (a.masterworkLevel ?? 0) + - (a.isExotic && config.assumeExoticsMasterworked ? 5 : 0) + - (!a.isExotic && config.assumeLegendariesMasterworked ? 5 : 0); - const bMasterworkBonus = - (b.masterworkLevel ?? 0) + - (b.isExotic && config.assumeExoticsMasterworked ? 5 : 0) + - (!b.isExotic && config.assumeLegendariesMasterworked ? 5 : 0); - scoreA += aMasterworkBonus; - scoreB += bMasterworkBonus; - - // Source preference (inventory first, then vendor/collection) - if (a.source === InventoryArmorSource.Inventory) scoreA += 5; - if (b.source === InventoryArmorSource.Inventory) scoreB += 5; - - // Stat contribution scoring with waste consideration - const aStats = [a.mobility, a.resilience, a.recovery, a.discipline, a.intellect, a.strength]; - const bStats = [b.mobility, b.resilience, b.recovery, b.discipline, b.intellect, b.strength]; - - for (let i = 0; i < 6; i++) { - if (distances[i] > 0) { - // Direct contribution to gaps - const aContribution = Math.min(distances[i], aStats[i]); - const bContribution = Math.min(distances[i], bStats[i]); - scoreA += aContribution; - scoreB += bContribution; - - // Bonus for filling large gaps - if (aContribution >= 5) scoreA += 2; - if (bContribution >= 5) scoreB += 2; - } else if (config.tryLimitWastedStats && stats[i] + aStats[i] > 200) { - // Penalty for creating waste - scoreA -= Math.max(0, stats[i] + aStats[i] - 200); - } else if (config.tryLimitWastedStats && stats[i] + bStats[i] > 200) { - scoreB -= Math.max(0, stats[i] + bStats[i] - 200); - } - } - - // Total stat sum bonus (prefer higher total stats) - const aTotalStats = aStats.reduce((sum, stat) => sum + stat, 0); - const bTotalStats = bStats.reduce((sum, stat) => sum + stat, 0); - scoreA += aTotalStats * 0.1; // Small bonus for overall stat power - scoreB += bTotalStats * 0.1; - - return scoreB - scoreA; // Higher contribution first - }); -} - // region Validation and Preparation Functions function checkSlots( config: BuildConfiguration, - constantModslotRequirement: Map, - availableClassItemTypes: Set, + requiredPerkSlotCounts: Map, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, chest: IPermutatorArmor, - leg: IPermutatorArmor -) { - let requirements = new Map(constantModslotRequirement); - const slots = [ - { slot: ArmorSlot.ArmorSlotHelmet, item: helmet }, - { slot: ArmorSlot.ArmorSlotGauntlet, item: gauntlet }, - { slot: ArmorSlot.ArmorSlotChest, item: chest }, - { slot: ArmorSlot.ArmorSlotLegs, item: leg }, - ]; + leg: IPermutatorArmor, + classItem: IPermutatorArmor +): boolean { + let requirements = new Map(requiredPerkSlotCounts); + const items = [helmet, gauntlet, chest, leg, classItem]; - for (let { item } of slots) { + for (let item of items) { if (item.armorSystem === ArmorSystem.Armor2) { if ( (item.isExotic && config.assumeEveryLegendaryIsArtifice) || @@ -190,19 +112,11 @@ function checkSlots( SlotRequirements += Math.max(0, requirements.get(key) ?? 0); } - if (SlotRequirements > 1) return { valid: false }; - if (SlotRequirements == 0) return { valid: true, requiredClassItemType: ArmorPerkOrSlot.Any }; - - const requiredClassItemPerk = [...requirements.entries()].find((c) => c[1] > 0)?.[0]; - if (!requiredClassItemPerk) return { valid: false, requiredClassItemType: ArmorPerkOrSlot.Any }; - return { - valid: availableClassItemTypes.has(requiredClassItemPerk), - requiredClassItemType: requiredClassItemPerk, - }; + return SlotRequirements === 0; } -function prepareConstantStatBonus(config: BuildConfiguration) { - const constantBonus = [0, 0, 0, 0, 0, 0]; +function computeEnabledModBonuses(config: BuildConfiguration) { + const enabledModBonuses = [0, 0, 0, 0, 0, 0]; // Apply configurated mods to the stat value // Apply mods for (const mod of config.enabledMods) { @@ -211,22 +125,22 @@ function prepareConstantStatBonus(config: BuildConfiguration) { bonus.stat == SpecialArmorStat.ClassAbilityRegenerationStat ? [1, 0, 2][config.characterClass] : bonus.stat; - constantBonus[statId] += bonus.value; + enabledModBonuses[statId] += bonus.value; } } - return constantBonus; + return enabledModBonuses; } -function prepareConstantModslotRequirement(config: BuildConfiguration) { +function calculateRequiredPerkCounts(config: BuildConfiguration) { let constantPerkRequirement = new Map(); for (let [key] of constantPerkRequirement) { constantPerkRequirement.set(key, 0); } - for (const req of config.armorRequirements) { - if ("perk" in req) { - let perk = req.perk; + for (const requirement of config.armorRequirements) { + if ("perk" in requirement) { + let perk = requirement.perk; const e = Object.entries(ArmorPerkSocketHashes).find(([, value]) => value == perk); if (e) perk = Number.parseInt(e[0]) as any as ArmorPerkOrSlot; @@ -234,11 +148,11 @@ function prepareConstantModslotRequirement(config: BuildConfiguration) { if (perk != ArmorPerkOrSlot.Any && perk != ArmorPerkOrSlot.None) { constantPerkRequirement.set(perk, (constantPerkRequirement.get(perk) ?? 0) + 1); } - } else if ("gearSetHash" in req) { + } else if ("gearSetHash" in requirement) { // Gear set requirement constantPerkRequirement.set( - req.gearSetHash, - (constantPerkRequirement.get(req.gearSetHash) ?? 0) + 1 + requirement.gearSetHash, + (constantPerkRequirement.get(requirement.gearSetHash) ?? 0) + 1 ); } } @@ -250,25 +164,68 @@ function* generateArmorCombinations( gauntlets: IPermutatorArmor[], chests: IPermutatorArmor[], legs: IPermutatorArmor[], - requiresAtLeastOneExotic: boolean + classItems: IPermutatorArmor[], + yieldExoticCombinations: boolean, + yieldAllLegendary: boolean ) { - for (let helmet of helmets) { - for (let gauntlet of gauntlets) { - if (helmet.isExotic && gauntlet.isExotic) continue; - for (let chest of chests) { - if ((helmet.isExotic || gauntlet.isExotic) && chest.isExotic) continue; - for (let leg of legs) { - if ((helmet.isExotic || gauntlet.isExotic || chest.isExotic) && leg.isExotic) continue; - if ( - requiresAtLeastOneExotic && - !(helmet.isExotic || gauntlet.isExotic || chest.isExotic || leg.isExotic) - ) - continue; - - yield [helmet, gauntlet, chest, leg]; - } - } - } + const legendaryHelmets = helmets.filter((h) => !h.isExotic); + const legendaryGauntlets = gauntlets.filter((g) => !g.isExotic); + const legendaryChests = chests.filter((c) => !c.isExotic); + const legendaryLegs = legs.filter((l) => !l.isExotic); + const legendaryClassItems = classItems.filter((d) => !d.isExotic); + + // Yield combinations with exactly one exotic item and legendaries in all other slots + if (yieldExoticCombinations) { + const exoticHelmets = helmets.filter((h) => h.isExotic); + const exoticGauntlets = gauntlets.filter((g) => g.isExotic); + const exoticChests = chests.filter((c) => c.isExotic); + const exoticLegs = legs.filter((l) => l.isExotic); + const exoticClassItems = classItems.filter((d) => d.isExotic); + + for (const helmet of exoticHelmets) + for (const gauntlet of legendaryGauntlets) + for (const chest of legendaryChests) + for (const leg of legendaryLegs) + for (const classItem of legendaryClassItems) + yield [helmet, gauntlet, chest, leg, classItem] as const; + + for (const helmet of legendaryHelmets) + for (const gauntlet of exoticGauntlets) + for (const chest of legendaryChests) + for (const leg of legendaryLegs) + for (const classItem of legendaryClassItems) + yield [helmet, gauntlet, chest, leg, classItem] as const; + + for (const helmet of legendaryHelmets) + for (const gauntlet of legendaryGauntlets) + for (const chest of exoticChests) + for (const leg of legendaryLegs) + for (const classItem of legendaryClassItems) + yield [helmet, gauntlet, chest, leg, classItem] as const; + + for (const helmet of legendaryHelmets) + for (const gauntlet of legendaryGauntlets) + for (const chest of legendaryChests) + for (const leg of exoticLegs) + for (const classItem of legendaryClassItems) + yield [helmet, gauntlet, chest, leg, classItem] as const; + + for (const helmet of legendaryHelmets) + for (const gauntlet of legendaryGauntlets) + for (const chest of legendaryChests) + for (const leg of legendaryLegs) + for (const classItem of exoticClassItems) + yield [helmet, gauntlet, chest, leg, classItem] as const; + } + + // Yield all-legendary combinations + if (yieldAllLegendary) { + for (const helmet of legendaryHelmets) + for (const gauntlet of legendaryGauntlets) + for (const chest of legendaryChests) + for (const leg of legendaryLegs) + for (const classItem of legendaryClassItems) + yield [helmet, gauntlet, chest, leg, classItem] as const; } } @@ -276,7 +233,10 @@ function estimateCombinationsToBeChecked( helmets: IPermutatorArmor[], gauntlets: IPermutatorArmor[], chests: IPermutatorArmor[], - legs: IPermutatorArmor[] + legs: IPermutatorArmor[], + classItems: IPermutatorArmor[], + yieldExoticCombinations: boolean, + yieldAllLegendary: boolean ) { let totalCalculations = 0; const exoticHelmets = helmets.filter((d) => d.isExotic).length; @@ -287,11 +247,51 @@ function estimateCombinationsToBeChecked( const legendaryChests = chests.length - exoticChests; const exoticLegs = legs.filter((d) => d.isExotic).length; const legendaryLegs = legs.length - exoticLegs; - totalCalculations += exoticHelmets * legendaryGauntlets * legendaryChests * legendaryLegs; - totalCalculations += legendaryHelmets * exoticGauntlets * legendaryChests * legendaryLegs; - totalCalculations += legendaryHelmets * legendaryGauntlets * exoticChests * legendaryLegs; - totalCalculations += legendaryHelmets * legendaryGauntlets * legendaryChests * exoticLegs; - totalCalculations += legendaryHelmets * legendaryGauntlets * legendaryChests * legendaryLegs; + const exoticClassItemCount = classItems.filter((d) => d.isExotic).length; + const legendaryClassItemCount = classItems.length - exoticClassItemCount; + + if (yieldExoticCombinations) { + totalCalculations += + exoticHelmets * + legendaryGauntlets * + legendaryChests * + legendaryLegs * + legendaryClassItemCount; + totalCalculations += + legendaryHelmets * + exoticGauntlets * + legendaryChests * + legendaryLegs * + legendaryClassItemCount; + totalCalculations += + legendaryHelmets * + legendaryGauntlets * + exoticChests * + legendaryLegs * + legendaryClassItemCount; + totalCalculations += + legendaryHelmets * + legendaryGauntlets * + legendaryChests * + exoticLegs * + legendaryClassItemCount; + totalCalculations += + legendaryHelmets * + legendaryGauntlets * + legendaryChests * + legendaryLegs * + exoticClassItemCount; + } + + if (yieldAllLegendary) { + totalCalculations += + legendaryHelmets * + legendaryGauntlets * + legendaryChests * + legendaryLegs * + legendaryClassItemCount; + } + return totalCalculations; } // endregion Validation and Preparation Functions @@ -483,48 +483,13 @@ addEventListener("message", async ({ data }) => { } } - const exoticClassItems = classItems.filter((d) => d.isExotic); - const legendaryClassItems = classItems.filter((d) => !d.isExotic); - const exoticClassItemIsEnforced = exoticClassItems.some( - (item) => config.selectedExotics.indexOf(item.hash) > -1 - ); - let availableClassItemPerkTypes = new Set(classItems.map((d) => d.gearSetHash || d.perk)); - // let availableClassItemPerkTypes1 = new Set(classItems.map((d) => d.gearSetHash)); - // runtime variables const runtime = { maximumPossibleTiers: [0, 0, 0, 0, 0, 0], }; - if (classItems.length == 0) { - console.warn( - `Thread#${threadSplit.current} - No class items found with the current configuration.` - ); - postMessage({ - runtime: runtime, - results: [], - done: true, - checkedCalculations: 0, - estimatedCalculations: 0, - stats: { - permutationCount: 0, - itemCount: items.length - classItems.length, - totalTime: Date.now() - startTime, - }, - }); - return; - } else if (exoticClassItems.length > 0 && legendaryClassItems.length == 0) { - // If we do not have legendary class items, we can not use any exotic armor in other slots - helmets = helmets.filter((d) => !d.isExotic); - gauntlets = gauntlets.filter((d) => !d.isExotic); - chests = chests.filter((d) => !d.isExotic); - legs = legs.filter((d) => !d.isExotic); - } - - const constantBonus = prepareConstantStatBonus(config); - const constantModslotRequirement = prepareConstantModslotRequirement(config); - - const requiresAtLeastOneExotic = config.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) > -1; + const enabledModBonuses = computeEnabledModBonuses(config); + const requiredPerkSlotCounts = calculateRequiredPerkCounts(config); let results: IPermutatorArmorSet[] = []; let resultsLength = 0; @@ -533,62 +498,48 @@ addEventListener("message", async ({ data }) => { let totalResults = 0; let doNotOutput = false; - // contains the value of the total amount of combinations to be checked - let estimatedCalculations = estimateCombinationsToBeChecked(helmets, gauntlets, chests, legs); + // Determine exotic combination mode from selectedExotics: + // - FORCE_USE_ANY_EXOTIC or specific exotic hash(es): yield only 1-exotic combinations + // - FORCE_USE_NO_EXOTIC: yield only all-legendary combinations + // - Empty array (no selection): yield both + const hasForceNoExotic = config.selectedExotics[0] === FORCE_USE_NO_EXOTIC; + const hasForceAnyExotic = config.selectedExotics[0] === FORCE_USE_ANY_EXOTIC; + const hasSpecificExotic = + config.selectedExotics.length > 0 && !hasForceNoExotic && !hasForceAnyExotic; + const noSelection = config.selectedExotics.length === 0; + + const yieldExoticCombinations = hasForceAnyExotic || hasSpecificExotic || noSelection; + const yieldAllLegendary = hasForceNoExotic || noSelection; + + let estimatedCalculations = estimateCombinationsToBeChecked( + helmets, + gauntlets, + chests, + legs, + classItems, + yieldExoticCombinations, + yieldAllLegendary + ); + let checkedCalculations = 0; let lastProgressReportTime = 0; + // define the delay; it can be 75ms if the estimated calculations are low // if the estimated calculations >= 1e6, then we will use 125ms let progressBarDelay = estimatedCalculations >= 1e6 ? 500 : 125; - for (let [helmet, gauntlet, chest, leg] of generateArmorCombinations( + for (let [helmet, gauntlet, chest, leg, classItem] of generateArmorCombinations( helmets, gauntlets, chests, legs, - // if exotic class items are enforced, we can not use any other exotic armor piece - requiresAtLeastOneExotic && !exoticClassItemIsEnforced + classItems, + yieldExoticCombinations, + yieldAllLegendary )) { checkedCalculations++; - /** - * At this point we already have: - * - Masterworked Exotic/Legendaries, if they must be masterworked (config.onlyUseMasterworkedExotics/config.onlyUseMasterworkedLegendaries) - * - disabled items were already removed (config.disabledItems) - */ - const slotCheckResult = checkSlots( - config, - constantModslotRequirement, - availableClassItemPerkTypes, - helmet, - gauntlet, - chest, - leg - ); - if (!slotCheckResult.valid) continue; - - const hasOneExotic = helmet.isExotic || gauntlet.isExotic || chest.isExotic || leg.isExotic; - // TODO This check should be in the generator - if (hasOneExotic && exoticClassItemIsEnforced) continue; - - let classItemsToUse: IPermutatorArmor[] = classItems; - if (hasOneExotic) { - // if we have an exotic armor piece, we can not use the exotic class item - classItemsToUse = legendaryClassItems; - } else if (config.selectedExotics[0] == FORCE_USE_ANY_EXOTIC || exoticClassItemIsEnforced) { - // if we have no exotic armor piece, we can use the exotic class item - classItemsToUse = exoticClassItems; - } - if (slotCheckResult.requiredClassItemType != ArmorPerkOrSlot.Any) { - classItemsToUse = classItems.filter( - (item) => - item.perk == slotCheckResult.requiredClassItemType || - item.gearSetHash == slotCheckResult.requiredClassItemType - ); - } - if (classItemsToUse.length == 0) { - // If we have no class items, we do not need to calculate the permutation + if (!checkSlots(config, requiredPerkSlotCounts, helmet, gauntlet, chest, leg, classItem)) continue; - } const result = handlePermutation( runtime, @@ -597,8 +548,8 @@ addEventListener("message", async ({ data }) => { gauntlet, chest, leg, - classItemsToUse, - constantBonus, + classItem, + enabledModBonuses, doNotOutput ); // Only add 50k to the list if the setting is activated. @@ -760,8 +711,8 @@ export function handlePermutation( gauntlet: IPermutatorArmor, chest: IPermutatorArmor, leg: IPermutatorArmor, - classItems: IPermutatorArmor[], - constantBonus: number[], + classItem: IPermutatorArmor, + enabledModBonuses: number[], doNotOutput = false ): never[] | IPermutatorArmorSet | null { const items = [helmet, gauntlet, chest, leg]; @@ -791,12 +742,12 @@ export function handlePermutation( baseStats[5], ]; const stats: number[] = [ - statsWithoutMods[0] + (constantBonus[0] || 0), - statsWithoutMods[1] + (constantBonus[1] || 0), - statsWithoutMods[2] + (constantBonus[2] || 0), - statsWithoutMods[3] + (constantBonus[3] || 0), - statsWithoutMods[4] + (constantBonus[4] || 0), - statsWithoutMods[5] + (constantBonus[5] || 0), + statsWithoutMods[0] + (enabledModBonuses[0] || 0), + statsWithoutMods[1] + (enabledModBonuses[1] || 0), + statsWithoutMods[2] + (enabledModBonuses[2] || 0), + statsWithoutMods[3] + (enabledModBonuses[3] || 0), + statsWithoutMods[4] + (enabledModBonuses[4] || 0), + statsWithoutMods[5] + (enabledModBonuses[5] || 0), ]; // early abort if fixed tiers exceeded @@ -853,204 +804,175 @@ export function handlePermutation( } } } - // sort class items once - const sortedClassItems = sortClassItemsForGaps(classItems, config, distances, stats); - - // reusable buffers - const adjustedStats = [0, 0, 0, 0, 0, 0]; - const adjustedStatsWithoutMods = [0, 0, 0, 0, 0, 0]; - const newDistances = [0, 0, 0, 0, 0, 0]; - const newOptionalDistances = [0, 0, 0, 0, 0, 0]; - // mod caps - const maxMajorMods = config.statModLimits?.maxMajorMods || 0; - const maxMods = config.statModLimits?.maxMods || 0; - const possibleIncreaseByMod = 10 * maxMajorMods + 5 * Math.max(0, maxMods - maxMajorMods); + // compute adjustedStats with class item + const adjustedStats = [ + stats[0] + (classItem.mobility || 0), + stats[1] + (classItem.resilience || 0), + stats[2] + (classItem.recovery || 0), + stats[3] + (classItem.discipline || 0), + stats[4] + (classItem.intellect || 0), + stats[5] + (classItem.strength || 0), + ]; + applyMasterworkStats(classItem, config, adjustedStats); - // helper to compute tuning maxima with optional extra T5 - function calcTuningMaxWithExtra(extra?: t5Improvement): number[] { - if (!extra) return preTuningMax.slice(); - const result = preTuningMax.slice(); - const mask = [false, false, false, false, false, false]; - for (const s of extra.archetypeStats) if (s >= 0 && s < 6) mask[s] = true; - const balanced: number[] = [0, 0, 0, 0, 0, 0]; - for (let i = 0; i < 6; i++) balanced[i] = mask[i] ? 0 : 1; - for (let n = 0; n < 6; n++) { - if (n === extra.tuningStat) continue; - const p: number[] = [0, 0, 0, 0, 0, 0]; - p[extra.tuningStat] = 5; - p[n] = -5; - for (let i = 0; i < 6; i++) result[i] += Math.max(balanced[i], p[i]); - } - return result; + // quick fixed-tier abort + for (let n: ArmorStat = 0; n < 6; n++) { + if (targetFixed[n] && adjustedStats[n] > targetVals[n]) return null; } - let finalResult: IPermutatorArmorSet | never[] = []; - let checkedClassItems = 0; - - for (const classItem of sortedClassItems) { - checkedClassItems++; + // adjustedStatsWithoutMods + const adjustedStatsWithoutMods = [ + statsWithoutMods[0] + (classItem.mobility || 0), + statsWithoutMods[1] + (classItem.resilience || 0), + statsWithoutMods[2] + (classItem.recovery || 0), + statsWithoutMods[3] + (classItem.discipline || 0), + statsWithoutMods[4] + (classItem.intellect || 0), + statsWithoutMods[5] + (classItem.strength || 0), + ]; + applyMasterworkStats(classItem, config, adjustedStatsWithoutMods); - // compute adjustedStats - adjustedStats[0] = stats[0] + (classItem.mobility || 0); - adjustedStats[1] = stats[1] + (classItem.resilience || 0); - adjustedStats[2] = stats[2] + (classItem.recovery || 0); - adjustedStats[3] = stats[3] + (classItem.discipline || 0); - adjustedStats[4] = stats[4] + (classItem.intellect || 0); - adjustedStats[5] = stats[5] + (classItem.strength || 0); - applyMasterworkStats(classItem, config, adjustedStats); + // artifice count including class item + const totalArtificeCount = + availableArtificeCount + (classItem.perk == ArmorPerkOrSlot.SlotArtifice ? 1 : 0); - // quick fixed-tier abort - for (let n: ArmorStat = 0; n < 6; n++) { - if (targetFixed[n] && adjustedStats[n] > targetVals[n]) return null; - } + let classItemT5: t5Improvement | undefined = undefined; + let tuningMax: number[] = preTuningMax; + if (config.calculateTierFiveTuning) { + classItemT5 = isT5WithTuning(classItem) ? mapItemToTuning(classItem) : undefined; - // adjustedStatsWithoutMods - adjustedStatsWithoutMods[0] = statsWithoutMods[0] + (classItem.mobility || 0); - adjustedStatsWithoutMods[1] = statsWithoutMods[1] + (classItem.resilience || 0); - adjustedStatsWithoutMods[2] = statsWithoutMods[2] + (classItem.recovery || 0); - adjustedStatsWithoutMods[3] = statsWithoutMods[3] + (classItem.discipline || 0); - adjustedStatsWithoutMods[4] = statsWithoutMods[4] + (classItem.intellect || 0); - adjustedStatsWithoutMods[5] = statsWithoutMods[5] + (classItem.strength || 0); - applyMasterworkStats(classItem, config, adjustedStatsWithoutMods); - - // tmp artifice count - const tmpArtificeCount = - availableArtificeCount + (classItem.perk == ArmorPerkOrSlot.SlotArtifice ? 1 : 0); - - let classItemT5: t5Improvement | undefined = undefined; - let tuningMax: number[] = preTuningMax.slice(); - if (config.calculateTierFiveTuning) { - // candidate T5 from class item if any - classItemT5 = isT5WithTuning(classItem) ? mapItemToTuning(classItem) : undefined; - - // tuning maxima without full generate - tuningMax = calcTuningMaxWithExtra(classItemT5); + if (classItemT5) { + tuningMax = preTuningMax.slice(); + const mask = [false, false, false, false, false, false]; + for (const s of classItemT5.archetypeStats) if (s >= 0 && s < 6) mask[s] = true; + const balanced: number[] = [0, 0, 0, 0, 0, 0]; + for (let i = 0; i < 6; i++) balanced[i] = mask[i] ? 0 : 1; + for (let n = 0; n < 6; n++) { + if (n === classItemT5.tuningStat) continue; + const p: number[] = [0, 0, 0, 0, 0, 0]; + p[classItemT5.tuningStat] = 5; + p[n] = -5; + for (let i = 0; i < 6; i++) tuningMax[i] += Math.max(balanced[i], p[i]); + } } + } - // newDistances - for (let n: ArmorStat = 0; n < 6; n++) - newDistances[n] = Math.max(0, targetVals[n] - adjustedStats[n]); - if (config.onlyShowResultsWithNoWastedStats) { - for (let stat: ArmorStat = 0; stat < 6; stat++) { - const v = 10 - (adjustedStats[stat] % 10); - newDistances[stat] = Math.max(newDistances[stat], v < 10 ? v : 0); - } + // newDistances + const newDistances = [0, 0, 0, 0, 0, 0]; + for (let n: ArmorStat = 0; n < 6; n++) + newDistances[n] = Math.max(0, targetVals[n] - adjustedStats[n]); + if (config.onlyShowResultsWithNoWastedStats) { + for (let stat: ArmorStat = 0; stat < 6; stat++) { + const v = 10 - (adjustedStats[stat] % 10); + newDistances[stat] = Math.max(newDistances[stat], v < 10 ? v : 0); } + } - // newOptionalDistances - for (let stat: ArmorStat = 0; stat < 6; stat++) newOptionalDistances[stat] = 0; - if (config.tryLimitWastedStats) { - for (let stat: ArmorStat = 0; stat < 6; stat++) { - if ( - newDistances[stat] === 0 && - !targetFixed[stat] && - adjustedStats[stat] < 200 && - adjustedStats[stat] % 10 > 0 - ) { - newOptionalDistances[stat] = 10 - (adjustedStats[stat] % 10); - } + // newOptionalDistances + const newOptionalDistances = [0, 0, 0, 0, 0, 0]; + if (config.tryLimitWastedStats) { + for (let stat: ArmorStat = 0; stat < 6; stat++) { + if ( + newDistances[stat] === 0 && + !targetFixed[stat] && + adjustedStats[stat] < 200 && + adjustedStats[stat] % 10 > 0 + ) { + newOptionalDistances[stat] = 10 - (adjustedStats[stat] % 10); } } + } - // cheap global bound check - const newDistanceSum = - newDistances[0] + - newDistances[1] + - newDistances[2] + - newDistances[3] + - newDistances[4] + - newDistances[5]; - const newTotalOptionalDistances = - newOptionalDistances[0] + - newOptionalDistances[1] + - newOptionalDistances[2] + - newOptionalDistances[3] + - newOptionalDistances[4] + - newOptionalDistances[5]; + // cheap global bound check + const newDistanceSum = + newDistances[0] + + newDistances[1] + + newDistances[2] + + newDistances[3] + + newDistances[4] + + newDistances[5]; + const newTotalOptionalDistances = + newOptionalDistances[0] + + newOptionalDistances[1] + + newOptionalDistances[2] + + newOptionalDistances[3] + + newOptionalDistances[4] + + newOptionalDistances[5]; - if ( - newDistanceSum > - 10 * 5 + 3 * availableArtificeCount + 5 * (baseT5Improvements.length + (classItemT5 ? 1 : 0)) - ) { - if (config.earlyAbortClassItems && checkedClassItems >= 3) break; - else continue; - } + if ( + newDistanceSum > + 10 * 5 + 3 * availableArtificeCount + 5 * (baseT5Improvements.length + (classItemT5 ? 1 : 0)) + ) { + return null; + } - let availableTunings: Tuning[] = [[0, 0, 0, 0, 0, 0]]; - - // per-stat quick feasibility check - let passesPerStat = true; - for (let stat = 0; stat < 6; stat++) { - const possibleIncreaseByTuning = tuningMax[stat]; - const possibleIncreaseByArtifice = 3 * tmpArtificeCount; - const possibleIncrease = - possibleIncreaseByMod + possibleIncreaseByTuning + possibleIncreaseByArtifice; - if (possibleIncrease < newDistances[stat]) { - passesPerStat = false; - break; - } - } - if (!passesPerStat) { - if (config.earlyAbortClassItems && checkedClassItems >= 3) break; - else continue; - } - if (config.calculateTierFiveTuning) { - // lazy: only generate full tunings when cheaper checks pass - const tmpPossibleT5Improvements: t5Improvement[] = baseT5Improvements.slice(); - if (classItemT5) tmpPossibleT5Improvements.push(classItemT5); - availableTunings = generate_tunings(tmpPossibleT5Improvements); - } + // mod caps + const maxMajorMods = config.statModLimits?.maxMajorMods || 0; + const maxMods = config.statModLimits?.maxMods || 0; + const possibleIncreaseByMod = 10 * maxMajorMods + 5 * Math.max(0, maxMods - maxMajorMods); - // heavy work: mod precalc - let result: StatModifierPrecalc | null; - if (newDistanceSum === 0 && newTotalOptionalDistances === 0) { - result = { mods: [], tuning: [0, 0, 0, 0, 0, 0], modBonus: [0, 0, 0, 0, 0, 0] }; - } else { - result = get_mods_precalc( - adjustedStats, - targetVals, - config, - newDistances, - newOptionalDistances, - tmpArtificeCount, - config.modOptimizationStrategy, - availableTunings - ); + // per-stat quick feasibility check + for (let stat = 0; stat < 6; stat++) { + const possibleIncreaseByTuning = tuningMax[stat]; + const possibleIncreaseByArtifice = 3 * totalArtificeCount; + const possibleIncrease = + possibleIncreaseByMod + possibleIncreaseByTuning + possibleIncreaseByArtifice; + if (possibleIncrease < newDistances[stat]) { + return null; } + } - if (result !== null) { - performTierAvailabilityTesting( - runtime, - config, - adjustedStats, - targetVals, - newDistances, - tmpArtificeCount, - availableTunings - ); + let availableTunings: Tuning[] = [[0, 0, 0, 0, 0, 0]]; + if (config.calculateTierFiveTuning) { + const tmpPossibleT5Improvements: t5Improvement[] = baseT5Improvements.slice(); + if (classItemT5) tmpPossibleT5Improvements.push(classItemT5); + availableTunings = generate_tunings(tmpPossibleT5Improvements); + } - if (Array.isArray(finalResult) && finalResult.length === 0) { - finalResult = tryCreateArmorSetWithClassItem( - runtime, - config, - helmet, - gauntlet, - chest, - leg, - classItem, - result, - adjustedStats, - adjustedStatsWithoutMods.slice(), - newDistances, - tmpArtificeCount, - doNotOutput - ); - } - } + // heavy work: mod precalc + let result: StatModifierPrecalc | null; + if (newDistanceSum === 0 && newTotalOptionalDistances === 0) { + result = { mods: [], tuning: [0, 0, 0, 0, 0, 0], modBonus: [0, 0, 0, 0, 0, 0] }; + } else { + result = get_mods_precalc( + adjustedStats, + targetVals, + config, + newDistances, + newOptionalDistances, + totalArtificeCount, + config.modOptimizationStrategy, + availableTunings + ); } - return finalResult; + if (result === null) return null; + + performTierAvailabilityTesting( + runtime, + config, + adjustedStats, + targetVals, + newDistances, + totalArtificeCount, + availableTunings + ); + + return tryCreateArmorSetWithClassItem( + runtime, + config, + helmet, + gauntlet, + chest, + leg, + classItem, + result, + adjustedStats, + adjustedStatsWithoutMods, + newDistances, + totalArtificeCount, + doNotOutput + ); } function getStatVal(statId: ArmorStat, mods: StatModifierPrecalc, start: number) { From f95222372b025ab290797bc00efa3881b710bbbd Mon Sep 17 00:00:00 2001 From: nznaza Date: Sun, 22 Feb 2026 18:56:04 -0600 Subject: [PATCH 78/84] feat: consolidate all armor pieces to use the same logic --- src/app/services/results-builder.worker.ts | 221 +++++++-------------- 1 file changed, 67 insertions(+), 154 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 3985e3cd..aea75742 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -85,23 +85,20 @@ function checkSlots( const items = [helmet, gauntlet, chest, leg, classItem]; for (let item of items) { + let effectivePerk = item.perk; + if (item.armorSystem === ArmorSystem.Armor2) { if ( - (item.isExotic && config.assumeEveryLegendaryIsArtifice) || - (!item.isExotic && config.assumeEveryLegendaryIsArtifice) || + (item.isExotic && config.assumeEveryExoticIsArtifice) || (!item.isExotic && - item.slot == ArmorSlot.ArmorSlotClass && - config.assumeClassItemIsArtifice) + (config.assumeEveryLegendaryIsArtifice || + (item.slot == ArmorSlot.ArmorSlotClass && config.assumeClassItemIsArtifice))) ) { - requirements.set( - ArmorPerkOrSlot.SlotArtifice, - (requirements.get(ArmorPerkOrSlot.SlotArtifice) ?? 0) - 1 - ); - continue; + effectivePerk = ArmorPerkOrSlot.SlotArtifice; } } - requirements.set(item.perk, (requirements.get(item.perk) ?? 0) - 1); + requirements.set(effectivePerk, (requirements.get(effectivePerk) ?? 0) - 1); if (item.gearSetHash != null) requirements.set(item.gearSetHash, (requirements.get(item.gearSetHash) ?? 0) - 1); } @@ -605,14 +602,21 @@ addEventListener("message", async ({ data }) => { export function getStatSum( items: IDestinyArmor[] ): [number, number, number, number, number, number] { - return [ - items[0].mobility + items[1].mobility + items[2].mobility + items[3].mobility, - items[0].resilience + items[1].resilience + items[2].resilience + items[3].resilience, - items[0].recovery + items[1].recovery + items[2].recovery + items[3].recovery, - items[0].discipline + items[1].discipline + items[2].discipline + items[3].discipline, - items[0].intellect + items[1].intellect + items[2].intellect + items[3].intellect, - items[0].strength + items[1].strength + items[2].strength + items[3].strength, - ]; + let mob = 0, + res = 0, + rec = 0, + dis = 0, + int_ = 0, + str = 0; + for (const item of items) { + mob += item.mobility; + res += item.resilience; + rec += item.recovery; + dis += item.discipline; + int_ += item.intellect; + str += item.strength; + } + return [mob, res, rec, dis, int_, str]; } function applyMasterworkStats( @@ -715,11 +719,11 @@ export function handlePermutation( enabledModBonuses: number[], doNotOutput = false ): never[] | IPermutatorArmorSet | null { - const items = [helmet, gauntlet, chest, leg]; + const items = [helmet, gauntlet, chest, leg, classItem]; // base stats and apply constant health tweak const baseStats = getStatSum(items); - baseStats[1] += !items[2].isExotic && config.addConstent1Health ? 1 : 0; + baseStats[1] += !chest.isExotic && config.addConstent1Health ? 1 : 0; // apply masterwork effects to baseStats (assumed idempotent) for (const it of items) applyMasterworkStats(it, config, baseStats); @@ -732,23 +736,9 @@ export function handlePermutation( targetFixed[n] = !!config.minimumStatTiers[n].fixed; } - // stats without mods, and stats with constant bonuses - const statsWithoutMods: number[] = [ - baseStats[0], - baseStats[1], - baseStats[2], - baseStats[3], - baseStats[4], - baseStats[5], - ]; - const stats: number[] = [ - statsWithoutMods[0] + (enabledModBonuses[0] || 0), - statsWithoutMods[1] + (enabledModBonuses[1] || 0), - statsWithoutMods[2] + (enabledModBonuses[2] || 0), - statsWithoutMods[3] + (enabledModBonuses[3] || 0), - statsWithoutMods[4] + (enabledModBonuses[4] || 0), - statsWithoutMods[5] + (enabledModBonuses[5] || 0), - ]; + // stats without mods, and stats with mod bonuses + const statsWithoutMods: number[] = [...baseStats]; + const stats: number[] = baseStats.map((s, i) => s + (enabledModBonuses[i] || 0)); // early abort if fixed tiers exceeded for (let n: ArmorStat = 0; n < 6; n++) { @@ -758,7 +748,7 @@ export function handlePermutation( // count available artifice slots const assumeEveryLegendaryIsArtifice = !!config.assumeEveryLegendaryIsArtifice; const assumeEveryExoticIsArtifice = !!config.assumeEveryExoticIsArtifice; - let availableArtificeCount = 0; + let artificeCount = 0; for (const d of items) { if ( d.perk == ArmorPerkOrSlot.SlotArtifice || @@ -766,11 +756,11 @@ export function handlePermutation( ((assumeEveryLegendaryIsArtifice && !d.isExotic) || (assumeEveryExoticIsArtifice && d.isExotic))) ) { - availableArtificeCount++; + artificeCount++; } } - // initial distances + // distances to target const distances: number[] = new Array(6); for (let n: ArmorStat = 0; n < 6; n++) distances[n] = Math.max(0, targetVals[n] - stats[n]); @@ -781,16 +771,16 @@ export function handlePermutation( } } - const baseT5Improvements: t5Improvement[] = []; - const preTuningMax: number[] = [0, 0, 0, 0, 0, 0]; + // T5 tuning improvements (all items including class item) + const t5Improvements: t5Improvement[] = []; + const tuningMax: number[] = [0, 0, 0, 0, 0, 0]; if (config.calculateTierFiveTuning) { - // precompute base T5 improvements and per-stat tuning maxima - for (const it of items) { - if (isT5WithTuning(it)) baseT5Improvements.push(mapItemToTuning(it)); + for (const item of items) { + if (isT5WithTuning(item)) t5Improvements.push(mapItemToTuning(item)); } - for (const t5 of baseT5Improvements) { + for (const t5 of t5Improvements) { const mask = [false, false, false, false, false, false]; for (const s of t5.archetypeStats) if (s >= 0 && s < 6) mask[s] = true; const balanced: number[] = [0, 0, 0, 0, 0, 0]; @@ -800,109 +790,38 @@ export function handlePermutation( const p: number[] = [0, 0, 0, 0, 0, 0]; p[t5.tuningStat] = 5; p[n] = -5; - for (let i = 0; i < 6; i++) preTuningMax[i] += Math.max(balanced[i], p[i]); - } - } - } - - // compute adjustedStats with class item - const adjustedStats = [ - stats[0] + (classItem.mobility || 0), - stats[1] + (classItem.resilience || 0), - stats[2] + (classItem.recovery || 0), - stats[3] + (classItem.discipline || 0), - stats[4] + (classItem.intellect || 0), - stats[5] + (classItem.strength || 0), - ]; - applyMasterworkStats(classItem, config, adjustedStats); - - // quick fixed-tier abort - for (let n: ArmorStat = 0; n < 6; n++) { - if (targetFixed[n] && adjustedStats[n] > targetVals[n]) return null; - } - - // adjustedStatsWithoutMods - const adjustedStatsWithoutMods = [ - statsWithoutMods[0] + (classItem.mobility || 0), - statsWithoutMods[1] + (classItem.resilience || 0), - statsWithoutMods[2] + (classItem.recovery || 0), - statsWithoutMods[3] + (classItem.discipline || 0), - statsWithoutMods[4] + (classItem.intellect || 0), - statsWithoutMods[5] + (classItem.strength || 0), - ]; - applyMasterworkStats(classItem, config, adjustedStatsWithoutMods); - - // artifice count including class item - const totalArtificeCount = - availableArtificeCount + (classItem.perk == ArmorPerkOrSlot.SlotArtifice ? 1 : 0); - - let classItemT5: t5Improvement | undefined = undefined; - let tuningMax: number[] = preTuningMax; - if (config.calculateTierFiveTuning) { - classItemT5 = isT5WithTuning(classItem) ? mapItemToTuning(classItem) : undefined; - - if (classItemT5) { - tuningMax = preTuningMax.slice(); - const mask = [false, false, false, false, false, false]; - for (const s of classItemT5.archetypeStats) if (s >= 0 && s < 6) mask[s] = true; - const balanced: number[] = [0, 0, 0, 0, 0, 0]; - for (let i = 0; i < 6; i++) balanced[i] = mask[i] ? 0 : 1; - for (let n = 0; n < 6; n++) { - if (n === classItemT5.tuningStat) continue; - const p: number[] = [0, 0, 0, 0, 0, 0]; - p[classItemT5.tuningStat] = 5; - p[n] = -5; for (let i = 0; i < 6; i++) tuningMax[i] += Math.max(balanced[i], p[i]); } } } - // newDistances - const newDistances = [0, 0, 0, 0, 0, 0]; - for (let n: ArmorStat = 0; n < 6; n++) - newDistances[n] = Math.max(0, targetVals[n] - adjustedStats[n]); - if (config.onlyShowResultsWithNoWastedStats) { - for (let stat: ArmorStat = 0; stat < 6; stat++) { - const v = 10 - (adjustedStats[stat] % 10); - newDistances[stat] = Math.max(newDistances[stat], v < 10 ? v : 0); - } - } - - // newOptionalDistances - const newOptionalDistances = [0, 0, 0, 0, 0, 0]; + // optional distances for waste limiting + const optionalDistances = [0, 0, 0, 0, 0, 0]; if (config.tryLimitWastedStats) { for (let stat: ArmorStat = 0; stat < 6; stat++) { if ( - newDistances[stat] === 0 && + distances[stat] === 0 && !targetFixed[stat] && - adjustedStats[stat] < 200 && - adjustedStats[stat] % 10 > 0 + stats[stat] < 200 && + stats[stat] % 10 > 0 ) { - newOptionalDistances[stat] = 10 - (adjustedStats[stat] % 10); + optionalDistances[stat] = 10 - (stats[stat] % 10); } } } // cheap global bound check - const newDistanceSum = - newDistances[0] + - newDistances[1] + - newDistances[2] + - newDistances[3] + - newDistances[4] + - newDistances[5]; - const newTotalOptionalDistances = - newOptionalDistances[0] + - newOptionalDistances[1] + - newOptionalDistances[2] + - newOptionalDistances[3] + - newOptionalDistances[4] + - newOptionalDistances[5]; - - if ( - newDistanceSum > - 10 * 5 + 3 * availableArtificeCount + 5 * (baseT5Improvements.length + (classItemT5 ? 1 : 0)) - ) { + const distanceSum = + distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; + const totalOptionalDistances = + optionalDistances[0] + + optionalDistances[1] + + optionalDistances[2] + + optionalDistances[3] + + optionalDistances[4] + + optionalDistances[5]; + + if (distanceSum > 10 * 5 + 3 * artificeCount + 5 * t5Improvements.length) { return null; } @@ -913,34 +832,28 @@ export function handlePermutation( // per-stat quick feasibility check for (let stat = 0; stat < 6; stat++) { - const possibleIncreaseByTuning = tuningMax[stat]; - const possibleIncreaseByArtifice = 3 * totalArtificeCount; - const possibleIncrease = - possibleIncreaseByMod + possibleIncreaseByTuning + possibleIncreaseByArtifice; - if (possibleIncrease < newDistances[stat]) { + if (possibleIncreaseByMod + tuningMax[stat] + 3 * artificeCount < distances[stat]) { return null; } } let availableTunings: Tuning[] = [[0, 0, 0, 0, 0, 0]]; if (config.calculateTierFiveTuning) { - const tmpPossibleT5Improvements: t5Improvement[] = baseT5Improvements.slice(); - if (classItemT5) tmpPossibleT5Improvements.push(classItemT5); - availableTunings = generate_tunings(tmpPossibleT5Improvements); + availableTunings = generate_tunings(t5Improvements); } // heavy work: mod precalc let result: StatModifierPrecalc | null; - if (newDistanceSum === 0 && newTotalOptionalDistances === 0) { + if (distanceSum === 0 && totalOptionalDistances === 0) { result = { mods: [], tuning: [0, 0, 0, 0, 0, 0], modBonus: [0, 0, 0, 0, 0, 0] }; } else { result = get_mods_precalc( - adjustedStats, + stats, targetVals, config, - newDistances, - newOptionalDistances, - totalArtificeCount, + distances, + optionalDistances, + artificeCount, config.modOptimizationStrategy, availableTunings ); @@ -951,10 +864,10 @@ export function handlePermutation( performTierAvailabilityTesting( runtime, config, - adjustedStats, + stats, targetVals, - newDistances, - totalArtificeCount, + distances, + artificeCount, availableTunings ); @@ -967,10 +880,10 @@ export function handlePermutation( leg, classItem, result, - adjustedStats, - adjustedStatsWithoutMods, - newDistances, - totalArtificeCount, + stats, + statsWithoutMods, + distances, + artificeCount, doNotOutput ); } From e06d9a7066afb882149bd32059e528d811550008 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sun, 22 Feb 2026 19:39:13 -0600 Subject: [PATCH 79/84] feat: improve array usage while calculating permutations --- src/app/services/results-builder.worker.ts | 375 ++++++++++++++------- 1 file changed, 248 insertions(+), 127 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index aea75742..2530a648 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -71,9 +71,111 @@ function mapItemToTuning(i: IPermutatorArmor): t5Improvement { }; } +// Precomputed configuration values that remain constant across all permutations. +// Computing these once and passing them in avoids redundant property lookups, +// array allocations, and repeated arithmetic on every call to handlePermutation. +export interface PreparedConfig { + targetVals: number[]; + targetFixed: boolean[]; + maxMajorMods: number; + maxMods: number; + possibleIncreaseByMod: number; + assumeEveryLegendaryIsArtifice: boolean; + assumeEveryExoticIsArtifice: boolean; + assumeClassItemIsArtifice: boolean; + calculateTierFiveTuning: boolean; + onlyShowResultsWithNoWastedStats: boolean; + tryLimitWastedStats: boolean; + addConstent1Health: boolean; + assumeExoticsMasterworked: boolean; + assumeLegendariesMasterworked: boolean; + minimumStatTierValues: number[]; + modOptimizationStrategy: ModOptimizationStrategy; +} + +export function prepareConfig(config: BuildConfiguration): PreparedConfig { + const targetVals: number[] = [0, 0, 0, 0, 0, 0]; + const targetFixed: boolean[] = [false, false, false, false, false, false]; + const minimumStatTierValues: number[] = [0, 0, 0, 0, 0, 0]; + for (let n = 0; n < 6; n++) { + targetVals[n] = (config.minimumStatTiers[n as ArmorStat].value || 0) * 10; + targetFixed[n] = !!config.minimumStatTiers[n as ArmorStat].fixed; + minimumStatTierValues[n] = config.minimumStatTiers[n as ArmorStat].value || 0; + } + const maxMajorMods = config.statModLimits?.maxMajorMods || 0; + const maxMods = config.statModLimits?.maxMods || 0; + return { + targetVals, + targetFixed, + maxMajorMods, + maxMods, + possibleIncreaseByMod: 10 * maxMajorMods + 5 * Math.max(0, maxMods - maxMajorMods), + assumeEveryLegendaryIsArtifice: !!config.assumeEveryLegendaryIsArtifice, + assumeEveryExoticIsArtifice: !!config.assumeEveryExoticIsArtifice, + assumeClassItemIsArtifice: !!config.assumeClassItemIsArtifice, + calculateTierFiveTuning: !!config.calculateTierFiveTuning, + onlyShowResultsWithNoWastedStats: !!config.onlyShowResultsWithNoWastedStats, + tryLimitWastedStats: !!config.tryLimitWastedStats, + addConstent1Health: !!config.addConstent1Health, + assumeExoticsMasterworked: !!config.assumeExoticsMasterworked, + assumeLegendariesMasterworked: !!config.assumeLegendariesMasterworked, + minimumStatTierValues, + modOptimizationStrategy: config.modOptimizationStrategy, + }; +} + +/** + * Applies masterwork stat bonuses to the stats array and returns whether the item + * counts as an artifice slot. Combines two operations that were previously separate + * loops over an items array. Uses direct index comparisons instead of .includes() + * for archetypeStats (always exactly 3 elements). + */ +function applyMWAndCheckArtifice( + item: IPermutatorArmor, + pc: PreparedConfig, + stats: number[] +): boolean { + if (item.armorSystem === ArmorSystem.Armor2) { + if ( + item.masterworkLevel === MAXIMUM_MASTERWORK_LEVEL || + (item.isExotic ? pc.assumeExoticsMasterworked : pc.assumeLegendariesMasterworked) + ) { + stats[0] += 2; + stats[1] += 2; + stats[2] += 2; + stats[3] += 2; + stats[4] += 2; + stats[5] += 2; + } + return ( + item.perk === ArmorPerkOrSlot.SlotArtifice || + (item.isExotic ? pc.assumeEveryExoticIsArtifice : pc.assumeEveryLegendaryIsArtifice) + ); + } + if (item.armorSystem === ArmorSystem.Armor3) { + let mult = item.masterworkLevel; + if (item.isExotic ? pc.assumeExoticsMasterworked : pc.assumeLegendariesMasterworked) + mult = MAXIMUM_MASTERWORK_LEVEL; + if (mult > 0) { + const a = item.archetypeStats; + const a0 = a[0], + a1 = a[1], + a2 = a[2]; + if (a0 !== 0 && a1 !== 0 && a2 !== 0) stats[0] += mult; + if (a0 !== 1 && a1 !== 1 && a2 !== 1) stats[1] += mult; + if (a0 !== 2 && a1 !== 2 && a2 !== 2) stats[2] += mult; + if (a0 !== 3 && a1 !== 3 && a2 !== 3) stats[3] += mult; + if (a0 !== 4 && a1 !== 4 && a2 !== 4) stats[4] += mult; + if (a0 !== 5 && a1 !== 5 && a2 !== 5) stats[5] += mult; + } + return item.perk === ArmorPerkOrSlot.SlotArtifice; + } + return item.perk === ArmorPerkOrSlot.SlotArtifice; +} + // region Validation and Preparation Functions function checkSlots( - config: BuildConfiguration, + prepared: PreparedConfig, requiredPerkSlotCounts: Map, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, @@ -89,10 +191,10 @@ function checkSlots( if (item.armorSystem === ArmorSystem.Armor2) { if ( - (item.isExotic && config.assumeEveryExoticIsArtifice) || + (item.isExotic && prepared.assumeEveryExoticIsArtifice) || (!item.isExotic && - (config.assumeEveryLegendaryIsArtifice || - (item.slot == ArmorSlot.ArmorSlotClass && config.assumeClassItemIsArtifice))) + (prepared.assumeEveryLegendaryIsArtifice || + (item.slot == ArmorSlot.ArmorSlotClass && prepared.assumeClassItemIsArtifice))) ) { effectivePerk = ArmorPerkOrSlot.SlotArtifice; } @@ -525,6 +627,9 @@ addEventListener("message", async ({ data }) => { // if the estimated calculations >= 1e6, then we will use 125ms let progressBarDelay = estimatedCalculations >= 1e6 ? 500 : 125; + // Precompute config-invariant values once before the hot loop + const prepared = prepareConfig(config); + for (let [helmet, gauntlet, chest, leg, classItem] of generateArmorCombinations( helmets, gauntlets, @@ -535,19 +640,19 @@ addEventListener("message", async ({ data }) => { yieldAllLegendary )) { checkedCalculations++; - if (!checkSlots(config, requiredPerkSlotCounts, helmet, gauntlet, chest, leg, classItem)) + if (!checkSlots(prepared, requiredPerkSlotCounts, helmet, gauntlet, chest, leg, classItem)) continue; const result = handlePermutation( runtime, - config, helmet, gauntlet, chest, leg, classItem, enabledModBonuses, - doNotOutput + doNotOutput, + prepared ); // Only add 50k to the list if the setting is activated. // We will still calculate the rest so that we get accurate results for the runtime values @@ -619,39 +724,6 @@ export function getStatSum( return [mob, res, rec, dis, int_, str]; } -function applyMasterworkStats( - item: IPermutatorArmor, - config: BuildConfiguration, - stats: number[] = [0, 0, 0, 0, 0, 0] -): void { - if (item.armorSystem == ArmorSystem.Armor2) { - if ( - item.masterworkLevel == MAXIMUM_MASTERWORK_LEVEL || - (item.isExotic && config.assumeExoticsMasterworked) || - (!item.isExotic && config.assumeLegendariesMasterworked) - ) { - // Armor 2.0 Masterworked items give +10 to all stats - for (let i = 0; i < 6; i++) { - stats[i] += 2; - } - } - } else if (item.armorSystem == ArmorSystem.Armor3) { - let multiplier = item.masterworkLevel; - if ( - (item.isExotic && config.assumeExoticsMasterworked) || - (!item.isExotic && config.assumeLegendariesMasterworked) - ) - multiplier = MAXIMUM_MASTERWORK_LEVEL; - if (multiplier == 0) return; - - // item.archetypeStats contains three stat indices. The OTHER THREE get +1 per multiplier - for (let i = 0; i < 6; i++) { - if (item.archetypeStats.includes(i)) continue; - stats[i] += multiplier; - } - } -} - function generate_tunings(possibleImprovements: t5Improvement[]): Tuning[] { const impValues = possibleImprovements.map((imp) => { let l = [[0, 0, 0, 0, 0, 0]]; @@ -710,95 +782,145 @@ function generate_tunings(possibleImprovements: t5Improvement[]): Tuning[] { export function handlePermutation( runtime: any, - config: BuildConfiguration, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, chest: IPermutatorArmor, leg: IPermutatorArmor, classItem: IPermutatorArmor, enabledModBonuses: number[], - doNotOutput = false + doNotOutput = false, + prepared: PreparedConfig ): never[] | IPermutatorArmorSet | null { - const items = [helmet, gauntlet, chest, leg, classItem]; - - // base stats and apply constant health tweak - const baseStats = getStatSum(items); - baseStats[1] += !chest.isExotic && config.addConstent1Health ? 1 : 0; - - // apply masterwork effects to baseStats (assumed idempotent) - for (const it of items) applyMasterworkStats(it, config, baseStats); - - // precompute targets and fixed flags - const targetVals: number[] = new Array(6); - const targetFixed: boolean[] = new Array(6); - for (let n: ArmorStat = 0; n < 6; n++) { - targetVals[n] = (config.minimumStatTiers[n].value || 0) * 10; - targetFixed[n] = !!config.minimumStatTiers[n].fixed; - } - - // stats without mods, and stats with mod bonuses - const statsWithoutMods: number[] = [...baseStats]; - const stats: number[] = baseStats.map((s, i) => s + (enabledModBonuses[i] || 0)); - - // early abort if fixed tiers exceeded - for (let n: ArmorStat = 0; n < 6; n++) { + const pc = prepared; + const targetVals = pc.targetVals; + const targetFixed = pc.targetFixed; + + // ── Inline stat summation + mod bonuses in one allocation ── + // Merges getStatSum(), health tweak, and enabledModBonuses into a single array. + // statsWithoutMods is deferred until we know we'll produce output. + const b0 = enabledModBonuses[0], + b1 = enabledModBonuses[1], + b2 = enabledModBonuses[2]; + const b3 = enabledModBonuses[3], + b4 = enabledModBonuses[4], + b5 = enabledModBonuses[5]; + + const stats: number[] = [ + helmet.mobility + gauntlet.mobility + chest.mobility + leg.mobility + classItem.mobility + b0, + helmet.resilience + + gauntlet.resilience + + chest.resilience + + leg.resilience + + classItem.resilience + + (!chest.isExotic && pc.addConstent1Health ? 1 : 0) + + b1, + helmet.recovery + gauntlet.recovery + chest.recovery + leg.recovery + classItem.recovery + b2, + helmet.discipline + + gauntlet.discipline + + chest.discipline + + leg.discipline + + classItem.discipline + + b3, + helmet.intellect + + gauntlet.intellect + + chest.intellect + + leg.intellect + + classItem.intellect + + b4, + helmet.strength + gauntlet.strength + chest.strength + leg.strength + classItem.strength + b5, + ]; + + // ── Masterwork stats + artifice counting in one pass (no items array, no .includes()) ── + let artificeCount = 0; + if (applyMWAndCheckArtifice(helmet, pc, stats)) artificeCount++; + if (applyMWAndCheckArtifice(gauntlet, pc, stats)) artificeCount++; + if (applyMWAndCheckArtifice(chest, pc, stats)) artificeCount++; + if (applyMWAndCheckArtifice(leg, pc, stats)) artificeCount++; + if (applyMWAndCheckArtifice(classItem, pc, stats)) artificeCount++; + + // ── Early abort: fixed tiers exceeded ── + for (let n = 0; n < 6; n++) { if (targetFixed[n] && stats[n] > targetVals[n]) return null; } - // count available artifice slots - const assumeEveryLegendaryIsArtifice = !!config.assumeEveryLegendaryIsArtifice; - const assumeEveryExoticIsArtifice = !!config.assumeEveryExoticIsArtifice; - let artificeCount = 0; - for (const d of items) { - if ( - d.perk == ArmorPerkOrSlot.SlotArtifice || - (d.armorSystem === ArmorSystem.Armor2 && - ((assumeEveryLegendaryIsArtifice && !d.isExotic) || - (assumeEveryExoticIsArtifice && d.isExotic))) - ) { - artificeCount++; + // ── Distances to target (using array literal for V8 SMI optimization) ── + const distances: number[] = [ + Math.max(0, targetVals[0] - stats[0]), + Math.max(0, targetVals[1] - stats[1]), + Math.max(0, targetVals[2] - stats[2]), + Math.max(0, targetVals[3] - stats[3]), + Math.max(0, targetVals[4] - stats[4]), + Math.max(0, targetVals[5] - stats[5]), + ]; + + if (pc.onlyShowResultsWithNoWastedStats) { + for (let stat = 0; stat < 6; stat++) { + const v = 10 - (stats[stat] % 10); + if (v < 10 && v > distances[stat]) distances[stat] = v; } } - // distances to target - const distances: number[] = new Array(6); - for (let n: ArmorStat = 0; n < 6; n++) distances[n] = Math.max(0, targetVals[n] - stats[n]); + // ── Quick distance sum check before T5 work ── + // This early check avoids computing T5 improvements and tuningMax when the + // total distance already exceeds the maximum possible from mods + artifice alone. + const distanceSum = + distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; - if (config.onlyShowResultsWithNoWastedStats) { - for (let stat: ArmorStat = 0; stat < 6; stat++) { - const v = 10 - (stats[stat] % 10); - distances[stat] = Math.max(distances[stat], v < 10 ? v : 0); + if (distanceSum > 50 + 3 * artificeCount) { + // Even with max T5 tuning (5 per item * 5 items = 25), still too far? + // This is a conservative pre-check; the full check follows after T5 computation. + if (!pc.calculateTierFiveTuning || distanceSum > 50 + 3 * artificeCount + 25) { + return null; } } - // T5 tuning improvements (all items including class item) + // ── T5 tuning improvements (without items array, with direct index comparisons) ── + let t5Count = 0; const t5Improvements: t5Improvement[] = []; const tuningMax: number[] = [0, 0, 0, 0, 0, 0]; - if (config.calculateTierFiveTuning) { - for (const item of items) { - if (isT5WithTuning(item)) t5Improvements.push(mapItemToTuning(item)); - } + if (pc.calculateTierFiveTuning) { + if (isT5WithTuning(helmet)) t5Improvements.push(mapItemToTuning(helmet)); + if (isT5WithTuning(gauntlet)) t5Improvements.push(mapItemToTuning(gauntlet)); + if (isT5WithTuning(chest)) t5Improvements.push(mapItemToTuning(chest)); + if (isT5WithTuning(leg)) t5Improvements.push(mapItemToTuning(leg)); + if (isT5WithTuning(classItem)) t5Improvements.push(mapItemToTuning(classItem)); + t5Count = t5Improvements.length; for (const t5 of t5Improvements) { - const mask = [false, false, false, false, false, false]; - for (const s of t5.archetypeStats) if (s >= 0 && s < 6) mask[s] = true; - const balanced: number[] = [0, 0, 0, 0, 0, 0]; - for (let i = 0; i < 6; i++) balanced[i] = mask[i] ? 0 : 1; + const arch = t5.archetypeStats; + const a0 = arch[0], + a1 = arch[1], + a2 = arch[2]; + // Compute balanced values inline (1 if stat NOT in archetypeStats) + const bal0 = a0 !== 0 && a1 !== 0 && a2 !== 0 ? 1 : 0; + const bal1 = a0 !== 1 && a1 !== 1 && a2 !== 1 ? 1 : 0; + const bal2 = a0 !== 2 && a1 !== 2 && a2 !== 2 ? 1 : 0; + const bal3 = a0 !== 3 && a1 !== 3 && a2 !== 3 ? 1 : 0; + const bal4 = a0 !== 4 && a1 !== 4 && a2 !== 4 ? 1 : 0; + const bal5 = a0 !== 5 && a1 !== 5 && a2 !== 5 ? 1 : 0; + const bal = [bal0, bal1, bal2, bal3, bal4, bal5]; + for (let n = 0; n < 6; n++) { if (n === t5.tuningStat) continue; - const p: number[] = [0, 0, 0, 0, 0, 0]; - p[t5.tuningStat] = 5; - p[n] = -5; - for (let i = 0; i < 6; i++) tuningMax[i] += Math.max(balanced[i], p[i]); + // p[tuningStat]=5, p[n]=-5, rest=0 → accumulate max(balanced[i], p[i]) + for (let i = 0; i < 6; i++) { + const pVal = i === t5.tuningStat ? 5 : i === n ? -5 : 0; + tuningMax[i] += Math.max(bal[i], pVal); + } } } } - // optional distances for waste limiting + // ── Full global bound check with T5 ── + if (distanceSum > 50 + 3 * artificeCount + 5 * t5Count) { + return null; + } + + // ── Optional distances for waste limiting ── const optionalDistances = [0, 0, 0, 0, 0, 0]; - if (config.tryLimitWastedStats) { - for (let stat: ArmorStat = 0; stat < 6; stat++) { + if (pc.tryLimitWastedStats) { + for (let stat = 0; stat < 6; stat++) { if ( distances[stat] === 0 && !targetFixed[stat] && @@ -810,9 +932,6 @@ export function handlePermutation( } } - // cheap global bound check - const distanceSum = - distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; const totalOptionalDistances = optionalDistances[0] + optionalDistances[1] + @@ -821,16 +940,8 @@ export function handlePermutation( optionalDistances[4] + optionalDistances[5]; - if (distanceSum > 10 * 5 + 3 * artificeCount + 5 * t5Improvements.length) { - return null; - } - - // mod caps - const maxMajorMods = config.statModLimits?.maxMajorMods || 0; - const maxMods = config.statModLimits?.maxMods || 0; - const possibleIncreaseByMod = 10 * maxMajorMods + 5 * Math.max(0, maxMods - maxMajorMods); - - // per-stat quick feasibility check + // ── Per-stat quick feasibility check (uses precomputed possibleIncreaseByMod) ── + const possibleIncreaseByMod = pc.possibleIncreaseByMod; for (let stat = 0; stat < 6; stat++) { if (possibleIncreaseByMod + tuningMax[stat] + 3 * artificeCount < distances[stat]) { return null; @@ -838,7 +949,7 @@ export function handlePermutation( } let availableTunings: Tuning[] = [[0, 0, 0, 0, 0, 0]]; - if (config.calculateTierFiveTuning) { + if (pc.calculateTierFiveTuning) { availableTunings = generate_tunings(t5Improvements); } @@ -850,11 +961,11 @@ export function handlePermutation( result = get_mods_precalc( stats, targetVals, - config, + prepared, distances, optionalDistances, artificeCount, - config.modOptimizationStrategy, + prepared.modOptimizationStrategy, availableTunings ); } @@ -863,7 +974,7 @@ export function handlePermutation( performTierAvailabilityTesting( runtime, - config, + prepared, stats, targetVals, distances, @@ -871,9 +982,19 @@ export function handlePermutation( availableTunings ); + // ── Deferred statsWithoutMods: only allocated when the permutation passes all checks ── + const statsWithoutMods: number[] = [ + stats[0] - b0, + stats[1] - b1, + stats[2] - b2, + stats[3] - b3, + stats[4] - b4, + stats[5] - b5, + ]; + return tryCreateArmorSetWithClassItem( runtime, - config, + prepared, helmet, gauntlet, chest, @@ -895,7 +1016,7 @@ function getStatVal(statId: ArmorStat, mods: StatModifierPrecalc, start: number) // region Tier Availability Testing function performTierAvailabilityTesting( runtime: any, - config: BuildConfiguration, + prepared: PreparedConfig, stats: number[], targetStats: number[], distances: number[], @@ -930,7 +1051,7 @@ function performTierAvailabilityTesting( if (minStat >= 200) continue; // Already at max value, no need to test - const minTier = config.minimumStatTiers[stat as ArmorStat].value * 10; + const minTier = prepared.minimumStatTierValues[stat] * 10; // Binary search to find maximum possible value let low = Math.max(runtime.maximumPossibleTiers[stat], minTier); @@ -954,7 +1075,7 @@ function performTierAvailabilityTesting( const mods = get_mods_precalc( stats, targetStats, - config, + prepared, testDistances, [0, 0, 0, 0, 0, 0], availableArtificeCount, @@ -979,7 +1100,7 @@ function performTierAvailabilityTesting( const mods = get_mods_precalc( stats, targetStats, - config, + prepared, testDistances, [0, 0, 0, 0, 0, 0], availableArtificeCount, @@ -1003,7 +1124,7 @@ function performTierAvailabilityTesting( function tryCreateArmorSetWithClassItem( runtime: any, - config: BuildConfiguration, + prepared: PreparedConfig, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, chest: IPermutatorArmor, @@ -1031,7 +1152,7 @@ function tryCreateArmorSetWithClassItem( for (let n = 0; n < 6; n++) finalStats[n] += result.tuning[n]; const waste1 = getWaste(finalStats); - if (config.onlyShowResultsWithNoWastedStats && waste1 > 0) return []; + if (prepared.onlyShowResultsWithNoWastedStats && waste1 > 0) return []; return createArmorSet( helmet, @@ -1172,7 +1293,7 @@ type StatModifierPrecalc = { function get_mods_precalc( currentStats: number[], targetStats: number[], - config: BuildConfiguration, + prepared: PreparedConfig, distances: number[], optionalDistances: number[], availableArtificeCount: number, @@ -1195,8 +1316,8 @@ function get_mods_precalc( availableTunings, 0, availableArtificeCount, - config.statModLimits.maxMajorMods, - config.statModLimits.maxMods + prepared.maxMajorMods, + prepared.maxMods ); if (pickedMods === null) return null; From 969fa7a736695e66b5356952e757d1cbe396b6a1 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sun, 22 Feb 2026 20:34:37 -0600 Subject: [PATCH 80/84] feat: improve constants assignment and reading for builder worker --- .../services/results-builder.worker.spec.ts | 784 ------------------ src/app/services/results-builder.worker.ts | 357 +++----- 2 files changed, 133 insertions(+), 1008 deletions(-) delete mode 100644 src/app/services/results-builder.worker.spec.ts diff --git a/src/app/services/results-builder.worker.spec.ts b/src/app/services/results-builder.worker.spec.ts deleted file mode 100644 index 6612aa63..00000000 --- a/src/app/services/results-builder.worker.spec.ts +++ /dev/null @@ -1,784 +0,0 @@ -/* - * Copyright (c) 2023 D2ArmorPicker by Mijago. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { getSkillTier, getWaste, handlePermutation } from "./results-builder.worker"; -import { DestinyClass, TierType } from "bungie-api-ts/destiny2"; -import { ArmorSlot } from "../data/enum/armor-slot"; -import { - ArmorPerkOrSlot, - ArmorStat, - ARMORSTAT_ORDER, - STAT_MOD_VALUES, - StatModifier, -} from "../data/enum/armor-stat"; -import { BuildConfiguration } from "../data/buildConfiguration"; -import { IInventoryArmor, InventoryArmorSource } from "../data/types/IInventoryArmor"; -import { IPermutatorArmor } from "../data/types/IPermutatorArmor"; -import { IPermutatorArmorSet } from "../data/types/IPermutatorArmorSet"; -import { - ResultDefinition, - ResultItem, -} from "../components/authenticated-v2/results/results.component"; - -const plugs = [ - [1, 1, 10], - [1, 1, 11], - [1, 1, 12], - [1, 1, 13], - [1, 1, 14], - [1, 1, 15], - [1, 5, 5], - [1, 5, 6], - [1, 5, 7], - [1, 5, 8], - [1, 5, 9], - [1, 5, 10], - [1, 5, 11], - [1, 6, 5], - [1, 6, 6], - [1, 6, 7], - [1, 6, 8], - [1, 6, 9], - [1, 7, 5], - [1, 7, 6], - [1, 7, 7], - [1, 7, 8], - [1, 8, 5], - [1, 8, 6], - [1, 8, 7], - [1, 9, 5], - [1, 9, 6], - [1, 10, 1], - [1, 10, 5], - [1, 11, 1], - [1, 11, 5], - [1, 12, 1], - [1, 13, 1], - [1, 14, 1], - [1, 15, 1], - [5, 1, 5], - [5, 1, 6], - [5, 1, 7], - [5, 1, 8], - [5, 1, 9], - [5, 1, 10], - [5, 1, 11], - [5, 5, 1], - [5, 5, 5], - [5, 6, 1], - [5, 7, 1], - [5, 8, 1], - [5, 9, 1], - [5, 10, 1], - [5, 11, 1], - [6, 1, 5], - [6, 1, 6], - [6, 1, 7], - [6, 1, 8], - [6, 1, 9], - [6, 5, 1], - [6, 6, 1], - [6, 7, 1], - [6, 8, 1], - [6, 9, 1], - [7, 1, 5], - [7, 1, 6], - [7, 1, 7], - [7, 1, 8], - [7, 5, 1], - [7, 6, 1], - [7, 7, 1], - [7, 8, 1], - [8, 1, 5], - [8, 1, 6], - [8, 1, 7], - [8, 5, 1], - [8, 6, 1], - [8, 7, 1], - [9, 1, 5], - [9, 1, 6], - [9, 5, 1], - [9, 6, 1], - [10, 1, 1], - [10, 1, 5], - [10, 5, 1], - [11, 1, 1], - [11, 1, 5], - [11, 5, 1], - [12, 1, 1], - [13, 1, 1], - [14, 1, 1], - [15, 1, 1], -]; - -function buildTestItem( - slot: ArmorSlot, - isExotic: boolean, - stats: number[], - perk: ArmorPerkOrSlot = ArmorPerkOrSlot.Any -): IInventoryArmor { - return { - name: "item_" + slot, - armorSystem: 3, - clazz: DestinyClass.Titan, - source: InventoryArmorSource.Inventory, - description: "", - slot: slot, - mobility: stats[0], - resilience: stats[1], - recovery: stats[2], - discipline: stats[3], - intellect: stats[4], - strength: stats[5], - energyLevel: 10, - hash: 0, - icon: "", - exoticPerkHash: 0, - id: 0, - investmentStats: [], - itemInstanceId: "", - isExotic: isExotic ? 1 : 0, - isSunset: false, - itemType: 0, - itemSubType: 0, - masterworked: true, - perk: perk, - mayBeBugged: false, - rarity: TierType.Superior, - rawData: undefined, - statPlugHashes: [], - socketEntries: [], - watermarkIcon: "", - created_at: Date.now(), - updated_at: Date.now(), - }; -} - -function generateRandomStats() { - // pick 4 random plugs - const randomPlugs = []; - for (let i = 0; i < 4; i++) { - randomPlugs.push(plugs[Math.floor(Math.random() * plugs.length)]); - } - - // calculate the stats - const stats = [ - randomPlugs[0][0] + randomPlugs[1][0], - randomPlugs[0][1] + randomPlugs[1][1], - randomPlugs[0][2] + randomPlugs[1][2], - randomPlugs[2][0] + randomPlugs[3][0], - randomPlugs[2][1] + randomPlugs[3][1], - randomPlugs[2][2] + randomPlugs[3][2], - ]; - return stats; -} - -function randomPerk() { - // pick random number - const random = Math.floor(Math.random() * 100); - if (random < 50) { - return ArmorPerkOrSlot.SlotArtifice; - } - return undefined; -} - -function generateRandomBuild() { - return [ - buildTestItem(ArmorSlot.ArmorSlotHelmet, false, generateRandomStats(), randomPerk()), - buildTestItem(ArmorSlot.ArmorSlotGauntlet, false, generateRandomStats(), randomPerk()), - buildTestItem(ArmorSlot.ArmorSlotChest, false, generateRandomStats(), randomPerk()), - buildTestItem(ArmorSlot.ArmorSlotLegs, false, generateRandomStats(), randomPerk()), - ]; -} - -function buildRuntime() { - return { - maximumPossibleTiers: [0, 0, 0, 0, 0, 0], - }; -} - -describe("Results Worker", () => { - it("should swap mods around to see replace old mods", () => { - // this is an edge case in which the artifice mod, which initially will be applied to - // mobility, must be moved to Recovery. Otherwise, this set would not be possible. - - const runtime = buildRuntime(); - - const mockItems: IInventoryArmor[] = [ - buildTestItem(ArmorSlot.ArmorSlotHelmet, false, [2, 12, 20, 20, 9, 2]), - buildTestItem(ArmorSlot.ArmorSlotGauntlet, false, [2, 30, 2, 26, 6, 2]), - buildTestItem(ArmorSlot.ArmorSlotChest, true, [2, 11, 21, 17, 10, 8]), - buildTestItem(ArmorSlot.ArmorSlotLegs, false, [2, 7, 24, 15, 15, 2]), - ]; - - const config = new BuildConfiguration(); - config.minimumStatTiers[ArmorStat.StatWeapon].value = 2; - config.minimumStatTiers[ArmorStat.StatHealth].value = 10; - config.minimumStatTiers[ArmorStat.StatClass].value = 8; - config.minimumStatTiers[ArmorStat.StatGrenade].value = 9; - config.minimumStatTiers[ArmorStat.StatSuper].value = 5; - config.minimumStatTiers[ArmorStat.StatMelee].value = 2; - - let presult = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - [0, 0, 0, 0, 0, 0], // constant bonus - [5, 5, 5, 1, 1], // availableModCost - false, // doNotOutput - true, // hasArtificeClassItem, - true // and masterwoked class item - ) as IPermutatorArmorSet; - let result = CreateResultDefinition(presult, mockItems); - expect(result).toBeDefined(); - expect(result.mods.length).toEqual(5); - expect(result.artifice.length).toEqual(1); - expect(result.stats[0]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatWeapon].value * 10 - ); - expect(result.stats[1]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatHealth].value * 10 - ); - expect(result.stats[2]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatClass].value * 10 - ); - expect(result.stats[3]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatGrenade].value * 10 - ); - expect(result.stats[4]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatSuper].value * 10 - ); - expect(result.stats[5]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatMelee].value * 10 - ); - }); - it("should swap 3x artifice mods around to replace old mods", () => { - // this is an edge case in which the artifice mod, which initially will be applied to - // mobility, must be moved to Recovery. Otherwise, this set would not be possible. - - const runtime = buildRuntime(); - - const mockItems: IInventoryArmor[] = [ - buildTestItem(ArmorSlot.ArmorSlotHelmet, true, [6, 27, 3, 19, 7, 6]), - buildTestItem( - ArmorSlot.ArmorSlotGauntlet, - false, - [2, 10, 21, 24, 2, 7], - ArmorPerkOrSlot.SlotArtifice - ), - buildTestItem( - ArmorSlot.ArmorSlotChest, - false, - [6, 2, 23, 28, 2, 2], - ArmorPerkOrSlot.SlotArtifice - ), - buildTestItem( - ArmorSlot.ArmorSlotLegs, - false, - [11, 12, 10, 21, 8, 2], - ArmorPerkOrSlot.SlotArtifice - ), - ]; - - const config = new BuildConfiguration(); - config.minimumStatTiers[ArmorStat.StatWeapon].value = 6; - config.minimumStatTiers[ArmorStat.StatHealth].value = 6; - config.minimumStatTiers[ArmorStat.StatClass].value = 10; - config.minimumStatTiers[ArmorStat.StatGrenade].value = 10; - config.minimumStatTiers[ArmorStat.StatSuper].value = 0; - config.minimumStatTiers[ArmorStat.StatMelee].value = 0; - - let presult = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - [0, 0, 0, 0, 0, 0], // constant bonus - [5, 5, 5, 5, 5], // availableModCost - false, // doNotOutput - true, // hasArtificeClassItem - true // and masterwoked class item - ) as IPermutatorArmorSet; - let result = CreateResultDefinition(presult, mockItems); - expect(result).toBeDefined(); - expect(result.stats[0]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatWeapon].value * 10 - ); - expect(result.stats[1]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatHealth].value * 10 - ); - expect(result.stats[2]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatClass].value * 10 - ); - expect(result.stats[3]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatGrenade].value * 10 - ); - expect(result.stats[4]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatSuper].value * 10 - ); - expect(result.stats[5]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatMelee].value * 10 - ); - }); - it("should swap 3x artifice mods around to replace old mods v2", () => { - // this is an edge case in which the artifice mod, which initially will be applied to - // mobility, must be moved to Recovery. Otherwise, this set would not be possible. - - const runtime = buildRuntime(); - - const mockItems: IInventoryArmor[] = [ - buildTestItem( - ArmorSlot.ArmorSlotHelmet, - false, - [13, 16, 2, 24, 2, 7], - ArmorPerkOrSlot.SlotArtifice - ), - buildTestItem( - ArmorSlot.ArmorSlotGauntlet, - false, - [26, 6, 2, 26, 2, 2], - ArmorPerkOrSlot.SlotArtifice - ), - buildTestItem(ArmorSlot.ArmorSlotChest, true, [6, 24, 2, 17, 7, 7]), - buildTestItem( - ArmorSlot.ArmorSlotLegs, - false, - [22, 9, 2, 24, 2, 6], - ArmorPerkOrSlot.SlotArtifice - ), - ]; - - const config = new BuildConfiguration(); - config.minimumStatTiers[ArmorStat.StatWeapon].value = 9; - config.minimumStatTiers[ArmorStat.StatHealth].value = 10; - config.minimumStatTiers[ArmorStat.StatClass].value = 0; - config.minimumStatTiers[ArmorStat.StatGrenade].value = 10; - config.minimumStatTiers[ArmorStat.StatSuper].value = 0; - config.minimumStatTiers[ArmorStat.StatMelee].value = 0; - - const enabledModBonuses = [-10, 0, 10, 0, 0, -10]; - let presult = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - enabledModBonuses, // enabled mod bonuses - [5, 5, 5, 5, 5], // availableModCost - false, // doNotOutput - true, // hasArtificeClassItem - true // and masterwoked class item - ) as IPermutatorArmorSet; - let result = CreateResultDefinition(presult, mockItems); - expect(result).toBeDefined(); - console.log(result); - expect(result.stats[0]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatWeapon].value * 10 - ); - expect(result.stats[1]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatHealth].value * 10 - ); - expect(result.stats[2]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatClass].value * 10 - ); - expect(result.stats[3]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatGrenade].value * 10 - ); - expect(result.stats[4]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatSuper].value * 10 - ); - expect(result.stats[5]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatMelee].value * 10 - ); - - for (let n = 0; n < 6; n++) { - const minor = - 1 * result.mods.filter((mod: number) => Math.floor(mod / 3) == n && mod % 3 == 1).length; - const major = - 1 * result.mods.filter((mod: number) => Math.floor(mod / 3) == n && mod % 3 == 2).length; - const artif = - 1 * - result.artifice.filter((mod: number) => Math.floor(mod / 3) - 1 == n && mod % 3 == 0) - .length; - expect(result.stats[n]).toEqual( - result.statsNoMods[n] + 5 * minor + 10 * major + 3 * artif + enabledModBonuses[n] - ); - } - }); - - it("should be able to keep plain zero-waste builds", () => { - const runtime = buildRuntime(); - - const mockItems: IInventoryArmor[] = [ - buildTestItem(ArmorSlot.ArmorSlotHelmet, false, [8, 9, 16, 23, 2, 8]), - buildTestItem(ArmorSlot.ArmorSlotGauntlet, false, [2, 9, 20, 26, 6, 2]), - buildTestItem(ArmorSlot.ArmorSlotChest, true, [7, 2, 23, 21, 10, 2]), - buildTestItem(ArmorSlot.ArmorSlotLegs, false, [3, 20, 11, 20, 2, 8]), - ]; - - const config = BuildConfiguration.buildEmptyConfiguration(); - config.tryLimitWastedStats = true; - config.onlyShowResultsWithNoWastedStats = true; - - let result = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - [0, 0, 0, 0, 0, 0], // constant bonus - [5, 5, 5, 5, 5], // availableModCost - false, // doNotOutput - true, // hasArtificeClassItem - true // and masterwoked class item - ); - expect(result).toBeDefined(); - expect(result).not.toBeNull(); - }); - - it("should be able to solve complex zero-waste builds", () => { - // this is an edge case in which the artifice mod, which initially will be applied to - // mobility, must be moved to Recovery. Otherwise, this set would not be possible. - - const runtime = buildRuntime(); - - const mockItems: IInventoryArmor[] = [ - buildTestItem(ArmorSlot.ArmorSlotHelmet, false, [8, 9, 16, 23, 2, 8]), - buildTestItem( - ArmorSlot.ArmorSlotGauntlet, - false, - [2, 9, 20, 26, 6, 2], - ArmorPerkOrSlot.SlotArtifice - ), - buildTestItem( - ArmorSlot.ArmorSlotChest, - false, - [7, 2, 23, 21, 10, 2], - ArmorPerkOrSlot.SlotArtifice - ), - buildTestItem(ArmorSlot.ArmorSlotLegs, true, [3, 20, 11, 20, 2, 8]), - ]; - - // the numbers currently sum to 0; now we artifically reduce them to enforce wasted stats calculation - mockItems[0].mobility -= 0; - mockItems[0].resilience -= 5 + 3 + 3; // minor mod + two artifice mods - mockItems[0].recovery -= 5; // minor mod - mockItems[0].discipline -= 5; // minor mod - mockItems[0].intellect -= 5; // minor mod - mockItems[0].strength -= 5 + 3; // minor mod + artifice mod - - const config = new BuildConfiguration(); - config.tryLimitWastedStats = true; - config.onlyShowResultsWithNoWastedStats = true; - - let presult = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - [0, 0, 0, 0, 0, 0], // constant bonus - [5, 5, 5, 5, 5], // availableModCost - false, // doNotOutput - true, // hasArtificeClassItem - true // and masterwoked class item - ) as IPermutatorArmorSet; - let result = CreateResultDefinition(presult, mockItems); - expect(result).toBeDefined(); - expect(result).not.toBeNull(); - expect(result.waste).toEqual(0); - }); - - it("should be able to give correct build presets", () => { - // this is an edge case in which the artifice mod, which initially will be applied to - // mobility, must be moved to Recovery. Otherwise, this set would not be possible. - - for (let n = 0; n < 10000; n++) { - let runtime = buildRuntime(); - const mockItems = generateRandomBuild(); - - const config = new BuildConfiguration(); - config.tryLimitWastedStats = true; - //config.onlyShowResultsWithNoWastedStats = true - - const enabledModBonuses1 = [0, 0, 0, 0, 0, 0]; - let availableModCost = [ - // random 0-5 - Math.floor(Math.random() * 6), - Math.floor(Math.random() * 6), - Math.floor(Math.random() * 6), - Math.floor(Math.random() * 6), - Math.floor(Math.random() * 6), - ]; - availableModCost = [5, 5, 5, 5, 5]; - handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - enabledModBonuses1, - availableModCost, - false, - true, // hasArtificeClassItem - true // and masterwoked class item - ); - - // grab the runtime.maximumPossibleTiers and iterate over them to see if it correctly fills them - // first, pick a random order - const order = ARMORSTAT_ORDER.sort(() => Math.random() - 0.5); - - for (let statId of order) { - config.minimumStatTiers[statId as ArmorStat].value = - runtime.maximumPossibleTiers[statId] / 10; - - runtime = buildRuntime(); - let presult = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - enabledModBonuses1, - availableModCost, - false, - true, // hasArtificeClassItem - true // and masterwoked class item - ) as IPermutatorArmorSet; - let result = CreateResultDefinition(presult, mockItems); - expect(result).toBeDefined(); - expect(result).not.toBeNull(); - expect(result.mods.length).toBeLessThanOrEqual(5); - if (!result) { - console.log("Failed to find a build with minimumStatTiers", config.minimumStatTiers); - console.log("RUN", n); - console.log("availableModCost", availableModCost); - console.log("base stats", [ - 10 + - mockItems[0].mobility + - mockItems[1].mobility + - mockItems[2].mobility + - mockItems[3].mobility, - 10 + - mockItems[0].resilience + - mockItems[1].resilience + - mockItems[2].resilience + - mockItems[3].resilience, - 10 + - mockItems[0].recovery + - mockItems[1].recovery + - mockItems[2].recovery + - mockItems[3].recovery, - 10 + - mockItems[0].discipline + - mockItems[1].discipline + - mockItems[2].discipline + - mockItems[3].discipline, - 10 + - mockItems[0].intellect + - mockItems[1].intellect + - mockItems[2].intellect + - mockItems[3].intellect, - 10 + - mockItems[0].strength + - mockItems[1].strength + - mockItems[2].strength + - mockItems[3].strength, - ]); - console.log("target stats", [ - config.minimumStatTiers[ArmorStat.StatWeapon].value * 10, - config.minimumStatTiers[ArmorStat.StatHealth].value * 10, - config.minimumStatTiers[ArmorStat.StatClass].value * 10, - config.minimumStatTiers[ArmorStat.StatGrenade].value * 10, - config.minimumStatTiers[ArmorStat.StatSuper].value * 10, - config.minimumStatTiers[ArmorStat.StatMelee].value * 10, - ]); - console.log( - "Available artifice mods", - mockItems.map((item) => (item.perk > 0 ? 1 : 0)).reduce((a, b) => a + b, 0 as number) - ); - console.log("------------------------------------------------------------------------"); - console.log("------------------------------------------------------------------------"); - console.log("------------------------------------------------------------------------"); - break; - } - } - } - }); - - it("should swap mods around", () => { - // this is an edge case in which the artifice mod, which initially will be applied to - // mobility, must be moved to Recovery. Otherwise, this set would not be possible. - - const runtime = buildRuntime(); - - const mockItems: IInventoryArmor[] = [ - buildTestItem(ArmorSlot.ArmorSlotHelmet, false, [13, 14, 4, 17, 9, 8]), - buildTestItem(ArmorSlot.ArmorSlotGauntlet, false, [8, 16, 11, 22, 4, 14]), - buildTestItem(ArmorSlot.ArmorSlotChest, true, [9, 13, 10, 18, 4, 8]), - buildTestItem(ArmorSlot.ArmorSlotLegs, false, [19, 4, 9, 12, 4, 17]), - ]; - - const config = new BuildConfiguration(); - config.assumeLegendariesMasterworked = true; - config.assumeExoticsMasterworked = true; - config.minimumStatTiers[ArmorStat.StatWeapon].value = 0; - config.minimumStatTiers[ArmorStat.StatHealth].value = 9; - config.minimumStatTiers[ArmorStat.StatClass].value = 6; - config.minimumStatTiers[ArmorStat.StatGrenade].value = 7; - config.minimumStatTiers[ArmorStat.StatSuper].value = 0; - config.minimumStatTiers[ArmorStat.StatMelee].value = 0; - - // calculate the stat sum of mockItems - const statSum = [ - mockItems[0].mobility + mockItems[1].mobility + mockItems[2].mobility + mockItems[3].mobility, - mockItems[0].resilience + - mockItems[1].resilience + - mockItems[2].resilience + - mockItems[3].resilience, - mockItems[0].recovery + mockItems[1].recovery + mockItems[2].recovery + mockItems[3].recovery, - mockItems[0].discipline + - mockItems[1].discipline + - mockItems[2].discipline + - mockItems[3].discipline, - mockItems[0].intellect + - mockItems[1].intellect + - mockItems[2].intellect + - mockItems[3].intellect, - mockItems[0].strength + mockItems[1].strength + mockItems[2].strength + mockItems[3].strength, - ]; - console.log("statSum", statSum); - - //const enabledModBonuses = [-10, -10, -10, -10, -10, -10]; - const enabledModBonuses = [0, 0, 0, 0, 0, 0]; - let presult = handlePermutation( - runtime, - config, - mockItems[0] as IPermutatorArmor, - mockItems[1] as IPermutatorArmor, - mockItems[2] as IPermutatorArmor, - mockItems[3] as IPermutatorArmor, - enabledModBonuses, // enabled mod bonuses - [5, 5, 5, 5, 5], // availableModCost - false, // doNotOutput - true, // hasArtificeClassItem - true // and masterwoked class item - ) as IPermutatorArmorSet; - let result = CreateResultDefinition(presult, mockItems); - expect(result).toBeDefined(); - console.log(result); - expect(result.mods.length).toBeLessThanOrEqual(5); - expect(result.stats[0]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatWeapon].value * 10 - ); - expect(result.stats[1]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatHealth].value * 10 - ); - expect(result.stats[2]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatClass].value * 10 - ); - expect(result.stats[3]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatGrenade].value * 10 - ); - expect(result.stats[4]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatSuper].value * 10 - ); - expect(result.stats[5]).toBeGreaterThanOrEqual( - config.minimumStatTiers[ArmorStat.StatMelee].value * 10 - ); - - for (let n = 0; n < 6; n++) { - const minor = - 1 * result.mods.filter((mod: number) => Math.floor(mod / 3) == n && mod % 3 == 1).length; - const major = - 1 * result.mods.filter((mod: number) => Math.floor(mod / 3) == n && mod % 3 == 2).length; - const artif = - 1 * - result.artifice.filter((mod: number) => Math.floor(mod / 3) - 1 == n && mod % 3 == 0) - .length; - expect(result.stats[n]).toEqual( - result.statsNoMods[n] + 5 * minor + 10 * major + 3 * artif + enabledModBonuses[n] - ); - } - }); -}); - -function CreateResultDefinition( - armorSet: IPermutatorArmorSet, - items: IInventoryArmor[] -): ResultDefinition { - let exotic = items.find((x) => x.isExotic); - - if (armorSet == null) { - console.error("ArmorSet is null", items); - } - - return { - exotic: - exotic == null - ? undefined - : { - icon: exotic?.icon, - watermark: exotic?.watermarkIcon, - name: exotic?.name, - hash: exotic?.hash, - }, - artifice: armorSet.usedArtifice, - modCount: armorSet.usedMods.length, - modCost: armorSet.usedMods.reduce((p, d: StatModifier) => p + STAT_MOD_VALUES[d][2], 0), - mods: armorSet.usedMods, - stats: armorSet.statsWithMods, - statsNoMods: armorSet.statsWithoutMods, - tiers: getSkillTier(armorSet.statsWithMods), - waste: getWaste(armorSet.statsWithMods), - items: items.map( - (instance): ResultItem => ({ - energyLevel: instance.energyLevel, - hash: instance.hash, - itemInstanceId: instance.itemInstanceId, - name: instance.name, - exotic: !!instance.isExotic, - masterworked: instance.masterworked, - slot: instance.slot, - perk: instance.perk, - transferState: 0, // TRANSFER_NONE - stats: [ - instance.mobility, - instance.resilience, - instance.recovery, - instance.discipline, - instance.intellect, - instance.strength, - ], - source: instance.source, - statsNoMods: [], - }) - ), - usesCollectionRoll: items.some((v) => v.source === InventoryArmorSource.Collections), - usesVendorRoll: items.some((v) => v.source === InventoryArmorSource.Vendor), - } as ResultDefinition; -} diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 2530a648..28492271 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -36,7 +36,6 @@ import { import { environment } from "../../environments/environment"; -import { ModOptimizationStrategy } from "../data/enum/mod-optimization-strategy"; import { IPermutatorArmor } from "../data/types/IPermutatorArmor"; import { IPermutatorArmorSet, @@ -48,7 +47,33 @@ import { ArmorSystem } from "../data/types/IManifestArmor"; import { precalculatedTuningModCombinations } from "../data/generated/precalculatedModCombinationsWithTunings"; -// endregion Imports +let runtime: { + maximumPossibleTiers: number[]; +} = { + maximumPossibleTiers: [0, 0, 0, 0, 0, 0], +}; + +// Module-level configuration to avoid passing around +let assumeEveryLegendaryIsArtifice: boolean; +let assumeEveryExoticIsArtifice: boolean; +let assumeClassItemIsArtifice: boolean; +let calculateTierFiveTuning: boolean; +let onlyShowResultsWithNoWastedStats: boolean; +let tryLimitWastedStats: boolean; +let addConstent1Health: boolean; +let assumeExoticsMasterworked: boolean; +let assumeLegendariesMasterworked: boolean; +let maxMajorMods: number; +let maxMods: number; +let minimumStatTierValues: number[]; + +// Module-level constants for performance +let enabledModBonuses: number[]; +let requiredPerkSlotCounts: Map; +let targetVals: number[]; +let targetFixed: boolean[]; +let possibleIncreaseByMod: number; +let doNotOutput: boolean = false; type t5Improvement = { tuningStat: ArmorStat; @@ -71,74 +96,17 @@ function mapItemToTuning(i: IPermutatorArmor): t5Improvement { }; } -// Precomputed configuration values that remain constant across all permutations. -// Computing these once and passing them in avoids redundant property lookups, -// array allocations, and repeated arithmetic on every call to handlePermutation. -export interface PreparedConfig { - targetVals: number[]; - targetFixed: boolean[]; - maxMajorMods: number; - maxMods: number; - possibleIncreaseByMod: number; - assumeEveryLegendaryIsArtifice: boolean; - assumeEveryExoticIsArtifice: boolean; - assumeClassItemIsArtifice: boolean; - calculateTierFiveTuning: boolean; - onlyShowResultsWithNoWastedStats: boolean; - tryLimitWastedStats: boolean; - addConstent1Health: boolean; - assumeExoticsMasterworked: boolean; - assumeLegendariesMasterworked: boolean; - minimumStatTierValues: number[]; - modOptimizationStrategy: ModOptimizationStrategy; -} - -export function prepareConfig(config: BuildConfiguration): PreparedConfig { - const targetVals: number[] = [0, 0, 0, 0, 0, 0]; - const targetFixed: boolean[] = [false, false, false, false, false, false]; - const minimumStatTierValues: number[] = [0, 0, 0, 0, 0, 0]; - for (let n = 0; n < 6; n++) { - targetVals[n] = (config.minimumStatTiers[n as ArmorStat].value || 0) * 10; - targetFixed[n] = !!config.minimumStatTiers[n as ArmorStat].fixed; - minimumStatTierValues[n] = config.minimumStatTiers[n as ArmorStat].value || 0; - } - const maxMajorMods = config.statModLimits?.maxMajorMods || 0; - const maxMods = config.statModLimits?.maxMods || 0; - return { - targetVals, - targetFixed, - maxMajorMods, - maxMods, - possibleIncreaseByMod: 10 * maxMajorMods + 5 * Math.max(0, maxMods - maxMajorMods), - assumeEveryLegendaryIsArtifice: !!config.assumeEveryLegendaryIsArtifice, - assumeEveryExoticIsArtifice: !!config.assumeEveryExoticIsArtifice, - assumeClassItemIsArtifice: !!config.assumeClassItemIsArtifice, - calculateTierFiveTuning: !!config.calculateTierFiveTuning, - onlyShowResultsWithNoWastedStats: !!config.onlyShowResultsWithNoWastedStats, - tryLimitWastedStats: !!config.tryLimitWastedStats, - addConstent1Health: !!config.addConstent1Health, - assumeExoticsMasterworked: !!config.assumeExoticsMasterworked, - assumeLegendariesMasterworked: !!config.assumeLegendariesMasterworked, - minimumStatTierValues, - modOptimizationStrategy: config.modOptimizationStrategy, - }; -} - /** * Applies masterwork stat bonuses to the stats array and returns whether the item * counts as an artifice slot. Combines two operations that were previously separate * loops over an items array. Uses direct index comparisons instead of .includes() * for archetypeStats (always exactly 3 elements). */ -function applyMWAndCheckArtifice( - item: IPermutatorArmor, - pc: PreparedConfig, - stats: number[] -): boolean { +function applyMWAndCheckArtifice(item: IPermutatorArmor, stats: number[]): boolean { if (item.armorSystem === ArmorSystem.Armor2) { if ( item.masterworkLevel === MAXIMUM_MASTERWORK_LEVEL || - (item.isExotic ? pc.assumeExoticsMasterworked : pc.assumeLegendariesMasterworked) + (item.isExotic ? assumeExoticsMasterworked : assumeLegendariesMasterworked) ) { stats[0] += 2; stats[1] += 2; @@ -149,12 +117,12 @@ function applyMWAndCheckArtifice( } return ( item.perk === ArmorPerkOrSlot.SlotArtifice || - (item.isExotic ? pc.assumeEveryExoticIsArtifice : pc.assumeEveryLegendaryIsArtifice) + (item.isExotic ? assumeEveryExoticIsArtifice : assumeEveryLegendaryIsArtifice) ); } if (item.armorSystem === ArmorSystem.Armor3) { let mult = item.masterworkLevel; - if (item.isExotic ? pc.assumeExoticsMasterworked : pc.assumeLegendariesMasterworked) + if (item.isExotic ? assumeExoticsMasterworked : assumeLegendariesMasterworked) mult = MAXIMUM_MASTERWORK_LEVEL; if (mult > 0) { const a = item.archetypeStats; @@ -175,8 +143,6 @@ function applyMWAndCheckArtifice( // region Validation and Preparation Functions function checkSlots( - prepared: PreparedConfig, - requiredPerkSlotCounts: Map, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, chest: IPermutatorArmor, @@ -191,10 +157,10 @@ function checkSlots( if (item.armorSystem === ArmorSystem.Armor2) { if ( - (item.isExotic && prepared.assumeEveryExoticIsArtifice) || + (item.isExotic && assumeEveryExoticIsArtifice) || (!item.isExotic && - (prepared.assumeEveryLegendaryIsArtifice || - (item.slot == ArmorSlot.ArmorSlotClass && prepared.assumeClassItemIsArtifice))) + (assumeEveryLegendaryIsArtifice || + (item.slot == ArmorSlot.ArmorSlotClass && assumeClassItemIsArtifice))) ) { effectivePerk = ArmorPerkOrSlot.SlotArtifice; } @@ -396,9 +362,7 @@ function estimateCombinationsToBeChecked( // endregion Validation and Preparation Functions // region Main Worker Event Handler -addEventListener("message", async ({ data }) => { - if (data.type != "builderRequest") return; - +async function handleArmorBuilderRequest(data: any): Promise { const threadSplit = data.threadSplit as { count: number; current: number }; const config = data.config as BuildConfiguration; const anyStatFixed = Object.values(config.minimumStatTiers).some( @@ -412,7 +376,7 @@ addEventListener("message", async ({ data }) => { const startTime = Date.now(); console.log(`Thread ${threadSplit.current} started with ${items.length} items to process.`); - console.time(`Total run thread#${threadSplit.current}`); + console.time(`Total run thread #${threadSplit.current}`); // toggle feature flags config.onlyShowResultsWithNoWastedStats = environment.featureFlags.enableZeroWaste && config.onlyShowResultsWithNoWastedStats; @@ -582,20 +546,40 @@ addEventListener("message", async ({ data }) => { } } - // runtime variables - const runtime = { - maximumPossibleTiers: [0, 0, 0, 0, 0, 0], - }; + // Reset runtime state for this calculation + runtime.maximumPossibleTiers = [0, 0, 0, 0, 0, 0]; + + // Initialize module-level constants directly from config + enabledModBonuses = computeEnabledModBonuses(config); + requiredPerkSlotCounts = calculateRequiredPerkCounts(config); - const enabledModBonuses = computeEnabledModBonuses(config); - const requiredPerkSlotCounts = calculateRequiredPerkCounts(config); + // Initialize target values and configuration flags + targetVals = [0, 0, 0, 0, 0, 0]; + targetFixed = [false, false, false, false, false, false]; + minimumStatTierValues = [0, 0, 0, 0, 0, 0]; + for (let n = 0; n < 6; n++) { + targetVals[n] = (config.minimumStatTiers[n as ArmorStat].value || 0) * 10; + targetFixed[n] = !!config.minimumStatTiers[n as ArmorStat].fixed; + minimumStatTierValues[n] = config.minimumStatTiers[n as ArmorStat].value || 0; + } + maxMajorMods = config.statModLimits?.maxMajorMods || 0; + maxMods = config.statModLimits?.maxMods || 0; + possibleIncreaseByMod = 10 * maxMajorMods + 5 * Math.max(0, maxMods - maxMajorMods); + assumeEveryLegendaryIsArtifice = !!config.assumeEveryLegendaryIsArtifice; + assumeEveryExoticIsArtifice = !!config.assumeEveryExoticIsArtifice; + assumeClassItemIsArtifice = !!config.assumeClassItemIsArtifice; + calculateTierFiveTuning = !!config.calculateTierFiveTuning; + onlyShowResultsWithNoWastedStats = !!config.onlyShowResultsWithNoWastedStats; + tryLimitWastedStats = !!config.tryLimitWastedStats; + addConstent1Health = !!config.addConstent1Health; + assumeExoticsMasterworked = !!config.assumeExoticsMasterworked; + assumeLegendariesMasterworked = !!config.assumeLegendariesMasterworked; let results: IPermutatorArmorSet[] = []; let resultsLength = 0; let listedResults = 0; let totalResults = 0; - let doNotOutput = false; // Determine exotic combination mode from selectedExotics: // - FORCE_USE_ANY_EXOTIC or specific exotic hash(es): yield only 1-exotic combinations @@ -627,8 +611,7 @@ addEventListener("message", async ({ data }) => { // if the estimated calculations >= 1e6, then we will use 125ms let progressBarDelay = estimatedCalculations >= 1e6 ? 500 : 125; - // Precompute config-invariant values once before the hot loop - const prepared = prepareConfig(config); + doNotOutput = false; for (let [helmet, gauntlet, chest, leg, classItem] of generateArmorCombinations( helmets, @@ -640,20 +623,9 @@ addEventListener("message", async ({ data }) => { yieldAllLegendary )) { checkedCalculations++; - if (!checkSlots(prepared, requiredPerkSlotCounts, helmet, gauntlet, chest, leg, classItem)) - continue; - - const result = handlePermutation( - runtime, - helmet, - gauntlet, - chest, - leg, - classItem, - enabledModBonuses, - doNotOutput, - prepared - ); + if (!checkSlots(helmet, gauntlet, chest, leg, classItem)) continue; + + const result = handlePermutation(helmet, gauntlet, chest, leg, classItem); // Only add 50k to the list if the setting is activated. // We will still calculate the rest so that we get accurate results for the runtime values if (isIPermutatorArmorSet(result)) { @@ -663,7 +635,6 @@ addEventListener("message", async ({ data }) => { resultsLength++; listedResults++; doNotOutput = - doNotOutput || (config.limitParsedResults && listedResults >= 3e4 / threadSplit.count) || listedResults >= 1e6 / threadSplit.count; } @@ -700,6 +671,17 @@ addEventListener("message", async ({ data }) => { totalTime: Date.now() - startTime, }, }); +} + +addEventListener("message", async ({ data }) => { + switch (data.type) { + case "builderRequest": + await handleArmorBuilderRequest(data); + break; + default: + console.warn(`Unknown message type: ${data.type}`); + break; + } }); // endregion Main Worker Event Handler @@ -781,27 +763,17 @@ function generate_tunings(possibleImprovements: t5Improvement[]): Tuning[] { } export function handlePermutation( - runtime: any, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, chest: IPermutatorArmor, leg: IPermutatorArmor, - classItem: IPermutatorArmor, - enabledModBonuses: number[], - doNotOutput = false, - prepared: PreparedConfig -): never[] | IPermutatorArmorSet | null { - const pc = prepared; - const targetVals = pc.targetVals; - const targetFixed = pc.targetFixed; - - // ── Inline stat summation + mod bonuses in one allocation ── - // Merges getStatSum(), health tweak, and enabledModBonuses into a single array. - // statsWithoutMods is deferred until we know we'll produce output. + classItem: IPermutatorArmor +): IPermutatorArmorSet | null { + // Inline stat summation + mod bonuses in one allocation const b0 = enabledModBonuses[0], b1 = enabledModBonuses[1], - b2 = enabledModBonuses[2]; - const b3 = enabledModBonuses[3], + b2 = enabledModBonuses[2], + b3 = enabledModBonuses[3], b4 = enabledModBonuses[4], b5 = enabledModBonuses[5]; @@ -812,7 +784,7 @@ export function handlePermutation( chest.resilience + leg.resilience + classItem.resilience + - (!chest.isExotic && pc.addConstent1Health ? 1 : 0) + + (!chest.isExotic && addConstent1Health ? 1 : 0) + b1, helmet.recovery + gauntlet.recovery + chest.recovery + leg.recovery + classItem.recovery + b2, helmet.discipline + @@ -830,20 +802,19 @@ export function handlePermutation( helmet.strength + gauntlet.strength + chest.strength + leg.strength + classItem.strength + b5, ]; - // ── Masterwork stats + artifice counting in one pass (no items array, no .includes()) ── let artificeCount = 0; - if (applyMWAndCheckArtifice(helmet, pc, stats)) artificeCount++; - if (applyMWAndCheckArtifice(gauntlet, pc, stats)) artificeCount++; - if (applyMWAndCheckArtifice(chest, pc, stats)) artificeCount++; - if (applyMWAndCheckArtifice(leg, pc, stats)) artificeCount++; - if (applyMWAndCheckArtifice(classItem, pc, stats)) artificeCount++; + if (applyMWAndCheckArtifice(helmet, stats)) artificeCount++; + if (applyMWAndCheckArtifice(gauntlet, stats)) artificeCount++; + if (applyMWAndCheckArtifice(chest, stats)) artificeCount++; + if (applyMWAndCheckArtifice(leg, stats)) artificeCount++; + if (applyMWAndCheckArtifice(classItem, stats)) artificeCount++; - // ── Early abort: fixed tiers exceeded ── + // Early abort: fixed tiers exceeded for (let n = 0; n < 6; n++) { if (targetFixed[n] && stats[n] > targetVals[n]) return null; } - // ── Distances to target (using array literal for V8 SMI optimization) ── + // Distances to target (using array literal for V8 SMI optimization) const distances: number[] = [ Math.max(0, targetVals[0] - stats[0]), Math.max(0, targetVals[1] - stats[1]), @@ -853,14 +824,14 @@ export function handlePermutation( Math.max(0, targetVals[5] - stats[5]), ]; - if (pc.onlyShowResultsWithNoWastedStats) { + if (onlyShowResultsWithNoWastedStats) { for (let stat = 0; stat < 6; stat++) { const v = 10 - (stats[stat] % 10); if (v < 10 && v > distances[stat]) distances[stat] = v; } } - // ── Quick distance sum check before T5 work ── + // Quick distance sum check before T5 work // This early check avoids computing T5 improvements and tuningMax when the // total distance already exceeds the maximum possible from mods + artifice alone. const distanceSum = @@ -869,17 +840,17 @@ export function handlePermutation( if (distanceSum > 50 + 3 * artificeCount) { // Even with max T5 tuning (5 per item * 5 items = 25), still too far? // This is a conservative pre-check; the full check follows after T5 computation. - if (!pc.calculateTierFiveTuning || distanceSum > 50 + 3 * artificeCount + 25) { + if (!calculateTierFiveTuning || distanceSum > 50 + 3 * artificeCount + 25) { return null; } } - // ── T5 tuning improvements (without items array, with direct index comparisons) ── + // T5 tuning improvements (without items array, with direct index comparisons) let t5Count = 0; const t5Improvements: t5Improvement[] = []; const tuningMax: number[] = [0, 0, 0, 0, 0, 0]; - if (pc.calculateTierFiveTuning) { + if (calculateTierFiveTuning) { if (isT5WithTuning(helmet)) t5Improvements.push(mapItemToTuning(helmet)); if (isT5WithTuning(gauntlet)) t5Improvements.push(mapItemToTuning(gauntlet)); if (isT5WithTuning(chest)) t5Improvements.push(mapItemToTuning(chest)); @@ -912,14 +883,14 @@ export function handlePermutation( } } - // ── Full global bound check with T5 ── + // Full global bound check with T5 if (distanceSum > 50 + 3 * artificeCount + 5 * t5Count) { return null; } - // ── Optional distances for waste limiting ── + // Optional distances for waste limiting const optionalDistances = [0, 0, 0, 0, 0, 0]; - if (pc.tryLimitWastedStats) { + if (tryLimitWastedStats) { for (let stat = 0; stat < 6; stat++) { if ( distances[stat] === 0 && @@ -940,8 +911,7 @@ export function handlePermutation( optionalDistances[4] + optionalDistances[5]; - // ── Per-stat quick feasibility check (uses precomputed possibleIncreaseByMod) ── - const possibleIncreaseByMod = pc.possibleIncreaseByMod; + // Per-stat quick feasibility check (uses precomputed possibleIncreaseByMod) for (let stat = 0; stat < 6; stat++) { if (possibleIncreaseByMod + tuningMax[stat] + 3 * artificeCount < distances[stat]) { return null; @@ -949,7 +919,7 @@ export function handlePermutation( } let availableTunings: Tuning[] = [[0, 0, 0, 0, 0, 0]]; - if (pc.calculateTierFiveTuning) { + if (calculateTierFiveTuning) { availableTunings = generate_tunings(t5Improvements); } @@ -958,31 +928,14 @@ export function handlePermutation( if (distanceSum === 0 && totalOptionalDistances === 0) { result = { mods: [], tuning: [0, 0, 0, 0, 0, 0], modBonus: [0, 0, 0, 0, 0, 0] }; } else { - result = get_mods_precalc( - stats, - targetVals, - prepared, - distances, - optionalDistances, - artificeCount, - prepared.modOptimizationStrategy, - availableTunings - ); + result = get_mods_precalc(stats, distances, optionalDistances, artificeCount, availableTunings); } if (result === null) return null; - performTierAvailabilityTesting( - runtime, - prepared, - stats, - targetVals, - distances, - artificeCount, - availableTunings - ); + performTierAvailabilityTesting(stats, distances, artificeCount, availableTunings); - // ── Deferred statsWithoutMods: only allocated when the permutation passes all checks ── + // Deferred statsWithoutMods: only allocated when the permutation passes all checks const statsWithoutMods: number[] = [ stats[0] - b0, stats[1] - b1, @@ -992,20 +945,34 @@ export function handlePermutation( stats[5] - b5, ]; - return tryCreateArmorSetWithClassItem( - runtime, - prepared, + if (doNotOutput) return null; + + const usedArtifice = result.mods.filter((d: StatModifier) => 0 == d % 3); + const usedMods = result.mods.filter((d: StatModifier) => 0 != d % 3); + + // Apply mods to stats for final calculation + const finalStats = [...stats]; + for (let statModifier of result.mods) { + const stat = Math.floor((statModifier - 1) / 3); + finalStats[stat] += STAT_MOD_VALUES[statModifier][1]; + } + + for (let n = 0; n < 6; n++) finalStats[n] += result.tuning[n]; + + const waste1 = getWaste(finalStats); + if (onlyShowResultsWithNoWastedStats && waste1 > 0) return null; + + return createArmorSet( helmet, gauntlet, chest, leg, classItem, - result, - stats, + usedArtifice, + usedMods, + finalStats, statsWithoutMods, - distances, - artificeCount, - doNotOutput + result.tuning ); } @@ -1015,10 +982,7 @@ function getStatVal(statId: ArmorStat, mods: StatModifierPrecalc, start: number) // region Tier Availability Testing function performTierAvailabilityTesting( - runtime: any, - prepared: PreparedConfig, stats: number[], - targetStats: number[], distances: number[], availableArtificeCount: number, availableTunings: Tuning[] @@ -1051,7 +1015,7 @@ function performTierAvailabilityTesting( if (minStat >= 200) continue; // Already at max value, no need to test - const minTier = prepared.minimumStatTierValues[stat] * 10; + const minTier = minimumStatTierValues[stat] * 10; // Binary search to find maximum possible value let low = Math.max(runtime.maximumPossibleTiers[stat], minTier); @@ -1074,12 +1038,9 @@ function performTierAvailabilityTesting( // Check if this value is achievable with mods const mods = get_mods_precalc( stats, - targetStats, - prepared, testDistances, [0, 0, 0, 0, 0, 0], availableArtificeCount, - ModOptimizationStrategy.None, tmpTunings ); @@ -1099,12 +1060,9 @@ function performTierAvailabilityTesting( testDistances[stat] = Math.max(low - minStat, 0); const mods = get_mods_precalc( stats, - targetStats, - prepared, testDistances, [0, 0, 0, 0, 0, 0], availableArtificeCount, - ModOptimizationStrategy.None, tmpTunings ); if (mods != null) { @@ -1122,52 +1080,6 @@ function performTierAvailabilityTesting( } } -function tryCreateArmorSetWithClassItem( - runtime: any, - prepared: PreparedConfig, - helmet: IPermutatorArmor, - gauntlet: IPermutatorArmor, - chest: IPermutatorArmor, - leg: IPermutatorArmor, - classItem: IPermutatorArmor, - result: StatModifierPrecalc, - adjustedStats: number[], - statsWithoutMods: number[], - newDistances: number[], - availableArtificeCount: number, - doNotOutput: boolean -): IPermutatorArmorSet | never[] { - if (doNotOutput) return []; - - const usedArtifice = result.mods.filter((d: StatModifier) => 0 == d % 3); - const usedMods = result.mods.filter((d: StatModifier) => 0 != d % 3); - - // Apply mods to stats for final calculation - const finalStats = [...adjustedStats]; - for (let statModifier of result.mods) { - const stat = Math.floor((statModifier - 1) / 3); - finalStats[stat] += STAT_MOD_VALUES[statModifier][1]; - } - - for (let n = 0; n < 6; n++) finalStats[n] += result.tuning[n]; - - const waste1 = getWaste(finalStats); - if (prepared.onlyShowResultsWithNoWastedStats && waste1 > 0) return []; - - return createArmorSet( - helmet, - gauntlet, - chest, - leg, - classItem, - usedArtifice, - usedMods, - finalStats, - statsWithoutMods, - result.tuning - ); -} - // region Mod Calculation Functions function get_mods_recursive( currentStats: number[], @@ -1185,9 +1097,9 @@ function get_mods_recursive( // statIdx is no longer useful here // 1. If there is any tuning with no negative in any value, then return [] - // if (availableTunings.some(tuning => tuning.every(v => v >= 0))) { - // return []; - // } + // if (availableTunings.some(tuning => tuning.every(v => v >= 0))) { + // return []; + // } // Now there are only tunings with negative values left. // 2.1 If there is any stat where (currentStat - tuningValue) >= target value, then return @@ -1292,12 +1204,9 @@ type StatModifierPrecalc = { function get_mods_precalc( currentStats: number[], - targetStats: number[], - prepared: PreparedConfig, distances: number[], optionalDistances: number[], availableArtificeCount: number, - optimize: ModOptimizationStrategy = ModOptimizationStrategy.None, availableTunings: Tuning[] ): StatModifierPrecalc | null { const totalDistance = @@ -1311,13 +1220,13 @@ function get_mods_precalc( let pickedMods = get_mods_recursive( currentStats, - targetStats, + targetVals, distances, availableTunings, 0, availableArtificeCount, - prepared.maxMajorMods, - prepared.maxMods + maxMajorMods, + maxMods ); if (pickedMods === null) return null; From b710c05859dd7abe07cd07d0e55ae36bd74b1047 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sun, 22 Feb 2026 20:52:12 -0600 Subject: [PATCH 81/84] feat: improve readability for builder worker --- src/app/services/results-builder.worker.ts | 58 +++++++++------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 28492271..1c003935 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -73,7 +73,7 @@ let requiredPerkSlotCounts: Map; let targetVals: number[]; let targetFixed: boolean[]; let possibleIncreaseByMod: number; -let doNotOutput: boolean = false; +let resultLimitReached: boolean = false; type t5Improvement = { tuningStat: ArmorStat; @@ -609,9 +609,9 @@ async function handleArmorBuilderRequest(data: any): Promise { // define the delay; it can be 75ms if the estimated calculations are low // if the estimated calculations >= 1e6, then we will use 125ms - let progressBarDelay = estimatedCalculations >= 1e6 ? 500 : 125; + let progressBarDelay = estimatedCalculations >= 1e6 ? 125 : 75; - doNotOutput = false; + resultLimitReached = false; for (let [helmet, gauntlet, chest, leg, classItem] of generateArmorCombinations( helmets, @@ -634,7 +634,7 @@ async function handleArmorBuilderRequest(data: any): Promise { results.push(result); resultsLength++; listedResults++; - doNotOutput = + resultLimitReached = (config.limitParsedResults && listedResults >= 3e4 / threadSplit.count) || listedResults >= 1e6 / threadSplit.count; } @@ -644,10 +644,7 @@ async function handleArmorBuilderRequest(data: any): Promise { postMessage({ runtime, results, done: false, checkedCalculations, estimatedCalculations }); results = []; resultsLength = 0; - } else if ( - resultsLength > 100 && - lastProgressReportTime + progressBarDelay < performance.now() - ) { + } else if (lastProgressReportTime + progressBarDelay < performance.now()) { lastProgressReportTime = performance.now(); postMessage({ checkedCalculations, @@ -769,7 +766,7 @@ export function handlePermutation( leg: IPermutatorArmor, classItem: IPermutatorArmor ): IPermutatorArmorSet | null { - // Inline stat summation + mod bonuses in one allocation + // Inline stat summation (without mod bonuses) const b0 = enabledModBonuses[0], b1 = enabledModBonuses[1], b2 = enabledModBonuses[2], @@ -777,29 +774,32 @@ export function handlePermutation( b4 = enabledModBonuses[4], b5 = enabledModBonuses[5]; - const stats: number[] = [ - helmet.mobility + gauntlet.mobility + chest.mobility + leg.mobility + classItem.mobility + b0, + const statsWithoutMods: number[] = [ + helmet.mobility + gauntlet.mobility + chest.mobility + leg.mobility + classItem.mobility, helmet.resilience + gauntlet.resilience + chest.resilience + leg.resilience + classItem.resilience + - (!chest.isExotic && addConstent1Health ? 1 : 0) + - b1, - helmet.recovery + gauntlet.recovery + chest.recovery + leg.recovery + classItem.recovery + b2, + (!chest.isExotic && addConstent1Health ? 1 : 0), + helmet.recovery + gauntlet.recovery + chest.recovery + leg.recovery + classItem.recovery, helmet.discipline + gauntlet.discipline + chest.discipline + leg.discipline + - classItem.discipline + - b3, - helmet.intellect + - gauntlet.intellect + - chest.intellect + - leg.intellect + - classItem.intellect + - b4, - helmet.strength + gauntlet.strength + chest.strength + leg.strength + classItem.strength + b5, + classItem.discipline, + helmet.intellect + gauntlet.intellect + chest.intellect + leg.intellect + classItem.intellect, + helmet.strength + gauntlet.strength + chest.strength + leg.strength + classItem.strength, + ]; + + // Add mod bonuses to get the working stats array + const stats: number[] = [ + statsWithoutMods[0] + b0, + statsWithoutMods[1] + b1, + statsWithoutMods[2] + b2, + statsWithoutMods[3] + b3, + statsWithoutMods[4] + b4, + statsWithoutMods[5] + b5, ]; let artificeCount = 0; @@ -935,17 +935,7 @@ export function handlePermutation( performTierAvailabilityTesting(stats, distances, artificeCount, availableTunings); - // Deferred statsWithoutMods: only allocated when the permutation passes all checks - const statsWithoutMods: number[] = [ - stats[0] - b0, - stats[1] - b1, - stats[2] - b2, - stats[3] - b3, - stats[4] - b4, - stats[5] - b5, - ]; - - if (doNotOutput) return null; + if (resultLimitReached) return null; const usedArtifice = result.mods.filter((d: StatModifier) => 0 == d % 3); const usedMods = result.mods.filter((d: StatModifier) => 0 != d % 3); From 866d98067e969225942f6df773cffa73ff1441f0 Mon Sep 17 00:00:00 2001 From: nznaza Date: Mon, 23 Feb 2026 00:28:36 -0600 Subject: [PATCH 82/84] feat: add early exit for max tiers and graceful termination for workers --- .../results-table-view.component.html | 4 +- .../results-table-view.component.ts | 31 +++-- .../results/results.component.html | 28 ++-- .../results/results.component.ts | 6 +- src/app/services/armor-calculator.service.ts | 106 ++++++++++++--- src/app/services/results-builder.worker.ts | 122 +++++++++++++++--- 6 files changed, 230 insertions(+), 67 deletions(-) diff --git a/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.html b/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.html index 16ebc993..f9ea8d12 100644 --- a/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.html +++ b/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.html @@ -20,8 +20,8 @@ class="result-table" mat-table matSort - matSortActive="Mods" - matSortDirection="asc" + matSortActive="Total" + matSortDirection="desc" multiTemplateDataRows> diff --git a/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.ts b/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.ts index 1c81ee50..d7ed2681 100644 --- a/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.ts +++ b/src/app/components/authenticated-v2/results/results-table-view/results-table-view.component.ts @@ -78,10 +78,13 @@ export class ResultsTableViewComponent implements OnInit, AfterViewInit, OnChang // Performance optimizations private totalStatsCache = new Map(); - private readonly MAX_INITIAL_RESULTS = 200; // Limit initial results private displayedResults: ResultDefinition[] = []; showAllResults = false; + // View / initialization state + private viewInitialized = false; + private pendingResultsUpdate = false; + private ngUnsubscribe = new Subject(); constructor( @@ -115,6 +118,14 @@ export class ResultsTableViewComponent implements OnInit, AfterViewInit, OnChang if (this.sort) { this.tableDataSource.sort = this.sort; } + + this.viewInitialized = true; + + // If results arrived before the view was initialized, update the table now + if (this.pendingResultsUpdate && this.results) { + this.pendingResultsUpdate = false; + this.updateTableData(); + } } ngOnChanges(changes: SimpleChanges): void { @@ -122,8 +133,14 @@ export class ResultsTableViewComponent implements OnInit, AfterViewInit, OnChang // Reset expanded element when results change this.expandedElement = null; this.expandedElementId = null; - this.updateTableData(); - this.cdr.markForCheck(); + + // Defer table data initialization until the view (and paginator) are ready + if (this.viewInitialized) { + this.updateTableData(); + this.cdr.markForCheck(); + } else { + this.pendingResultsUpdate = true; + } } } @@ -191,9 +208,7 @@ export class ResultsTableViewComponent implements OnInit, AfterViewInit, OnChang this.expandedElementId = null; // Limit initial results for performance - this.displayedResults = this.showAllResults - ? this.results - : this.results.slice(0, this.MAX_INITIAL_RESULTS); + this.displayedResults = this.results; this.tableDataSource.data = this.displayedResults; @@ -252,10 +267,6 @@ export class ResultsTableViewComponent implements OnInit, AfterViewInit, OnChang this.cdr.markForCheck(); } - get hasMoreResults(): boolean { - return !this.showAllResults && this.results.length > this.MAX_INITIAL_RESULTS; - } - // TrackBy function to improve performance by helping Angular track changes trackByResult(index: number, item: ResultDefinition): any { return item.stats.join("-") + item.mods.join("-") + (item.exotic?.hash || "none"); diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index 4efdc15f..a0a1e055 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -27,35 +27,29 @@ {{ this.itemCount | number }}
- Combinations generated + Results shown
- {{ this.totalResults | number }} + {{ this.savedResults | number }} report_problem - report_problem -
+ Combinations calculated + {{ this.totalPermutations | number }} +
+
Time required {{ this.totalTime | number }}ms @@ -293,7 +287,7 @@

No results found

- + + +
+ hourglass_top +

Continuing calculation...

+ +

+ Additional combinations are still being checked. Current results are shown while the + calculation continues. +

+ +
+ +
+
cancel @@ -271,6 +296,16 @@

No results found

+ +
+ warning +
+ Calculation was cancelled. + + These results might be incomplete. The possible max stats might be inaccurate +
+
val) + ) { + console.log("All threads have reached their local result limit"); + console.log( + ArmorCalculatorService.results.length + + " results found, " + + sumDone + + " calculations done out of estimated " + + sumTotal + ); + this.processIntermediateResults(inventoryArmorItems); + + ArmorCalculatorService.allThreadsResultLimitReached = true; + } + // Handle completion of individual worker threads if (data.done == true) { ArmorCalculatorService.doneWorkerCount++; @@ -614,6 +639,88 @@ export class ArmorCalculatorService implements OnDestroy { ); } + private processIntermediateResults(inventoryArmorItems: IInventoryArmor[]): void { + // Do not toggle calculatingResults or reset progress; workers are still running. + + ArmorCalculatorService.endResults = []; + + for (let armorSet of ArmorCalculatorService.results) { + const items = armorSet.armor.map((x) => + inventoryArmorItems.find((y) => y.id == x) + ) as IInventoryArmor[]; + const exotic = items.find((x) => x.isExotic); + const v: ResultDefinition = { + loaded: false, + tuningStats: armorSet.tuning, + exotic: + exotic == null + ? undefined + : { + icon: exotic.icon, + watermark: exotic.watermarkIcon, + name: exotic.name, + hash: exotic.hash, + }, + artifice: armorSet.usedArtifice, + modCount: armorSet.usedMods.length, + modCost: armorSet.usedMods.reduce((p, d: StatModifier) => p + STAT_MOD_VALUES[d][2], 0), + mods: armorSet.usedMods, + stats: armorSet.statsWithMods, + statsNoMods: armorSet.statsWithoutMods, + tiers: getSkillTier(armorSet.statsWithMods), + waste: getWaste(armorSet.statsWithMods), + items: items.map( + (instance): ResultItem => ({ + tuningStat: instance.tuningStat, + energyLevel: instance.energyLevel, + hash: instance.hash, + itemInstanceId: instance.itemInstanceId, + name: instance.name, + exotic: !!instance.isExotic, + masterworked: instance.masterworkLevel == MAXIMUM_MASTERWORK_LEVEL, + archetypeStats: instance.archetypeStats, + armorSystem: instance.armorSystem, + masterworkLevel: instance.masterworkLevel, + slot: instance.slot, + perk: instance.perk, + transferState: 0, + tier: instance.tier, + stats: [ + instance.mobility, + instance.resilience, + instance.recovery, + instance.discipline, + instance.intellect, + instance.strength, + ], + source: instance.source, + statsNoMods: [], + }) + ), + usesCollectionRoll: items.some((y) => y.source === InventoryArmorSource.Collections), + usesVendorRoll: items.some((y) => y.source === InventoryArmorSource.Vendor), + }; + ArmorCalculatorService.endResults.push(v); + } + + this._armorResults.next({ + results: ArmorCalculatorService.endResults, + savedResults: ArmorCalculatorService.results.length, + totalPermutations: ArmorCalculatorService.totalPermutationsCount, + itemCount: inventoryArmorItems.length, + totalTime: null, + maximumPossibleTiers: ArmorCalculatorService.globalMaximumPossibleTiers.map( + (k) => Math.min(200, k) / 10 + ), + }); + + this.logger.info( + "ArmorCalculatorService", + "processIntermediateResults", + "Published intermediate results after all workers reached result limit" + ); + } + // Manual trigger method for testing manualTriggerCalculation() { this.logger.info( @@ -838,6 +945,10 @@ export class ArmorCalculatorService implements OnDestroy { Array(6).fill(0) ); ArmorCalculatorService.globalMaximumPossibleTiers = [0, 0, 0, 0, 0, 0]; + ArmorCalculatorService.threadResultLimitReachedArr = [...Array(nthreads).keys()].map( + () => false + ); + ArmorCalculatorService.allThreadsResultLimitReached = false; // Improve per thread performance by shuffling the inventory // sorting is a naive aproach that can be optimized diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 1d4d9f1d..75e75f0d 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -686,14 +686,21 @@ async function handleArmorBuilderRequest(data: any): Promise { } } - if (resultsLength >= 5000) { + if (resultsLength >= 5000 || (resultLimitReached && resultsLength > 0)) { // Check if the best result is in this batch if (bestResult && results.includes(bestResult)) { bestResultSent = true; } // @ts-ignore - postMessage({ runtime, results, done: false, checkedCalculations, estimatedCalculations }); + postMessage({ + runtime, + results, + done: false, + checkedCalculations, + estimatedCalculations, + resultLimitReached, + }); results = []; resultsLength = 0; await new Promise((resolve) => setTimeout(resolve, 0)); @@ -730,6 +737,7 @@ async function handleArmorBuilderRequest(data: any): Promise { done: true, checkedCalculations, estimatedCalculations, + resultLimitReached, stats: { savedResults: resultsSent, computedPermutations: computedResults, From 99f8d2b8a65d722a030dc8f49e224530e9c8e93e Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 28 Feb 2026 18:03:39 -0600 Subject: [PATCH 84/84] feat: fix total permutations count --- src/app/services/armor-calculator.service.ts | 1 + src/app/services/results-builder.worker.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/services/armor-calculator.service.ts b/src/app/services/armor-calculator.service.ts index badd0b85..17e31560 100644 --- a/src/app/services/armor-calculator.service.ts +++ b/src/app/services/armor-calculator.service.ts @@ -894,6 +894,7 @@ export class ArmorCalculatorService implements OnDestroy { ArmorCalculatorService.results = []; ArmorCalculatorService.savedResultsCount = 0; + ArmorCalculatorService.totalPermutationsCount = 0; ArmorCalculatorService.resultMaximumTiers = []; // Reset progress and worker state diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 75e75f0d..98f1e3cf 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -42,6 +42,7 @@ import { ArmorSystem } from "../data/types/IManifestArmor"; import { precalculatedTuningModCombinations } from "../data/generated/precalculatedModCombinationsWithTunings"; +// endregion Imports let runtime: { maximumPossibleTiers: number[]; } = {