Skip to content

Commit 4be5b53

Browse files
committed
LUHJLVTYKV
1 parent ef221a8 commit 4be5b53

29 files changed

+760
-145
lines changed

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
"Bash(grep -E \"\\\\.\\(ps1|sh|mjs\\)$\")",
2626
"Bash(dotnet run:*)",
2727
"Bash(dotnet-script /tmp/test_gameplay.cs)",
28-
"Bash(\"X:/JammySeedFinder/src/MotelyJAML/.cursorignore\")"
28+
"Bash(\"X:/JammySeedFinder/src/MotelyJAML/.cursorignore\")",
29+
"Bash(pnpm update:*)",
30+
"Bash(pnpm ls:*)"
2931
]
3032
}
3133
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
alwaysApply: true
3+
---
4+
5+
You are an expert in Web development, including JavaScript, TypeScript, CSS, React, Tailwind, Node.js, and Next.js. You excel at selecting and choosing the best tools, avoiding unnecessary duplication and complexity.
6+
7+
When making a suggestion, you break things down into discrete changes and suggest a small test after each stage to ensure things are on the right track.
8+
9+
Produce code to illustrate examples, or when directed to in the conversation. If you can answer without code, that is preferred, and you will be asked to elaborate if it is required. Prioritize code examples when dealing with complex logic, but use conceptual explanations for high-level architecture or design patterns.
10+
11+
Before writing or suggesting code, you conduct a deep-dive review of the existing code and describe how it works between <CODE_REVIEW> tags. Once you have completed the review, you produce a careful plan for the change in <PLANNING> tags. Pay attention to variable names and string literals—when reproducing code, make sure that these do not change unless necessary or directed. If naming something by convention, surround in double colons and in ::UPPERCASE::.
12+
13+
Finally, you produce correct outputs that provide the right balance between solving the immediate problem and remaining generic and flexible.
14+
15+
You always ask for clarification if anything is unclear or ambiguous. You stop to discuss trade-offs and implementation options if there are choices to make.
16+
17+
You are keenly aware of security, and make sure at every step that we don't do anything that could compromise data or introduce new vulnerabilities. Whenever there is a potential security risk (e.g., input handling, authentication management), you will do an additional review, showing your reasoning between <SECURITY_REVIEW> tags.
18+
19+
Additionally, consider performance implications, efficient error handling, and edge cases to ensure that the code is not only functional but also robust and optimized.
20+
21+
Everything produced must be operationally sound. We consider how to host, manage, monitor, and maintain our solutions. You consider operational concerns at every step and highlight them where they are relevant.
22+
23+
Finally, adjust your approach based on feedback, ensuring that your suggestions evolve with the project's needs.

AGENTS.md

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
A .NET library + WASM/Node packages for Balatro seed analysis. It predicts what items, jokers, tags, vouchers, etc. a given seed will produce using Balatro's PRNG system.
66

7+
### Call it JAML
8+
9+
Filter documents are **JAML** (`.jaml`). In user-facing prose and comments, prefer **JAML** over “YAML” — *YAML Ain’t Motely’s Language.* (YamlDotNet is still the parser; that’s implementation.)
10+
11+
Optional top-level **`aesthetics`** (e.g. `- palindrome`) is parsed from the JAML document and merged in `MotelySearchOrchestrator.PrepareSearch` when it doesn’t conflict with the host’s seeds / keywords / random mode.
12+
713
## Architecture: Two Thin Hosts, One Brain
814

