feat: fix PRNG algorithm and add native Bun cracker#16
feat: fix PRNG algorithm and add native Bun cracker#16CoreSheep wants to merge 1 commit intopaoloanzn:mainfrom
Conversation
Add companion buddy hatching tools: crack.mjs for brute-forcing desired buddy traits, hatch.py for the Python-based hatching logic, verify.mjs for verifying cracked UUIDs, and CLAUDE.md project config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR adds three new executable scripts implementing a creature companion generation system: Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Python as Python (hatch.py)
participant Bun as Bun CLI
participant JS as verify.mjs
User->>Python: --crack --target "rare species"
Python->>Python: Interactive trait selection
Note over Python: Generate random userID candidates
loop Until match found
Python->>Bun: Generate batch hashes for candidates
Bun-->>Python: Hash values
Python->>Python: Derive attributes via PRNG
Python->>Python: Check if matches target criteria
end
Python->>JS: Execute with matched userID
JS-->>Python: JSON attributes from verify
Python->>Python: Verify match & display companion
Python-->>User: Print cracked userID + companion details
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crack.mjs`:
- Around line 99-100: The log currently emits the cracked value under the wrong
key `userid`; change the output to use the correct `accountUuid` (or `uuid`) key
instead by replacing the `userid: candidate` entry in the JSON payload with
`accountUuid: candidate` (keeping the rest of the object spread of `result`,
`attempts`, and `elapsed_sec` intact), so the console.log(JSON.stringify(...))
call reflects the correct field name for downstream consumers.
- Around line 62-64: The current substring matching using target.includes(...)
causes "uncommon" to match "common"; change matching in the
wantRarity/wantSpecies detection to test whole tokens instead: split/tokenize
the target string (e.g., on whitespace/punctuation) into normalized words and
check if any token equals an entry from RARITIES or SPECIES, or alternatively
use word-boundary regex (\b) when testing each r in RARITIES and s in SPECIES;
update the blocks that set wantRarity and wantSpecies to use this exact-token
check against target instead of includes to avoid partial matches.
In `@hatch.py`:
- Around line 572-580: The parsing loops for RARITIES and SPECIES wrongly match
shorter tokens like "common" before "uncommon", causing want_rarity/want_species
to be set incorrectly in hunt() and crack(); fix by matching longest tokens
first or using whole-word/token comparison: reorder or sort RARITIES and SPECIES
by descending length (or split target into words and compare for equality)
before the for-loops that set want_rarity and want_species so "uncommon" will be
matched before "common".
- Around line 635-640: The Bun crack flow is still generating 64-char hex
"userID" values and instructing users to edit "userID"; change this to generate
and use 36-char UUIDs (accountUuid) to match the validated client flow: replace
the random 64-char hex generator with a UUID v4 generator, update all literal
"userID" usages to "accountUuid", ensure Bun.hash and the SplitMix32
seeding/forward steps consume the 36-char UUID string (or its exact canonical
bytes) the real client hashes, and update comments/outputs accordingly (also
apply the same replacements where "userID" appears in the other occurrences
noted).
- Around line 845-850: The verification currently only compares verified.rarity
and verified.species so other fields (eye, hat, shiny, stats, etc.) can differ
and still print "matches"; update the check in the hatch verification block
(where hatch(userid) is assigned to verified and compared to comp) to compare
the entire companion object—either call a comprehensive equality method (e.g.,
implement/use Companion.__eq__ or a compare_companions(verified, comp) helper)
or explicitly check all relevant attributes (verified.eye, verified.hat,
verified.shiny, verified.stats, rarity, species) and only print the success
message when all fields match, otherwise print the warning.
- Around line 779-781: The guard in _interactive_select(mode="crack") only
checks want_rarity, want_species, and want_shiny and therefore wrongly rejects
valid hat-only or eye-only searches; update the conditional to include the
hat/eye flags (e.g., want_hat and want_eye) so the check becomes "if not
(want_rarity or want_species or want_shiny or want_hat or want_eye):" and also
update the printed prompt to list 'hat' and 'eye' along with RARITIES, SPECIES
and 'shiny' so the user message accurately reflects the selectable traits.
In `@verify.mjs`:
- Around line 77-81: The current top-level code in verify.mjs falls back to a
hard-coded UUID by using process.argv[2] || '<sample>', so change the behavior
to require an explicit UUID: remove the default fallback and check the variable
(uuid from process.argv[2]) and if missing print a concise usage/error message
and exit non‑zero; keep the existing SALT, hashString and roll calls but only
run them when uuid is provided. Locate the usage around the uuid assignment and
the console.log calls (symbols: process.argv[2], uuid, SALT, hashString, roll)
and implement the guard that prints usage and calls process.exit(1) when no UUID
is supplied.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 525783dd-d7ec-40ae-9ff0-7d3d152427f8
📒 Files selected for processing (4)
CLAUDE.mdcrack.mjshatch.pyverify.mjs
| let wantRarity = null, wantSpecies = null, wantShiny = target.includes('shiny') | ||
| for (const r of RARITIES) { if (target.includes(r)) { wantRarity = r; break } } | ||
| for (const s of SPECIES) { if (target.includes(s)) { wantSpecies = s; break } } |
There was a problem hiding this comment.
Parse target traits as whole tokens.
target.includes(r) makes "uncommon" match "common" first, so an uncommon request is cracked as common. Tokenize the input or use word boundaries before matching rarities/species.
Proposed fix
-let wantRarity = null, wantSpecies = null, wantShiny = target.includes('shiny')
-for (const r of RARITIES) { if (target.includes(r)) { wantRarity = r; break } }
-for (const s of SPECIES) { if (target.includes(s)) { wantSpecies = s; break } }
+const tokens = new Set(target.split(/\s+/).filter(Boolean))
+let wantRarity = null, wantSpecies = null, wantShiny = tokens.has('shiny')
+for (const r of RARITIES) { if (tokens.has(r)) { wantRarity = r; break } }
+for (const s of SPECIES) { if (tokens.has(s)) { wantSpecies = s; break } }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let wantRarity = null, wantSpecies = null, wantShiny = target.includes('shiny') | |
| for (const r of RARITIES) { if (target.includes(r)) { wantRarity = r; break } } | |
| for (const s of SPECIES) { if (target.includes(s)) { wantSpecies = s; break } } | |
| const tokens = new Set(target.split(/\s+/).filter(Boolean)) | |
| let wantRarity = null, wantSpecies = null, wantShiny = tokens.has('shiny') | |
| for (const r of RARITIES) { if (tokens.has(r)) { wantRarity = r; break } } | |
| for (const s of SPECIES) { if (tokens.has(s)) { wantSpecies = s; break } } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crack.mjs` around lines 62 - 64, The current substring matching using
target.includes(...) causes "uncommon" to match "common"; change matching in the
wantRarity/wantSpecies detection to test whole tokens instead: split/tokenize
the target string (e.g., on whitespace/punctuation) into normalized words and
check if any token equals an entry from RARITIES or SPECIES, or alternatively
use word-boundary regex (\b) when testing each r in RARITIES and s in SPECIES;
update the blocks that set wantRarity and wantSpecies to use this exact-token
check against target instead of includes to avoid partial matches.
| const elapsed = ((performance.now() - start) / 1000).toFixed(2) | ||
| console.log(JSON.stringify({ userid: candidate, ...result, attempts, elapsed_sec: elapsed })) |
There was a problem hiding this comment.
Don’t publish the cracked UUID as userid.
This tool is explicitly cracking UUID/accountUuid values, but the success payload still uses the old userid key. That makes it easy for downstream consumers to keep wiring the wrong field.
Proposed fix
- console.log(JSON.stringify({ userid: candidate, ...result, attempts, elapsed_sec: elapsed }))
+ console.log(JSON.stringify({ accountUuid: candidate, ...result, attempts, elapsed_sec: elapsed }))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const elapsed = ((performance.now() - start) / 1000).toFixed(2) | |
| console.log(JSON.stringify({ userid: candidate, ...result, attempts, elapsed_sec: elapsed })) | |
| const elapsed = ((performance.now() - start) / 1000).toFixed(2) | |
| console.log(JSON.stringify({ accountUuid: candidate, ...result, attempts, elapsed_sec: elapsed })) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crack.mjs` around lines 99 - 100, The log currently emits the cracked value
under the wrong key `userid`; change the output to use the correct `accountUuid`
(or `uuid`) key instead by replacing the `userid: candidate` entry in the JSON
payload with `accountUuid: candidate` (keeping the rest of the object spread of
`result`, `attempts`, and `elapsed_sec` intact), so the
console.log(JSON.stringify(...)) call reflects the correct field name for
downstream consumers.
| for r in RARITIES: | ||
| if r in target.lower(): | ||
| want_rarity = r | ||
| break | ||
|
|
||
| for s in SPECIES: | ||
| if s in target.lower(): | ||
| want_species = s | ||
| break |
There was a problem hiding this comment.
uncommon is parsed as common in both CLI paths.
Both loops check if r in target.lower() while iterating RARITIES in ["common", "uncommon", ...] order. A target like uncommon shiny matches common first, so hunt() and crack() search for the wrong rarity.
Also applies to: 770-777
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hatch.py` around lines 572 - 580, The parsing loops for RARITIES and SPECIES
wrongly match shorter tokens like "common" before "uncommon", causing
want_rarity/want_species to be set incorrectly in hunt() and crack(); fix by
matching longest tokens first or using whole-word/token comparison: reorder or
sort RARITIES and SPECIES by descending length (or split target into words and
compare for equality) before the for-loops that set want_rarity and want_species
so "uncommon" will be matched before "common".
| # Runs the full crack in a Bun script for native performance: | ||
| # - Generate random 64-char hex userIDs | ||
| # - Hash with Bun.hash (wyhash) — the real Claude Code hash | ||
| # - Run SplitMix32 PRNG forward | ||
| # - Check if derived traits match the target | ||
| # - Output the first matching userID |
There was a problem hiding this comment.
This crack flow still targets the old identifier.
It brute-forces 64-char hex userIDs and tells users to edit "userID", while the validated Bun flow elsewhere in this PR moved to 36-char UUID/accountUuid values. Keeping this path as-is will keep generating values the real client never hashes.
Also applies to: 689-693, 733-735, 856-859
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hatch.py` around lines 635 - 640, The Bun crack flow is still generating
64-char hex "userID" values and instructing users to edit "userID"; change this
to generate and use 36-char UUIDs (accountUuid) to match the validated client
flow: replace the random 64-char hex generator with a UUID v4 generator, update
all literal "userID" usages to "accountUuid", ensure Bun.hash and the SplitMix32
seeding/forward steps consume the 36-char UUID string (or its exact canonical
bytes) the real client hashes, and update comments/outputs accordingly (also
apply the same replacements where "userID" appears in the other occurrences
noted).
| if not want_rarity and not want_species and not want_shiny: | ||
| print(f" Specify at least one trait: {', '.join(RARITIES + SPECIES + ['shiny'])}") | ||
| return |
There was a problem hiding this comment.
Hat-only / eye-only interactive searches are blocked.
_interactive_select(mode="crack") lets users choose hat and eye, but this guard ignores both fields and rejects the request unless rarity/species/shiny is also set.
Proposed fix
- if not want_rarity and not want_species and not want_shiny:
- print(f" Specify at least one trait: {', '.join(RARITIES + SPECIES + ['shiny'])}")
+ if not any([want_rarity, want_species, want_shiny, want_hat, want_eye]):
+ print(" Specify at least one trait.")
return📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if not want_rarity and not want_species and not want_shiny: | |
| print(f" Specify at least one trait: {', '.join(RARITIES + SPECIES + ['shiny'])}") | |
| return | |
| if not any([want_rarity, want_species, want_shiny, want_hat, want_eye]): | |
| print(" Specify at least one trait.") | |
| return |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hatch.py` around lines 779 - 781, The guard in
_interactive_select(mode="crack") only checks want_rarity, want_species, and
want_shiny and therefore wrongly rejects valid hat-only or eye-only searches;
update the conditional to include the hat/eye flags (e.g., want_hat and
want_eye) so the check becomes "if not (want_rarity or want_species or
want_shiny or want_hat or want_eye):" and also update the printed prompt to list
'hat' and 'eye' along with RARITIES, SPECIES and 'shiny' so the user message
accurately reflects the selectable traits.
| # Verify with our own pipeline | ||
| verified = hatch(userid) | ||
| if verified.rarity != comp.rarity or verified.species != comp.species: | ||
| print(f" {BOLD}WARNING: verification mismatch — hash inconsistency{RESET}") | ||
| else: | ||
| print(f" {DIM}Verified: hatch(userID) matches Bun output{RESET}") |
There was a problem hiding this comment.
Verify the full companion before printing “matches”.
This currently compares only rarity and species, so a mismatch in eye, hat, shiny, or stats still reports a successful verification.
Proposed fix
- if verified.rarity != comp.rarity or verified.species != comp.species:
+ if (
+ verified.rarity != comp.rarity
+ or verified.species != comp.species
+ or verified.eye != comp.eye
+ or verified.hat != comp.hat
+ or verified.shiny != comp.shiny
+ or verified.stats != comp.stats
+ ):
print(f" {BOLD}WARNING: verification mismatch — hash inconsistency{RESET}")
else:
print(f" {DIM}Verified: hatch(userID) matches Bun output{RESET}")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Verify with our own pipeline | |
| verified = hatch(userid) | |
| if verified.rarity != comp.rarity or verified.species != comp.species: | |
| print(f" {BOLD}WARNING: verification mismatch — hash inconsistency{RESET}") | |
| else: | |
| print(f" {DIM}Verified: hatch(userID) matches Bun output{RESET}") | |
| # Verify with our own pipeline | |
| verified = hatch(userid) | |
| if ( | |
| verified.rarity != comp.rarity | |
| or verified.species != comp.species | |
| or verified.eye != comp.eye | |
| or verified.hat != comp.hat | |
| or verified.shiny != comp.shiny | |
| or verified.stats != comp.stats | |
| ): | |
| print(f" {BOLD}WARNING: verification mismatch — hash inconsistency{RESET}") | |
| else: | |
| print(f" {DIM}Verified: hatch(userID) matches Bun output{RESET}") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hatch.py` around lines 845 - 850, The verification currently only compares
verified.rarity and verified.species so other fields (eye, hat, shiny, stats,
etc.) can differ and still print "matches"; update the check in the hatch
verification block (where hatch(userid) is assigned to verified and compared to
comp) to compare the entire companion object—either call a comprehensive
equality method (e.g., implement/use Companion.__eq__ or a
compare_companions(verified, comp) helper) or explicitly check all relevant
attributes (verified.eye, verified.hat, verified.shiny, verified.stats, rarity,
species) and only print the success message when all fields match, otherwise
print the warning.
| const uuid = process.argv[2] || '8df629b4-c274-22d7-4b23-c2993422180d' | ||
| console.log('UUID:', uuid) | ||
| console.log('Key:', uuid + SALT) | ||
| console.log('Hash:', hashString(uuid + SALT)) | ||
| console.log('Result:', JSON.stringify(roll(uuid), null, 2)) |
There was a problem hiding this comment.
Require an explicit UUID here.
Falling back to a hard-coded sample makes a missing argument look like a successful verification run. For a verifier CLI, returning a usage error is safer than silently checking the demo value.
Proposed fix
-const uuid = process.argv[2] || '8df629b4-c274-22d7-4b23-c2993422180d'
+const uuid = process.argv[2]
+if (!uuid) {
+ console.error('Usage: bun verify.mjs "<uuid>"')
+ process.exit(1)
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const uuid = process.argv[2] || '8df629b4-c274-22d7-4b23-c2993422180d' | |
| console.log('UUID:', uuid) | |
| console.log('Key:', uuid + SALT) | |
| console.log('Hash:', hashString(uuid + SALT)) | |
| console.log('Result:', JSON.stringify(roll(uuid), null, 2)) | |
| const uuid = process.argv[2] | |
| if (!uuid) { | |
| console.error('Usage: bun verify.mjs "<uuid>"') | |
| process.exit(1) | |
| } | |
| console.log('UUID:', uuid) | |
| console.log('Key:', uuid + SALT) | |
| console.log('Hash:', hashString(uuid + SALT)) | |
| console.log('Result:', JSON.stringify(roll(uuid), null, 2)) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@verify.mjs` around lines 77 - 81, The current top-level code in verify.mjs
falls back to a hard-coded UUID by using process.argv[2] || '<sample>', so
change the behavior to require an explicit UUID: remove the default fallback and
check the variable (uuid from process.argv[2]) and if missing print a concise
usage/error message and exit non‑zero; keep the existing SALT, hashString and
roll calls but only run them when uuid is provided. Locate the usage around the
uuid assignment and the console.log calls (symbols: process.argv[2], uuid, SALT,
hashString, roll) and implement the guard that prints usage and calls
process.exit(1) when no UUID is supplied.
|
This should be properly intergrated into the atucal buddy code, not just be a few bash sricpts |
Summary
Built on top of the reverse-engineering work from claude-buddy-hatchery. While using the original
hatch.py, I discovered a critical algorithm mismatch that produces incorrect companion results. This PR adds corrected native Bun scripts and includes the original Python implementation for reference.What was the problem
The original
hatch.pyuses SplitMix32 as its PRNG, but the actual Claude Code binary (2.1.89+) uses Mulberry32. These are different algorithms with different output sequences — meaning every cracked userID fromhatch.pyproduces the wrong companion when used in the real Claude Code client.Additionally,
hatch.pycracks 64-char hexuserIDstrings, but Claude Code actually seeds the companion fromaccountUuid(a 36-char UUID format like8df629b4-c274-22d7-4b23-c2993422180d).Key differences found
hatch.py(original)crack.mjs/verify.mjs(this PR)userID(64-char hex)accountUuid(36-char UUID)What's included
crack.mjs— Native Bun companion crackeraccountUuid(the field Claude Code actually reads)verify.mjs— UUID verification toolhatch.py— Original Python hatchery (reference)How I found the issue
hatch.py.claude.jsonand ran/buddyhatch.pyimplements SplitMix32 but the binary uses Mulberry32crack.mjswith the correct algorithm, verified results matchFuture improvements
accountUuidcracking mode tohatch.py(Python Mulberry32 implementation)Usage
Credits
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features