feat(templates): add murder-mystery, a playable SQL detective game - #2582
Open
arsalann wants to merge 3 commits into
Open
feat(templates): add murder-mystery, a playable SQL detective game#2582arsalann wants to merge 3 commits into
arsalann wants to merge 3 commits into
Conversation
`bruin init murder-mystery` scaffolds a project; `bruin run` seeds a whole fictional town into DuckDB in about nine seconds — 12,400 residents, 14,200 handsets, 4.9M tower registrations, 74 MB — and the player investigates an assassination with `bruin query`, materializing findings as assets under `assets/notebook/`. The case is sized so that no single-column filter isolates anybody: the smallest crowd standing in any one attribute around any of the four culprits is 23 people. You get to a person by intersecting several attributes and clearing the ones who look guilty and are not. Determinism without random(). Every value derives from an MD5 digest of the row's own identity plus a per-column salt, so generation is order-independent and re-derivable. MD5 rather than hash() because MD5 is a published standard and cannot drift when DuckDB is upgraded; an unstable hash would silently invalidate a player's notes. Two clean installs produce byte-identical contents in all 31 tables. Exact stage cardinalities are hit by construction, not by tuning distributions and hoping: forced rows come from an override table and the rest of each quota is filled by ranking a broad pool. The same shape is used for certificates, qualifications, squad selection, plate series and cordon traffic, so no block looks special. The generator never writes down who it picked. Roles are assigned by ranking broad pools (every male aged 30-55, ~2,200 people) and taking the first row, attributes are stamped onto the chosen resident rather than filtered for, and the scaffolding schema that holds the assignment is dropped by the last asset. No citizen id and no full name appears anywhere in the template. Solvability checks live in integration-tests/templates/qa-murder-mystery because their query text is the deduction path. They assert each stage's cardinality, all twelve base rates, decoy resolvability, that no case-file row leaks a name, plate or number, and — the one that matters — that the four threads converge on the residents the generator actually chose. Cardinality alone can pass while the stages lead somewhere else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Prompt To Fix All With AI### Issue 1
templates/murder-mystery/.bruin.yml:7
**Shared DuckDB project state**
When two murder-mystery projects are initialized in one Git repository, both register `duckdb-default` as `ashmont.db` in the repository-root configuration, causing their runs to share and overwrite the same generated database and player notebook state.
### Issue 2
integration-tests/integration_test.go:2162-2166
**Lengthy test rationale comments**
This multi-line comment summarizes the broader game and QA design instead of concisely describing the adjacent workflow, making the test flow harder to scan; the same pattern also appears in the added init-template tests.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(templates): add murder-mystery, a p..." | Re-trigger Greptile |
…play prompt Renames Ashmont to Yorkville and re-registers its districts, streets, buildings and landmarks in homage to the ridge above a real city — Loma House on the escarpment above Wychwood Square, Austin Terrace and Walmer Road across its north side, Macpherson Mews behind it, the Nordheimer Vale ravine lands the council refused to rezone. The town and everyone in it stay fictional. The rename is label-only by construction: every pool keeps its length and order, so each row still draws the same index and only the string changes. The five people the generator picks are byte-identical before and after, all 136 solvability checks still pass, and two clean installs remain identical. Adds PLAY_WITH_AN_AGENT.md, a prompt you hand to a coding agent from a standing start. It installs Bruin, initializes the case, builds the town, reads out the briefing, and then stops and waits — carrying the one-step-at-a-time rules with it, since an agent handed the prompt does not yet have the project's AGENTS.md to read. Also addresses review feedback and two problems the rename exposed: - The connection is now duckdb-yorkville rather than duckdb-default. Nine other templates ship that name and bruin init merges into one config per Git repository, so in a repo that already held one of them this pipeline silently wrote the whole town into that template's database and yorkville.db never appeared. Verified fixed: both connections now coexist and the run lands in the right file. - A trading name no longer contradicts the trade it is registered under. The suffix follows the sector, so nothing files as "Roofing" while registered as a surveyor. business_words_b is gone with it, as is merchant_names, which was never referenced. - Ravine lands, not a riverside quay, now that the site is a vale. - Trimmed the long rationale comments in the new tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Reviews (2): Last reviewed commit: "refactor(murder-mystery): rename the tow..." | Re-trigger Greptile |
…play The README explained the game but did not walk anyone through playing it. It is now five numbered steps from install to first query, with the reference material in tables rather than prose: - a table of which document to read for what - the three case-paper queries to run first, with row counts so you know what to expect - a 'where to look for what' table mapping investigative questions to tables, so the 474-line data dictionary is not the only route in - the mechanics a player cannot infer and will otherwise waste time on: the time of the shot, that tower registrations are 15-minute buckets on a shared grid (which is what makes co-location analysis possible at all), and that the rally hours are recorded at four times the usual resolution - what a finished answer looks like, which was only in CASE_FILE.md before Also states where the database actually lands. 'bruin run .' from inside the pipeline folder writes yorkville.db one level up at the repo root, next to .bruin.yml, which is not where you would look for it. Every command in the file was run against a fresh install. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Reviews (3): Last reviewed commit: "docs(murder-mystery): restructure the RE..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
bruin init murder-mystery ashmont-casescaffolds a project;bruin runseeds a whole fictional town into DuckDB in 8.9s (74 MB, no credentials, no configuration) and the player investigates an assassination withbruin query, materializing findings as assets underassets/notebook/.Spec: the TRD in
.context/attachments/Wp3IX1/sql-murder-mystery-trd.md.What ships
templates/murder-mystery/integration-tests/templates/qa-murder-mystery/The two things that make it a game rather than a quiz
No single-column filter isolates anybody. The smallest crowd standing in any one attribute around any of the four culprits is 23 people.
WHERE sex = 'M' AND height_cm BETWEEN 186 AND 194returns 403. You reach a person by intersecting several attributes and clearing the ones who look guilty and are not.The generator never writes down who it picked. Roles are assigned by ranking a broad pool (every male aged 30–55, ~2,200 people) and taking the first row; attributes are then stamped onto the chosen resident rather than filtered for.
grepfor any culprit's citizen id or full name across the template returns nothing, and the scaffolding schema holding the assignment is dropped by the last asset.Determinism
Every value derives from an MD5 digest of the row's own identity plus a per-column salt — order-independent, re-derivable, and uncorrelated across columns. MD5 rather than
hash()because MD5 is a published standard and cannot drift on a DuckDB upgrade; an unstable hash would silently invalidate a player's notes.Two clean installs produce byte-identical contents in all 31 tables.
Two traps found while proving that, both fixed:
row_number() OVER (ORDER BY ...)over a non-total ordering, so ties could break by scan order.md5(string_agg(x))over an ordered subquery does not preserve order through the aggregate under parallel execution, and reported six phantom mismatches.Hitting exact cardinalities
The TRD's stage counts can't be reached by tuning distributions and hoping. They're hit by construction: forced rows come from an override table, and the rest of each quota is filled by ranking a broad pool. The same shape is reused for certificates, qualifications, squad selection, plate series and cordon traffic, so no block looks special.
Verified: S5 61 · S6 9 · S7 3 · S8 1 · D2 58 · D3 7 · D4 1 · D5 1 named driver · H1 1 · H2 3 · H3 1 (runner-up 7 towers) · P1 1 (min-nights 15 vs 6). All 12 base rates within tolerance.
Three TRD stages did not survive contact with a realistic model
What the QA checks actually prove
Cardinality checks alone can pass while the stages lead somewhere else entirely.
qa.integrityreads the scaffolding to learn who the generator chose and asserts the four threads land on exactly those residents:Layered onto a fresh install: 54 assets, 136 checks, all green.
Verification
bruin validateclean (48 assets). Three genuinely missingdependswere caught by it and fixed.make format— 0 issues.make test— pass, 99 packages.make integration-test-light— the newmurder_mystery_solvabilityworkflow passes in 22.9s. Two pre-existing failures inquery-semantic-cli-flags/query-semantic-window-metric(TIMESTAMPvsDATE) reproduce with this branch stashed and are unrelated.DATA_DICTIONARY.mdis generated from the asset headers, so it cannot drift from the schema.Not done, and it should gate the merge
TRD acceptance §11 is a blind playtest by two people who have not read the spec, solving all four roles in 60–90 minutes. The checks prove the counts and that the threads converge; they cannot prove a stage is findable, which is the exact failure that criterion exists to catch. §12 (an assistant given only
AGENTS.mddeclines to solve the case) is also untested here —AGENTS.mdships the ten rules, but I have not run an agent against it.🤖 Generated with Claude Code