jaml-ui 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/DESIGN.md +9 -11
  2. package/dist/assets.d.ts +6 -0
  3. package/dist/assets.js +9 -0
  4. package/dist/components/AnalyzerExplorer.d.ts +4 -1
  5. package/dist/components/AnalyzerExplorer.js +14 -48
  6. package/dist/components/GameCard.js +8 -7
  7. package/dist/components/JamlAestheticSelector.d.ts +4 -0
  8. package/dist/components/JamlAestheticSelector.js +6 -19
  9. package/dist/components/JamlAnalyzerFullscreen.d.ts +7 -1
  10. package/dist/components/JamlAnalyzerFullscreen.js +18 -47
  11. package/dist/components/JamlIde.js +12 -24
  12. package/dist/components/JamlIdeVisual.js +3 -56
  13. package/dist/components/JamlMapPreview.d.ts +6 -1
  14. package/dist/components/JamlMapPreview.js +99 -21
  15. package/dist/components/JamlSeedInput.d.ts +5 -0
  16. package/dist/components/JamlSeedInput.js +11 -14
  17. package/dist/components/JamlSpeedometer.d.ts +8 -8
  18. package/dist/components/JamlSpeedometer.js +24 -46
  19. package/dist/components/MotelyVersionBadge.d.ts +1 -3
  20. package/dist/components/MotelyVersionBadge.js +4 -16
  21. package/dist/components/jamlMap/JamlMapEditorDemo.d.ts +8 -0
  22. package/dist/components/jamlMap/JamlMapEditorDemo.js +170 -0
  23. package/dist/components/jamlMap/JokerPicker.d.ts +7 -0
  24. package/dist/components/jamlMap/JokerPicker.js +258 -0
  25. package/dist/components/jamlMap/MysterySlot.d.ts +32 -0
  26. package/dist/components/jamlMap/MysterySlot.js +109 -0
  27. package/dist/components/jamlMap/index.d.ts +3 -0
  28. package/dist/components/jamlMap/index.js +3 -0
  29. package/dist/core.d.ts +0 -2
  30. package/dist/core.js +0 -2
  31. package/dist/decode/motelyItemDecoder.d.ts +10 -23
  32. package/dist/decode/motelyItemDecoder.js +103 -272
  33. package/dist/decode/motelySprite.d.ts +4 -0
  34. package/dist/decode/motelySprite.js +57 -0
  35. package/dist/hooks/analyzerStreamRegistry.js +30 -82
  36. package/dist/hooks/useAnalyzer.d.ts +10 -3
  37. package/dist/hooks/useAnalyzer.js +11 -6
  38. package/dist/hooks/useIntersectionObserver.d.ts +14 -0
  39. package/dist/hooks/useIntersectionObserver.js +50 -0
  40. package/dist/index.d.ts +5 -8
  41. package/dist/index.js +4 -7
  42. package/dist/motely.d.ts +2 -2
  43. package/dist/motely.js +2 -2
  44. package/dist/motelyDisplay.d.ts +4 -623
  45. package/dist/motelyDisplay.js +26 -165
  46. package/dist/r3f/Card3D.d.ts +2 -2
  47. package/dist/r3f/Card3D.js +13 -48
  48. package/dist/r3f/JimboText3D.js +3 -2
  49. package/dist/render/CanvasRenderer.js +7 -171
  50. package/dist/sprites/spriteMapper.d.ts +71 -0
  51. package/dist/sprites/spriteMapper.js +40 -0
  52. package/dist/ui/JimboBadge.d.ts +8 -2
  53. package/dist/ui/JimboBadge.js +6 -22
  54. package/dist/ui/JimboToggleList.js +2 -7
  55. package/dist/ui/codeBlock.js +2 -3
  56. package/dist/ui/footer.d.ts +4 -0
  57. package/dist/ui/footer.js +6 -4
  58. package/dist/ui/hooks.d.ts +89 -0
  59. package/dist/ui/hooks.js +551 -0
  60. package/dist/ui/jimboBackground.js +2 -131
  61. package/dist/ui/jimboCopyRow.d.ts +4 -0
  62. package/dist/ui/jimboCopyRow.js +5 -22
  63. package/dist/ui/jimboFilterBar.d.ts +1 -4
  64. package/dist/ui/jimboFilterBar.js +2 -61
  65. package/dist/ui/jimboFlankNav.d.ts +1 -2
  66. package/dist/ui/jimboFlankNav.js +5 -30
  67. package/dist/ui/jimboTabs.d.ts +1 -5
  68. package/dist/ui/jimboTabs.js +6 -41
  69. package/dist/ui/jimboText.d.ts +1 -1
  70. package/dist/ui/jimboText.js +15 -32
  71. package/dist/ui/jimboTooltip.d.ts +1 -12
  72. package/dist/ui/jimboTooltip.js +6 -82
  73. package/dist/ui/panel.d.ts +2 -1
  74. package/dist/ui/panel.js +11 -47
  75. package/dist/ui/showcase.d.ts +4 -0
  76. package/dist/ui/showcase.js +9 -36
  77. package/dist/ui/sprites.js +3 -2
  78. package/dist/ui.d.ts +1 -0
  79. package/dist/ui.js +2 -0
  80. package/package.json +7 -6
  81. package/dist/decode/packedBalatroItem.d.ts +0 -13
  82. package/dist/decode/packedBalatroItem.js +0 -26
  83. package/dist/hooks/loadMotelyWasm.d.ts +0 -7
  84. package/dist/hooks/loadMotelyWasm.js +0 -16
  85. package/dist/utils/itemUtils.d.ts +0 -11
  86. package/dist/utils/itemUtils.js +0 -71