915
```
@@ -18,6 +24,16 @@ motely-node/ ← Node.js native addon output (published to npm)
1824

1925
Both are **OUTPUT DIRECTORIES**. They are populated by `build-and-pack.ps1`.
2026

27+
### JAML JSON schema — no hand edits, no drift
28+
29+
`jaml.schema.json`, `jaml-schema.js`, and `jaml-schema.d.ts` are **generated from C#** by `Motely.CLI`:
30+
31+
```bash
32+
dotnet run --project Motely.CLI/Motely.CLI.csproj -- --write-jaml-schema
33+
```
34+
35+
`JamlSchemaGenerator` writes the same content to every mirror path (repo root, `public/`, `Motely.NodeAddon/`, `motely-wasm/`, `motely-node/`). `build-and-pack.ps1` runs this step after version bump so packs stay consistent. Do **not** edit those files manually: you will fork copies, break the npm pipeline, and fight the next generator run. New JAML surface area (e.g. `JamlAesthetic` values) belongs in **`Motely.CLI/JamlSchemaGenerator.cs`** and in the parser/enum in **`Motely`**, then regenerate.
36+
2137
### The Layering Rule
2238

2339
```
@@ -40,7 +56,6 @@ Platform hosts (WASM/Node) → MotelySearchOrchestrator → Motely (core lib
4056
- Any file authored by TacoDiva (the Motely library author)
4157

4258
### Files YOU should edit:
43-
- `Motely/MotelyGameplayState.cs` — our game state wrapper
4459
- `Motely.Orchestration/MotelySearchOrchestrator.cs` — all search logic lives here
4560
- `Motely.Orchestration/MotelySearchSession.cs` — browser instance handles / cancellation
4661
- `Motely.BrowserWasm/Interop/` — Bootsharp interfaces + implementation (calls orchestrator)
@@ -81,37 +96,35 @@ Every PRNG stream's state is ultimately a `double`. `MotelySinglePrngStream(doub
8196

8297
### ref struct vs struct
8398

84-
Some stream types are `ref struct` (stack-only, cannot be stored on the heap). When you need to store a `ref struct` as a class field, decompose its fields into a storage struct (they're just doubles and strings), then reconstruct on demand. See `StreamCache` in `MotelyGameplayState.cs`.
99+
Some stream types are `ref struct` (stack-only, cannot be stored on the heap). When you need to store a `ref struct` as a class field, decompose its fields into a storage struct (they're just doubles and strings), then reconstruct on demand.
85100

86101
Do NOT change `ref struct` to `struct` in core Motely files. Work around it.
87102

88-
### The Parked Filter Pattern
89-
90-
`MotelyGameplayState` uses the search pipeline as a **context factory**. It parks a filter on a background thread via semaphores, keeping a `MotelySingleSearchContext` alive on the stack indefinitely. This is the ONLY way to get a context for single-seed analysis.
91-
92-
**DO NOT touch the parked filter code** (`ParkedFilterDesc`, `ParkedFilter`, `CheckSeed`, the semaphore dance). It is correct.
103+
### analyzeSeed Returns a Fixed Snapshot
93104

94-
**DO NOT touch the `Cmd<T>` delegate dispatch.** It is correct.
105+
`MotelySeedAnalyzer.AnalyzeToDto()` pre-computes a fixed set of items for antes 1-8: boss, voucher, tags, draw order, shop queue, packs. This is NOT infinite — it's a snapshot. The shop queue list is finite and exhaustible.
95106

96107
### One Ante At A Time
97108

98109
The game state represents sequential gameplay. One ante at a time. When the ante changes, reset streams. Do NOT use `Dictionary<int, StreamType>` to cache per-ante.
99110

100-
### MotelyGameplayState IS the Single Seed Context
111+
### TODO: MotelyGameplayState (Not Yet Implemented)
101112

102-
It's a stateful object that wraps `MotelySingleSearchContext` for one seed. You create it, call `NextShopItem(ante)` repeatedly, and it advances the PRNG. Infinite scroll. Don't pre-compute, don't batch, don't wrap the analyzer.
113+
Infinite shop item streaming requires a stateful C# object that wraps `MotelySingleSearchContext` for one seed, advancing the shop PRNG on each call (e.g. `createSeedContext → nextShopItem(ante)`). This does NOT exist yet. Today, infinite shop streaming only works on the TypeScript Game path. Building this on the Motely/WASM side requires:
114+
1. A new C# class (`MotelyGameplayState`) that holds live PRNG state
115+
2. New WASM exports: `createSeedContext(seed, deck, stake)` → contextId, `nextShopItem(contextId, ante)` → item
116+
3. JS glue in `motelyWasm.ts` to manage context lifecycle
103117

104118
## Common Pitfalls
105119

106120
1. **"Let me redesign the architecture"** — No. Make the minimal fix. Use what exists.
107121
2. **"Let me add a shared interop API class"** — No. Both hosts call orchestrator directly.
108-
3. **"Let me wrap the analyzer output"** — No. The analyzer pre-computes a fixed set. GameState is infinite scroll.
122+
3. **"Let me wrap the analyzer output"** — No. The analyzer pre-computes a fixed snapshot. It is not infinite.
109123
4. **"Let me add a Dictionary for caching"** — No. One ante at a time. Direct fields, reset on ante change.
110-
5. **"Let me create a new search/filter"** — No. The parked filter IS the mechanism. Use it.
111-
6. **"Let me edit MotelySingleSearchContext"** — No. Use Motely. Don't edit it.
112-
7. **"Let me edit the output JS/CJS files"** — No. They're build output.
113-
8. **"These stream types should be struct not ref struct"** — Don't change core Motely types.
114-
9. **"Let me hand-roll PRNG logic"** — No. Motely provides high-level stream interfaces. Use them.
124+
5. **"Let me edit MotelySingleSearchContext"** — No. Use Motely. Don't edit it.
125+
6. **"Let me edit the output JS/CJS files"** — No. They're build output.
126+
7. **"These stream types should be struct not ref struct"** — Don't change core Motely types.
127+
8. **"Let me hand-roll PRNG logic"** — No. Motely provides high-level stream interfaces. Use them.
115128

116129
## Build
117130

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4-
<MotelyVersion>4.0.1</MotelyVersion>
4+
<MotelyVersion>4.0.2</MotelyVersion>
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageVersion Include="Bootsharp" Version="0.7.0" />

Motely.BrowserWasm/Interop/IMotelyWasmBackend.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,10 @@ Task<string> StartPalindromeSearch(
3434

3535
Task<bool> ValidateJaml(string jamlContent);
3636
Task<string> ValidateJamlWithError(string jamlContent);
37+
38+
/// <summary>
39+
/// Infinite shop item stream: get <paramref name="count"/> items starting at <paramref name="offset"/>
40+
/// for a given seed/deck/stake/ante. Deterministic and stateless — same inputs always produce same output.
41+
/// </summary>
42+
Task<string> GetShopItems(string seed, string deck, string stake, int ante, int offset, int count);
3743
}

Motely.BrowserWasm/Interop/MotelyWasmBackend.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ public Task<string> ValidateJamlWithError(string jamlContent)
114114
return Task.FromResult(error ?? "Unknown validation error");
115115
}
116116

117+
public Task<string> GetShopItems(string seed, string deck, string stake, int ante, int offset, int count)
118+
{
119+
try
120+
{
121+
var json = MotelyShopItemProvider.GetShopItems(seed, deck, stake, ante, offset, count);
122+
return Task.FromResult(json);
123+
}
124+
catch (Exception ex)
125+
{
126+
return Task.FromResult($"{{\"error\":\"{ex.Message}\"}}");
127+
}
128+
}
129+
117130
private async Task<string> RunSearch(int instanceId, string jamlContent, MotelySearchRequest request)
118131
{
119132
var session = MotelySearchSession.Get(instanceId);

Motely.CLI/JamlSchemaGenerator.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ internal static class JamlSchemaGenerator
99
{
1010
private static readonly string[] RankValues = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Jack", "Q", "Queen", "K", "King", "A", "Ace"];
1111
private static readonly string[] SuitValues = ["Hearts", "Diamonds", "Clubs", "Spades"];
12-
private static readonly string[] MetadataKeys = ["name", "author", "dateCreated", "description", "deck", "stake", "seeds"];
12+
private static readonly string[] MetadataKeys = ["name", "author", "dateCreated", "description", "deck", "stake", "seeds", "aesthetics"];
1313
private static readonly string[] SectionKeys = ["defaults", "must", "should", "mustNot"];
1414
private static readonly string[] ClauseTypeKeys = [
1515
"joker", "jokers",
@@ -152,14 +152,29 @@ private static JsonObject Generate(string version)
152152
["deck"] = EnumStringProperty(EnumNames<MotelyDeck>(), "Balatro deck to search with", "Red"),
153153
["stake"] = EnumStringProperty(EnumNames<MotelyStake>(), "Balatro stake level", "White"),
154154
["seeds"] = StringArrayProperty("Known seed examples associated with this filter."),
155+
["aesthetics"] = new JsonObject
156+
{
157+
["type"] = "array",
158+
["description"] =
159+
"Optional seed-space constraints from JamlAesthetic (see definitions/JamlAesthetic). Merged in MotelySearchOrchestrator when compatible; conflicts with host seeds, keywords, or random mode.",
160+
["items"] = new JsonObject { ["$ref"] = "#/definitions/JamlAesthetic" },
161+
},
155162
["defaults"] = BuildDefaultsProperty(),
156163
["must"] = ClauseArray("Required clauses. All listed clauses must match."),
157164
["should"] = ClauseArray("Scored clauses. Matching clauses add score but do not gate the seed by themselves."),
158165
["mustNot"] = ClauseArray("Rejected clauses. If any listed clause matches, the seed is rejected.")
159166
},
160167
["definitions"] = new JsonObject
161168
{
162-
["clause"] = BuildClauseDefinition()
169+
["clause"] = BuildClauseDefinition(),
170+
["JamlAesthetic"] = new JsonObject
171+
{
172+
["title"] = "JamlAesthetic",
173+
["description"] =
174+
"Named constraint on which seeds participate in search. Motely: see JamlAesthetics for enumeration and Matches(); seed alphabet is MotelyCore.SeedDigits, max length MotelyCore.MaxSeedLength.",
175+
["type"] = "string",
176+
["enum"] = ToJsonArray(JamlAestheticParser.KnownJamlStringsForSchema()),
177+
},
163178
},
164179
["additionalProperties"] = false
165180
};
@@ -532,6 +547,7 @@ private static Dictionary<string, string[]> BuildValueSets()
532547
{
533548
return new()
534549
{
550+
["aesthetics"] = JamlAestheticParser.KnownJamlStringsForSchema(),
535551
["deck"] = EnumNames<MotelyDeck>(),
536552
["stake"] = EnumNames<MotelyStake>(),
537553
["joker"] = EnumNames<MotelyJoker>(),

Motely.NodeAddon/MotelyNodeExports.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,12 @@ public static BlockSearchResultDto RunPalindromeSearchAsync(string jamlContent)
8888
BatchCharCount = ProcessBlockRunner.BatchCharCount,
8989
Palindrome = true,
9090
});
91+
92+
/// <summary>
93+
/// Infinite shop item stream: get <paramref name="count"/> items starting at <paramref name="offset"/>.
94+
/// Stateless and deterministic — same inputs always produce same output.
95+
/// Returns JSON array of {id, name} objects.
96+
/// </summary>
97+
public static string GetShopItems(string seed, string deck, string stake, int ante, int offset, int count) =>
98+
MotelyShopItemProvider.GetShopItems(seed, deck, stake, ante, offset, count);
9199
}

Motely.NodeAddon/jaml-schema.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
export const JAML_SCHEMA_VERSION = "4.0.1";
1+
export const JAML_SCHEMA_VERSION = "4.0.2";
22
export const jamlSchema = {
33
"$schema": "http://json-schema.org/draft-07/schema#",
44
"$id": "https://seedfinder.app/jaml.schema.json",
5-
"version": "4.0.1",
5+
"version": "4.0.2",
66
"title": "JAML - Jimbo\u0027s Ante Markup Language",
77
"description": "Schema for Balatro seed filter configuration files (.jaml)",
88
"type": "object",
@@ -68,6 +68,13 @@ export const jamlSchema = {
6868
},
6969
"description": "Known seed examples associated with this filter."
7070
},
71+
"aesthetics": {
72+
"type": "array",
73+
"description": "Optional seed-space constraints from JamlAesthetic (see definitions/JamlAesthetic). Merged in MotelySearchOrchestrator when compatible; conflicts with host seeds, keywords, or random mode.",
74+
"items": {
75+
"$ref": "#/definitions/JamlAesthetic"
76+
}
77+
},
7178
"defaults": {
7279
"type": "object",
7380
"description": "Default values applied to clauses when a clause does not specify its own values.",
@@ -1700,6 +1707,14 @@ export const jamlSchema = {
17001707
],
17011708
"additionalProperties": false,
17021709
"minProperties": 1
1710+
},
1711+
"JamlAesthetic": {
1712+
"title": "JamlAesthetic",
1713+
"description": "Named constraint on which seeds participate in search. Motely: see JamlAesthetics for enumeration and Matches(); seed alphabet is MotelyCore.SeedDigits, max length MotelyCore.MaxSeedLength.",
1714+
"type": "string",
1715+
"enum": [
1716+
"palindrome"
1717+
]
17031718
}
17041719
},
17051720
"additionalProperties": false
@@ -1711,7 +1726,8 @@ export const METADATA_KEYS = [
17111726
"description",
17121727
"deck",
17131728
"stake",
1714-
"seeds"
1729+
"seeds",
1730+
"aesthetics"
17151731
];
17161732
export const SECTION_KEYS = [
17171733
"defaults",
@@ -1808,6 +1824,9 @@ export const SOURCE_KEYS = [
18081824
];
18091825

18101826
const VALUE_SETS = {
1827+
"aesthetics": [
1828+
"palindrome"
1829+
],
18111830
"deck": [
18121831
"Red",
18131832
"Blue",

Motely.NodeAddon/jaml.schema.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "http://json-schema.org/draft-07/schema#",
33
"$id": "https://seedfinder.app/jaml.schema.json",
4-
"version": "4.0.1",
4+
"version": "4.0.2",
55
"title": "JAML - Jimbo\u0027s Ante Markup Language",
66
"description": "Schema for Balatro seed filter configuration files (.jaml)",
77
"type": "object",
@@ -67,6 +67,13 @@
6767
},
6868
"description": "Known seed examples associated with this filter."
6969
},
70+
"aesthetics": {
71+
"type": "array",
72+
"description": "Optional seed-space constraints from JamlAesthetic (see definitions/JamlAesthetic). Merged in MotelySearchOrchestrator when compatible; conflicts with host seeds, keywords, or random mode.",
73+
"items": {
74+
"$ref": "#/definitions/JamlAesthetic"
75+
}
76+
},
7077
"defaults": {
7178
"type": "object",
7279
"description": "Default values applied to clauses when a clause does not specify its own values.",
@@ -1699,6 +1706,14 @@
16991706
],
17001707
"additionalProperties": false,
17011708
"minProperties": 1
1709+
},
1710+
"JamlAesthetic": {
1711+
"title": "JamlAesthetic",
1712+
"description": "Named constraint on which seeds participate in search. Motely: see JamlAesthetics for enumeration and Matches(); seed alphabet is MotelyCore.SeedDigits, max length MotelyCore.MaxSeedLength.",
1713+
"type": "string",
1714+
"enum": [
1715+
"palindrome"
1716+
]
17021717
}
17031718
},
17041719
"additionalProperties": false

0 commit comments

Comments
 (0)