@@ -1,314 +1,145 @@
1
1
  /**
2
- * Motely item decoder.
3
- *
4
- * MotelyItem.Value is a packed integer. The MotelyItemType enum
5
- * uses packed integers where the top nibble encodes category:
6
- * 0x1000 = Standardcard, 0x2000 = Spectral, 0x3000 = Tarot,
7
- * 0x4000 = Planet, 0x5000 = Joker, 0xF000 = Invalid
2
+ * Motely item decoder — thin wrapper over motely-wasm runtime enums.
3
+ * No hand-rolled bitmask tables. The WASM enum IS the source of truth.
8
4
  */
9
5
  import { Motely } from "motely-wasm";
10
- import { getItemCategory, getItemDisplayName } from "../utils/itemUtils.js";
11
- // ─── Category from MotelyItemType integer ────────────────────────────────────
12
- const CATEGORY_MASK = 0xf000;
13
- const VALUE_TYPE_MASK = 0xffff;
14
- const VALUE_SEAL_MASK = 0x70000;
15
- const VALUE_ENHANCEMENT_MASK = 0x780000;
16
- const VALUE_EDITION_MASK = 0x3800000;
17
- const CATEGORY_TO_TYPE = {
18
- 0x1000: "Standard card",
19
- 0x2000: "Spectral",
20
- 0x3000: "Tarot",
21
- 0x4000: "Planet",
22
- 0x5000: "Joker",
6
+ // ─── Helpers using motely-wasm runtime enums directly ────────────────────────
7
+ function enumKey(e, value) {
8
+ const k = e[String(value)];
9
+ return typeof k === "string" ? k : null;
10
+ }
11
+ function spaceSplit(value) {
12
+ return value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
13
+ }
14
+ const CATEGORY_MAP = {
15
+ [Motely.MotelyItemTypeCategory.Standardcard]: "playing",
16
+ [Motely.MotelyItemTypeCategory.SpectralCard]: "spectral",
17
+ [Motely.MotelyItemTypeCategory.TarotCard]: "tarot",
18
+ [Motely.MotelyItemTypeCategory.PlanetCard]: "planet",
19
+ [Motely.MotelyItemTypeCategory.Joker]: "joker",
23
20
  };
24
- // ─── Reverse lookup: MotelyItemType integer → string name ────────────────────
25
- const _itemTypeToName = new Map();
26
- const MOTELY_ITEM_TYPES_STANDARD = [
27
- "TwoOfClubs", "ThreeOfClubs", "FourOfClubs", "FiveOfClubs", "SixOfClubs", "SevenOfClubs", "EightOfClubs", "NineOfClubs", "TenOfClubs", "JackOfClubs", "QueenOfClubs", "KingOfClubs", "AceOfClubs",
28
- "TwoOfDiamonds", "ThreeOfDiamonds", "FourOfDiamonds", "FiveOfDiamonds", "SixOfDiamonds", "SevenOfDiamonds", "EightOfDiamonds", "NineOfDiamonds", "TenOfDiamonds", "JackOfDiamonds", "QueenOfDiamonds", "KingOfDiamonds", "AceOfDiamonds",
29
- "TwoOfHearts", "ThreeOfHearts", "FourOfHearts", "FiveOfHearts", "SixOfHearts", "SevenOfHearts", "EightOfHearts", "NineOfHearts", "TenOfHearts", "JackOfHearts", "QueenOfHearts", "KingOfHearts", "AceOfHearts",
30
- "TwoOfSpades", "ThreeOfSpades", "FourOfSpades", "FiveOfSpades", "SixOfSpades", "SevenOfSpades", "EightOfSpades", "NineOfSpades", "TenOfSpades", "JackOfSpades", "QueenOfSpades", "KingOfSpades", "AceOfSpades"
31
- ];
32
- const MOTELY_ITEM_TYPES_SPECTRAL = ["Familiar", "Grim", "Incantation", "Talisman", "Aura", "Wraith", "Sigil", "Ouija", "Ectoplasm", "Immolate", "Ankh", "DejaVu", "Hex", "Trance", "Medium", "Cryptid", "TheSoul", "BlackHole"];
33
- const MOTELY_ITEM_TYPES_TAROT = ["TheFool", "TheMagician", "TheHighPriestess", "TheEmpress", "TheEmperor", "TheHierophant", "TheLovers", "TheChariot", "Justice", "TheHermit", "TheWheelOfFortune", "Strength", "TheHangedMan", "Death", "Temperance", "TheDevil", "TheTower", "TheStar", "TheMoon", "TheSun", "Judgement", "TheWorld"];
34
- const MOTELY_ITEM_TYPES_PLANET = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "PlanetX", "Ceres", "Eris"];
35
- const MOTELY_ITEM_TYPES_JOKER = ["Joker", "GreedyJoker", "LustyJoker", "WrathfulJoker", "GluttonousJoker", "JollyJoker", "ZanyJoker", "MadJoker", "CrazyJoker", "DrollJoker", "SlyJoker", "WilyJoker", "CleverJoker", "DeviousJoker", "CraftyJoker", "HalfJoker", "CreditCard", "Banner", "MysticSummit", "EightBall", "Misprint", "RaisedFist", "ChaostheClown", "ScaryFace", "AbstractJoker", "DelayedGratification", "GrosMichel", "EvenSteven", "OddTodd", "Scholar", "BusinessCard", "Supernova", "RideTheBus", "Egg", "Runner", "IceCream", "Splash", "BlueJoker", "FacelessJoker", "GreenJoker", "Superposition", "ToDoList", "Cavendish", "RedCard", "SquareJoker", "RiffRaff", "Photograph", "ReservedParking", "MailInRebate", "Hallucination", "FortuneTeller", "Juggler", "Drunkard", "GoldenJoker", "Popcorn", "WalkieTalkie", "SmileyFace", "GoldenTicket", "Swashbuckler", "HangingChad", "ShootTheMoon", "JokerStencil", "FourFingers", "Mime", "CeremonialDagger", "MarbleJoker", "LoyaltyCard", "Dusk", "Fibonacci", "SteelJoker", "Hack", "Pareidolia", "SpaceJoker", "Burglar", "Blackboard", "SixthSense", "Constellation", "Hiker", "CardSharp", "Madness", "Seance", "Vampire", "Shortcut", "Hologram", "Cloud9", "Rocket", "MidasMask", "Luchador", "GiftCard", "TurtleBean", "Erosion", "ToTheMoon", "StoneJoker", "LuckyCat", "Bull", "DietCola", "TradingCard", "FlashCard", "SpareTrousers", "Ramen", "Seltzer", "Castle", "MrBones", "Acrobat", "SockAndBuskin", "Troubadour", "Certificate", "SmearedJoker", "Throwback", "RoughGem", "Bloodstone", "Arrowhead", "OnyxAgate", "GlassJoker", "Showman", "FlowerPot", "MerryAndy", "OopsAll6s", "TheIdol", "SeeingDouble", "Matador", "Satellite", "Cartomancer", "Astronomer", "Bootstraps", "DNA", "Vagabond", "Baron", "Obelisk", "BaseballCard", "AncientJoker", "Campfire", "Blueprint", "WeeJoker", "HitTheRoad", "TheDuo", "TheTrio", "TheFamily", "TheOrder", "TheTribe", "Stuntman", "InvisibleJoker", "Brainstorm", "DriversLicense", "BurntJoker", "Canio", "Triboulet", "Yorick", "Chicot", "Perkeo"];
36
- const MOTELY_ITEM_TYPES_INVALID = ["Invalid", "NotImplemented", "JokerExcludedByStream", "PlanetExcludedByStream", "TarotExcludedByStream", "SpectralExcludedByStream"];
37
- function ensureItemTypeMap() {
38
- if (_itemTypeToName.size > 0)
39
- return;
40
- // Fallback to runtime enum if present (motely-wasm < 14)
41
- const e = Motely.MotelyItemType;
42
- if (e && Object.keys(e).length > 0) {
43
- for (const [key, val] of Object.entries(e)) {
44
- if (typeof val === "number" && typeof key === "string" && !/^\d+$/.test(key)) {
45
- _itemTypeToName.set(val, key);
46
- }
47
- }
48
- }
49
- else {
50
- // Populate using hardcoded categories (motely-wasm 14+)
51
- _itemTypeToName.set(0, "None"); // Handle 0
52
- MOTELY_ITEM_TYPES_STANDARD.forEach((name, i) => _itemTypeToName.set(0x1000 + i, name));
53
- MOTELY_ITEM_TYPES_SPECTRAL.forEach((name, i) => _itemTypeToName.set(0x2000 + i, name));
54
- MOTELY_ITEM_TYPES_TAROT.forEach((name, i) => _itemTypeToName.set(0x3000 + i, name));
55
- MOTELY_ITEM_TYPES_PLANET.forEach((name, i) => _itemTypeToName.set(0x4000 + i, name));
56
- MOTELY_ITEM_TYPES_JOKER.forEach((name, i) => _itemTypeToName.set(0x5000 + i, name));
57
- MOTELY_ITEM_TYPES_INVALID.forEach((name, i) => _itemTypeToName.set(0xf000 + i, name));
58
- }
59
- }
60
- function asRuntimeItem(input) {
61
- return input !== null && typeof input === "object" ? input : null;
62
- }
63
- function finiteNumber(value) {
64
- return typeof value === "number" && Number.isFinite(value) ? value : null;
65
- }
66
- function runtimeEnumName(enumObject, value) {
67
- if (value === null || !enumObject || typeof enumObject !== "object")
68
- return null;
69
- const enumKey = enumObject[String(value)];
70
- return typeof enumKey === "string" && enumKey.length > 0 ? enumKey : null;
71
- }
72
- function parseStandardcardEnumKey(enumKey) {
73
- const match = /^([CDHS])(10|[2-9JQKA])$/.exec(enumKey);
74
- if (!match)
75
- return null;
76
- const suitMap = {
77
- C: "Clubs",
78
- D: "Diamonds",
79
- H: "Hearts",
80
- S: "Spades",
81
- };
82
- const rankMap = {
83
- J: "Jack",
84
- Q: "Queen",
85
- K: "King",
86
- A: "Ace",
87
- };
88
- return {
89
- rank: rankMap[match[2]] ?? match[2],
90
- suit: suitMap[match[1]],
91
- };
92
- }
93
- function rankNameFromEnum(enumKey) {
94
- if (!enumKey)
95
- return null;
96
- const rankMap = {
97
- Two: "2",
98
- Three: "3",
99
- Four: "4",
100
- Five: "5",
101
- Six: "6",
102
- Seven: "7",
103
- Eight: "8",
104
- Nine: "9",
105
- Ten: "10",
106
- Jack: "Jack",
107
- Queen: "Queen",
108
- King: "King",
109
- Ace: "Ace",
110
- };
111
- return rankMap[enumKey] ?? null;
112
- }
21
+ // ─── Core decoder ────────────────────────────────────────────────────────────
113
22
  function resolvePackedValue(input) {
23
+ if (input == null)
24
+ return null;
114
25
  if (typeof input === "number")
115
26
  return Number.isFinite(input) ? input : null;
116
- const runtimeItem = asRuntimeItem(input);
117
- return finiteNumber(runtimeItem?.value);
27
+ return input.value ?? input.type ?? null;
118
28
  }
119
- function resolveEditionValue(input) {
120
- const runtimeItem = asRuntimeItem(input);
121
- const direct = finiteNumber(runtimeItem?.edition);
122
- if (direct !== null)
123
- return direct;
124
- const packedValue = resolvePackedValue(input);
125
- return packedValue !== null ? packedValue & VALUE_EDITION_MASK : null;
126
- }
127
- function resolveSealValue(input) {
128
- const runtimeItem = asRuntimeItem(input);
129
- const direct = finiteNumber(runtimeItem?.seal);
130
- if (direct !== null)
131
- return direct;
132
- const packedValue = resolvePackedValue(input);
133
- return packedValue !== null ? packedValue & VALUE_SEAL_MASK : null;
29
+ export function resolveMotelyItemType(input) {
30
+ const val = resolvePackedValue(input);
31
+ return val !== null ? val & 0xffff : null;
134
32
  }
135
- function resolveEnhancementValue(input) {
136
- const runtimeItem = asRuntimeItem(input);
137
- const direct = finiteNumber(runtimeItem?.enhancement);
138
- if (direct !== null)
139
- return direct;
140
- const packedValue = resolvePackedValue(input);
141
- return packedValue !== null ? packedValue & VALUE_ENHANCEMENT_MASK : null;
33
+ export function motelyItemTypeName(input) {
34
+ const itemType = resolveMotelyItemType(input);
35
+ if (itemType === null)
36
+ return "Unknown";
37
+ return enumKey(Motely.MotelyItemType, itemType) ?? `item#${itemType}`;
142
38
  }
143
- export function resolveMotelyItemType(input) {
144
- if (typeof input === "number")
145
- return Number.isFinite(input) ? (input & VALUE_TYPE_MASK) : null;
146
- const runtimeItem = asRuntimeItem(input);
147
- const directType = finiteNumber(runtimeItem?.type);
148
- if (directType !== null)
149
- return directType & VALUE_TYPE_MASK;
150
- const packedValue = finiteNumber(runtimeItem?.value);
151
- return packedValue !== null ? packedValue & VALUE_TYPE_MASK : null;
39
+ export function motelyItemCategory(itemType) {
40
+ const catValue = (itemType >> 12) & 0xf;
41
+ return CATEGORY_MAP[catValue] ?? "unknown";
152
42
  }
153
43
  export function motelyItemRenderCategory(input) {
154
- const enumKey = motelyItemTypeName(input);
155
- return enumKey === "Unknown" ? "unknown" : getItemCategory(enumKey);
44
+ const itemType = resolveMotelyItemType(input);
45
+ if (itemType === null)
46
+ return "unknown";
47
+ return motelyItemCategory(itemType);
48
+ }
49
+ export function motelyItemDisplayName(input) {
50
+ return spaceSplit(motelyItemTypeName(input));
156
51
  }
157
52
  export function motelyItemEditionName(input) {
158
- const enumKey = runtimeEnumName(Motely.MotelyItemEdition, resolveEditionValue(input));
159
- return enumKey === null || enumKey === "None" ? null : enumKey;
53
+ if (input == null)
54
+ return null;
55
+ const val = typeof input === "number" ? input : input.edition;
56
+ if (val == null)
57
+ return null;
58
+ const key = enumKey(Motely.MotelyItemEdition, typeof val === "number" ? val : 0);
59
+ if (!key || key === "None")
60
+ return null;
61
+ return key;
160
62
  }
161
63
  export function motelyItemSealName(input) {
162
- const enumKey = runtimeEnumName(Motely.MotelyItemSeal, resolveSealValue(input));
163
- return enumKey === null || enumKey === "None" ? null : enumKey;
64
+ if (input == null)
65
+ return null;
66
+ const val = typeof input === "number" ? null : input.seal;
67
+ if (val == null)
68
+ return null;
69
+ const key = enumKey(Motely.MotelyItemSeal, val);
70
+ if (!key || key === "None")
71
+ return null;
72
+ return key;
164
73
  }
165
74
  export function motelyItemEnhancementName(input) {
166
- const enumKey = runtimeEnumName(Motely.MotelyItemEnhancement, resolveEnhancementValue(input));
167
- return enumKey === null || enumKey === "None" ? null : enumKey;
168
- }
169
- export function motelyStandardcardSuitName(input) {
170
- const runtimeItem = asRuntimeItem(input);
171
- const directSuit = runtimeEnumName(Motely.MotelyStandardcardSuit, finiteNumber(runtimeItem?.suit));
172
- if (directSuit === "Clubs" || directSuit === "Diamonds" || directSuit === "Hearts" || directSuit === "Spades") {
173
- return directSuit;
174
- }
175
- const parsed = parseStandardcardEnumKey(motelyItemTypeName(input));
176
- return parsed?.suit ?? null;
75
+ if (input == null)
76
+ return null;
77
+ const val = typeof input === "number" ? null : input.enhancement;
78
+ if (val == null)
79
+ return null;
80
+ const key = enumKey(Motely.MotelyItemEnhancement, val);
81
+ if (!key || key === "None")
82
+ return null;
83
+ return key;
177
84
  }
178
85
  export function motelyStandardcardRankName(input) {
179
- const runtimeItem = asRuntimeItem(input);
180
- const directRank = runtimeEnumName(Motely.MotelyStandardcardRank, finiteNumber(runtimeItem?.rank));
181
- const normalizedDirect = rankNameFromEnum(directRank);
182
- if (normalizedDirect !== null)
183
- return normalizedDirect;
184
- const parsed = parseStandardcardEnumKey(motelyItemTypeName(input));
185
- return parsed?.rank ?? null;
186
- }
187
- /** Get the enum key name for a MotelyItemType value. */
188
- export function motelyItemTypeName(input) {
189
- const itemType = resolveMotelyItemType(input);
190
- if (itemType === null)
191
- return "Unknown";
192
- ensureItemTypeMap();
193
- return _itemTypeToName.get(itemType) ?? "Unknown";
86
+ if (input == null)
87
+ return null;
88
+ const val = typeof input === "number" ? null : input.rank;
89
+ if (val == null)
90
+ return null;
91
+ return enumKey(Motely.MotelyStandardcardRank, val);
194
92
  }
195
- /** Get the category string for a MotelyItemType value. */
196
- export function motelyItemCategory(input) {
197
- const itemType = resolveMotelyItemType(input);
198
- if (itemType === null)
199
- return "Unknown";
200
- const renderCategory = motelyItemRenderCategory(itemType);
201
- if (renderCategory === "playing")
202
- return "Standard card";
203
- if (renderCategory === "spectral")
204
- return "Spectral";
205
- if (renderCategory === "tarot")
206
- return "Tarot";
207
- if (renderCategory === "planet")
208
- return "Planet";
209
- if (renderCategory === "joker")
210
- return "Joker";
211
- return CATEGORY_TO_TYPE[itemType & CATEGORY_MASK] ?? "Unknown";
93
+ export function motelyStandardcardSuitName(input) {
94
+ if (input == null)
95
+ return null;
96
+ const val = typeof input === "number" ? null : input.suit;
97
+ if (val == null)
98
+ return null;
99
+ return enumKey(Motely.MotelyStandardcardSuit, val);
212
100
  }
213
- /** Convert PascalCase enum key to display name. */
214
- export function motelyItemDisplayName(input) {
215
- const enumKey = motelyItemTypeName(input);
216
- return enumKey === "Unknown" ? "Unknown" : getItemDisplayName(enumKey);
101
+ export function decodeMotelyItemName(input) {
102
+ return motelyItemTypeName(input);
217
103
  }
218
- // ─── Module-level cache ──────────────────────────────────────────────────────
219
- const _cache = new Map();
220
- const _decodedBaseCache = new Map();
221
104
  export function decodeMotelyItem(input) {
222
105
  const itemType = resolveMotelyItemType(input);
223
106
  if (itemType === null)
224
107
  return null;
225
- let base = _decodedBaseCache.get(itemType) ?? null;
226
- if (!_decodedBaseCache.has(itemType)) {
227
- const enumKey = motelyItemTypeName(itemType);
228
- if (enumKey === "Unknown") {
229
- _decodedBaseCache.set(itemType, null);
230
- return null;
231
- }
232
- const category = motelyItemRenderCategory(itemType);
233
- const rank = motelyStandardcardRankName(itemType);
234
- const suit = motelyStandardcardSuitName(itemType);
235
- base = {
236
- itemType,
237
- enumKey,
238
- displayName: category === "playing" && rank && suit ? `${rank} of ${suit}` : getItemDisplayName(enumKey),
239
- category,
240
- rank,
241
- suit,
242
- };
243
- _decodedBaseCache.set(itemType, base);
244
- }
245
- if (!base)
246
- return null;
247
- const decoded = {
248
- ...base,
108
+ const enumKeyStr = enumKey(Motely.MotelyItemType, itemType) ?? `Unknown_${itemType}`;
109
+ const category = motelyItemCategory(itemType);
110
+ const displayName = spaceSplit(enumKeyStr);
111
+ return {
112
+ itemType,
113
+ enumKey: enumKeyStr,
114
+ displayName,
115
+ category,
249
116
  edition: motelyItemEditionName(input),
250
117
  seal: motelyItemSealName(input),
251
118
  enhancement: motelyItemEnhancementName(input),
119
+ rank: motelyStandardcardRankName(input),
120
+ suit: motelyStandardcardSuitName(input),
252
121
  };
253
- return decoded;
254
122
  }
255
- export function decodeMotelyItemToJamlCard(input, scale = 1) {
123
+ export function decodeMotelyItemToJamlCard(input, scale) {
256
124
  const decoded = decodeMotelyItem(input);
257
- if (!decoded || decoded.category === "unknown")
125
+ if (!decoded)
258
126
  return null;
259
- if (decoded.category === "playing") {
260
- if (!decoded.rank || !decoded.suit)
261
- return null;
262
- return {
263
- type: "playing",
264
- card: {
265
- name: `${decoded.rank} of ${decoded.suit}`,
266
- edition: decoded.edition ?? undefined,
267
- seal: decoded.seal ? `${decoded.seal} Seal` : undefined,
268
- enhancements: decoded.enhancement ? [decoded.enhancement] : undefined,
269
- rank: decoded.rank,
270
- suit: decoded.suit,
271
- scale,
272
- },
273
- };
274
- }
127
+ const type = decoded.category === "joker" ? "joker"
128
+ : decoded.category === "playing" ? "playing"
129
+ : "consumable";
275
130
  return {
276
- type: decoded.category === "joker" ? "joker" : "consumable",
131
+ type,
277
132
  card: {
278
133
  name: decoded.displayName,
279
134
  edition: decoded.edition ?? undefined,
135
+ seal: decoded.seal ?? undefined,
136
+ enhancements: decoded.enhancement ? [decoded.enhancement] : undefined,
137
+ rank: decoded.rank ?? undefined,
138
+ suit: decoded.suit ?? undefined,
280
139
  scale,
281
140
  },
282
141
  };
283
142
  }
284
- /**
285
- * Decode a MotelyItemType integer to a display name for sprite lookup.
286
- * Cached per value.
287
- */
288
- export function decodeMotelyItemName(input) {
289
- const itemType = resolveMotelyItemType(input);
290
- if (itemType === null)
291
- return null;
292
- if (_cache.has(itemType))
293
- return _cache.get(itemType) ?? null;
294
- const category = motelyItemCategory(itemType);
295
- if (category === "Unknown") {
296
- _cache.set(itemType, null);
297
- return null;
298
- }
299
- const decoded = decodeMotelyItem(input);
300
- const name = decoded?.displayName ?? motelyItemDisplayName(itemType);
301
- _cache.set(itemType, name);
302
- return name;
303
- }
304
- /** Warm the cache for a batch of item type values. */
305
- export function warmMotelyItemCache(itemTypes) {
306
- for (const t of itemTypes) {
307
- decodeMotelyItemName(t);
308
- decodeMotelyItem(t);
309
- }
310
- }
311
- /** Number of unique items decoded so far. */
312
- export function motelyItemCacheSize() {
313
- return _cache.size;
314
- }
143
+ // Cache warming is now a no-op since we use runtime enums directly
144
+ export function warmMotelyItemCache() { }
145
+ export function motelyItemCacheSize() { return 0; }
@@ -13,3 +13,7 @@ export interface MotelySpriteData {
13
13
  * resolves it to a sprite atlas path and grid coordinates for rendering.
14
14
  */
15
15
  export declare function motelyItemToSprite(rawValue: number): MotelySpriteData | null;
16
+ /**
17
+ * Resolves a sprite by name and category without needing a Motely integer.
18
+ */
19
+ export declare function getMotelySpriteByName(name: string, category?: MotelyRenderableCategory): MotelySpriteData | null;
@@ -1,5 +1,6 @@
1
1
  import { decodeMotelyItem } from "./motelyItemDecoder.js";
2
2
  import { getSpriteData, SHEET_META } from "../sprites/spriteMapper.js";
3
+ import { RANK_MAP, SUIT_MAP } from "../sprites/spriteData.js";
3
4
  import { resolveJamlAssetUrl } from "../assets.js";
4
5
  /**
5
6
  * Given a raw motely-wasm item value (which may be a bitpacked integer or raw MotelyItemType),
@@ -9,6 +10,21 @@ export function motelyItemToSprite(rawValue) {
9
10
  const decoded = decodeMotelyItem(rawValue);
10
11
  if (!decoded)
11
12
  return null;
13
+ if (decoded.category === "playing" && decoded.rank && decoded.suit) {
14
+ const col = RANK_MAP[decoded.rank];
15
+ const row = SUIT_MAP[decoded.suit];
16
+ if (col !== undefined && row !== undefined) {
17
+ return {
18
+ atlasPath: resolveJamlAssetUrl('deck'),
19
+ gridCol: col,
20
+ gridRow: row,
21
+ gridCols: 13,
22
+ gridRows: 4,
23
+ displayName: decoded.displayName,
24
+ category: "playing"
25
+ };
26
+ }
27
+ }
12
28
  const sprite = getSpriteData(decoded.displayName);
13
29
  if (!sprite)
14
30
  return null;
@@ -25,3 +41,44 @@ export function motelyItemToSprite(rawValue) {
25
41
  category: decoded.category
26
42
  };
27
43
  }
44
+ /**
45
+ * Resolves a sprite by name and category without needing a Motely integer.
46
+ */
47
+ export function getMotelySpriteByName(name, category = "unknown") {
48
+ if (category === "playing") {
49
+ // Attempt to parse "Rank of Suit"
50
+ const match = /^(.*?)\s+of\s+(.*?)$/i.exec(name);
51
+ if (match) {
52
+ const rank = match[1];
53
+ const suit = match[2];
54
+ const col = RANK_MAP[rank];
55
+ const row = SUIT_MAP[suit];
56
+ if (col !== undefined && row !== undefined) {
57
+ return {
58
+ atlasPath: resolveJamlAssetUrl('deck'),
59
+ gridCol: col,
60
+ gridRow: row,
61
+ gridCols: 13,
62
+ gridRows: 4,
63
+ displayName: name,
64
+ category: "playing"
65
+ };
66
+ }
67
+ }
68
+ }
69
+ const sprite = getSpriteData(name);
70
+ if (!sprite)
71
+ return null;
72
+ const meta = SHEET_META[sprite.type];
73
+ if (!meta)
74
+ return null;
75
+ return {
76
+ atlasPath: resolveJamlAssetUrl(meta.assetKey),
77
+ gridCol: sprite.pos.x,
78
+ gridRow: sprite.pos.y,
79
+ gridCols: meta.cols,
80
+ gridRows: meta.rows,
81
+ displayName: name,
82
+ category
83
+ };
84
+ }