Weave Archive
Co-create movement archive with a partner via entangled measurements that keep two contributions correlated in real time.
Entanglement· correlated co-creation
Section · Quantum
full primer →The hook.
Two choreographers share an entangled session: when one makes a move on the movement archive, the other's side updates in correlated lockstep.
Why this primitiveEntanglement is the right primitive here because movement archive reduces to a correlated co-creation problem; the kernel returns a result you can drop straight into the UI.
Kernel
a Bell-pair / GHZ entanglement kernel that returns correlated measurement outcomes for two players
Drives the UI as
a two-pane co-creation surface where one side's reveal updates the other in correlated steps
Appendix A
The mega-prompt.
This prompt is engineered to ship in a single Lovable build. Real Quantinuum Guppy/Selene circuit runs in the Linux sandbox at build time and the results are baked in as JSON. read the build strategy →
~21.8 KB419 lines1 msg · ~5 credits
Build "Weave Archive"—a one-page quantum dance co-creation app. Ship in ONE Lovable message (~5 credits).
CONSTRAINTS:
- Free tier only. No accounts, auth, Lovable Cloud, or database.
- Single static page + "About the quantum" footer strip.
- All quantum runs AT BUILD TIME in sandbox; zero runtime Python.
QUANTUM (Guppy + Selene):
1. pip install "guppylang>=1.0" in sandbox.
2. Create quantum/kernel.py: a real .py file with a @guppy Bell-pair entanglement kernel returning correlated measurement outcomes for two players. Grid: 8–20 inputs (e.g., 16 movement poses).
3. Create quantum/run.py: import the kernel, compile via guppylang, run on Selene emulator (≤8 qubits, 256 shots) over all inputs. Write src/data/quantum-results.json: array of {input, output} where output is raw kernel measurement data.
4. Execute quantum/run.py once during build. Commit quantum-results.json. No Python at runtime.
FRONTEND (TanStack Start, single route /):
- Import quantum-results.json statically.
- Layout: Card (left sidebar) for input picker tied to grid; Card (center) for live visual of selected record; Card (right, two-pane Tabs) for correlated co-creation—left pane shows Player 1 moves, right pane shows Player 2 moves, both driven by entangled JSON outcomes.
- "Quantum trace" disclosure (Collapsible): show raw kernel output for active record + toggle to display quantum/kernel.py source inline (prove it's real Selene entanglement).
- Design: dark workspace, one accent color (deep purple or teal for dance), one typeface (Inter or Geist), tactile Slider or Badge interactions when picking records.
BUILD ORDER (prevent scope creep):
1. Write quantum/kernel.py (Bell-pair, @guppy, real file).
2. Write quantum/run.py (import, compile, Selene shots, JSON output).
3. Run quantum/run.py in sandbox; commit quantum-results.json.
4. Build /: Card + Tabs + Collapsible + Badge components.
5. Wire input picker → JSON record → visual + co-creation panes.
6. Add "Quantum trace" disclosure with kernel source toggle.
7. Dark theme + accent color + micro-interactions.
8. Deploy.
Every number on screen must trace to real Selene output. Ship as one deploy.
--- QUANTINUUM APPENDIX (self-contained reference — paste verbatim) ---
[1] INSTALL (Lovable Linux sandbox, Python >= 3.12):
python -m pip install --no-cache-dir "guppylang>=1.0" numpy
# Guppy v1 is a BREAKING release: the Selene emulator now ships INSIDE guppylang.
# Do NOT pip-install the old standalone emulator package, and do NOT use pre-v1 syntax.
[2] IMPORTS (Guppy v1 — use exactly these, no other quantum libs):
# quantum/kernel.py and quantum/run.py top
from guppylang import guppy, OptimizationLevel
from guppylang.std.builtins import array, output # `output`, NOT `result`
from guppylang.std.quantum import qubit, h, cx, rx, ry, rz, measure, measure_array, discard, t as tgate, tdg
from guppylang.std.angles import angle, pi
from selene_sim import Quest # error models still live here
from selene_sim.error_models import (
IdealErrorModel, DepolarizingErrorModel, SimpleLeakageErrorModel,
)
import math, json, sys, tempfile, importlib.util, uuid
from pathlib import Path
[3] HARD RULES (violating any breaks the build):
- GUPPY v1: use output("tag", v) — `result()` no longer exists. measure(q) returns a
Measurement object; call .read() before using it in output(...) or an `if`.
Run via my_kernel.emulator(...).with_shots(S).run() — the pre-v1 build() helper is gone.
- @guppy reads source via inspect.getsource → kernels MUST live in a real .py file on disk. No exec(), no REPL strings, no inline templates.
- Allowed gate set ONLY: h, rx, ry, rz, cx, tgate, tdg. There is NO native ccx/toffoli, cswap, cphase, or crz — decompose using the snippets in [7].
- Qubit ownership: a qubit passed to a function is moved. You MUST measure() or discard() every qubit exactly once; never reuse after measure.
- Angle hygiene before baking a float into generated source:
theta = ((theta + math.pi) % (2.0 * math.pi)) - math.pi
and write it with repr: f"... {theta!r} ..." (str(float) can truncate).
[4] SELENE SHOT LOOP (Guppy v1 emulator builder — the ONLY correct form):
res = (
my_kernel # the @guppy program itself, NOT .compile()
.emulator(n_qubits=N)
.with_shots(S)
.with_seed(7)
.with_simulator(Quest())
.with_error_model(IdealErrorModel())
.run()
)
shots = []
for shot in res: # EmulatorResult is iterable
shots.append({str(tag): int(v) for tag, v in shot.entries})
# N = MAX number of qubits simultaneously LIVE in the kernel.
# measure(q) releases the slot, so one ancilla reused across k windows still counts as 1.
# The pre-v1 build(prog.compile()) + shot-loop helper NO LONGER EXISTS.
# Other builder methods: with_n_processes, with_timeout, with_verbose, with_progress_bar.
# ALWAYS pin .with_seed(<int>) — an unseeded run is not reproducible evidence.
# If you report GATE COUNTS, also pin program.with_opt_level(OptimizationLevel.Classical);
# v1 optimises on compile and will otherwise flatter your numbers.
[5] DRIVER PATTERN — sweep a kernel over many inputs (closures do NOT work):
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT))
def run_one(params: dict, shots: int = 256):
# Bake params as literals into a fresh .py file that imports your kernel helpers.
src = (
"from quantum.kernel import guppy, my_helper\n"
"@guppy\n"
"def program() -> None:\n"
f" my_helper({params['a']!r}, {params['b']!r})\n"
)
tmp = Path(tempfile.gettempdir()) / "qprogs"; tmp.mkdir(exist_ok=True)
name = f"prog_{uuid.uuid4().hex[:8]}"
path = tmp / f"{name}.py"; path.write_text(src)
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod # register BEFORE exec_module
spec.loader.exec_module(mod)
res = (
mod.program
.emulator(n_qubits=5)
.with_shots(shots)
.with_simulator(Quest())
.run()
)
out = []
for shot in res:
out.append({str(l): int(v) for l, v in shot.entries})
return out
[6] PER-QUBIT INTEGER DECODE (host-side):
# kernel emits: for j in range(n): output(f"x{j}", measure(q[j]).read())
# measure() returns a Measurement in v1 — .read() gives the bool/bit.
# Compiler error if you forget: "Values of type 'Measurement' cannot be passed to 'output' directly".
def decode(rec, n):
x = 0
for j in range(n): x |= (rec.get(f"x{j}", 0) & 1) << j
return x
[7] DECOMPOSITION LIBRARY (copy verbatim into quantum/kernel.py):
# ---- Toffoli (CCX) from H, CX, T, Tdg — 6-T standard decomposition ----
@guppy
def toffoli(c1: qubit, c2: qubit, tgt: qubit) -> None:
h(tgt)
cx(c2, tgt); tdg(tgt)
cx(c1, tgt); tgate(tgt)
cx(c2, tgt); tdg(tgt)
cx(c1, tgt); tgate(c2); tgate(tgt)
h(tgt)
cx(c1, c2); tgate(c1); tdg(c2)
cx(c1, c2)
# ---- CSWAP (Fredkin) from CX + Toffoli ----
@guppy
def cswap(c: qubit, a: qubit, b: qubit) -> None:
cx(b, a)
toffoli(c, a, b)
cx(b, a)
# ---- Controlled phase exp(i*theta) on |11> from rz + cx ----
@guppy
def cphase(c: qubit, d: qubit, theta: float) -> None:
rz(d, angle(theta / 2.0))
cx(c, d)
rz(d, angle(-theta / 2.0))
cx(c, d)
# ---- Amplitude-encoded feature state (3 floats in [0,1] → 2-qubit state) ----
@guppy
def prep_features(q0: qubit, q1: qubit, a: float, b: float, c: float) -> None:
ry(q0, angle(a))
ry(q1, angle(b))
cx(q0, q1)
rz(q1, angle(c))
# ---- SWAP test kernel; HOST inverts: F = clamp(2*P(anc=0) - 1, 0, 1) ----
@guppy
def swap_test(ai: float, bi: float, ci: float,
aj: float, bj: float, cj: float) -> None:
anc = qubit()
pi0 = qubit(); pi1 = qubit()
pj0 = qubit(); pj1 = qubit()
prep_features(pi0, pi1, ai, bi, ci)
prep_features(pj0, pj1, aj, bj, cj)
h(anc)
cswap(anc, pi0, pj0)
cswap(anc, pi1, pj1)
h(anc)
output("anc", measure(anc).read())
discard(pi0); discard(pi1); discard(pj0); discard(pj1)
# n_qubits = 5 for swap_test above.
[8] CLASSICAL CROSS-CHECK (NumPy reference — commit alongside quantum result):
import numpy as np
I = np.eye(2); X = np.array([[0,1],[1,0]])
def RY(t): c,s = math.cos(t/2), math.sin(t/2); return np.array([[c,-s],[s,c]])
def RZ(t): return np.array([[np.exp(-1j*t/2),0],[0,np.exp(1j*t/2)]])
CX = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])
def prep_psi(a,b,c):
s = np.kron(RY(a)@np.array([1,0]), RY(b)@np.array([1,0]))
s = CX @ s
s = np.kron(I, RZ(c)) @ s
return s
def classical_fidelity(p, q):
return float(abs(np.vdot(prep_psi(*p), prep_psi(*q)))**2)
# Commit BOTH quantum and classical values per record:
# {"input": [...], "quantum": 0.873, "classical": 0.881, "shots": 256}
[9] FRONTEND HANDOFF (TanStack Start):
# quantum/run.py last step:
Path("src/data/quantum-results.json").write_text(json.dumps({
"records": records, # list of {input, quantum, classical, ...}
"circuit": {"n_qubits": 5, "shots": 256},
"kernel_source": Path("quantum/kernel.py").read_text(), # show on /code tab
}, indent=2))
# In React: import data from "@/data/quantum-results.json" (resolveJsonModule:true).
# Render data.records; render data.kernel_source in a <pre> inside the Quantum trace tab.
[10] BUILD ORDER (do not deviate — keeps you under 5 credits):
1. Write quantum/kernel.py with the helpers from [7] you actually need.
2. Write quantum/run.py: driver pattern from [5], Selene loop from [4], cross-check from [8], JSON write from [9].
3. Execute once: python quantum/run.py (commit src/data/quantum-results.json).
4. Build the React route: static JSON import, shadcn Card + Tabs + Slider + Badge wiring.
5. Deploy. No Python at runtime; every on-screen number traces to committed Selene output.
[11] OUTPUT CONTRACT — selene_run v1 (write this exact shape to src/data/quantum-results.json):
{
"schemaVersion": 1,
"experiment": "<idea-slug>",
"title": "<idea title>",
"description": "<one plain-English sentence — what a non-quantum dev sees>",
"kernel": { "snippet": "<first 40 lines of quantum/kernel.py>", "qubits": <int>, "shotsPerRow": <int> },
"verdict": { "text": "<one-sentence headline of the result>", "good": true },
"metrics": [ { "name": "<label>", "value": <number>, "unit": "<%|bits|Hz|...>", "good": true } ],
"series": [ { "id": "<id>", "kind": "histogram"|"bar"|"line", "title": "...",
"xLabel": "...", "yLabel": "...", "yKeys": ["count"],
"points": [ { "label": "<x>", "values": { "count": <num> } } ] } ]
}
Also include provenance so the result is checkable:
"extras": { "shots": <int>, "seed": <int>, "nQubits": <int>,
"errorModel": "IdealErrorModel"|"DepolarizingErrorModel(...)",
"guppy": ">=1.0", "generatedAt": "<ISO date>" }
NO ESCAPE HATCH: extras is for non-rendered metadata ONLY. Anything the user is
meant to SEE belongs in metrics[] or series[] — if you need extras to render, the
mapping is wrong. Render the provenance line as plain text under the chart.
Reason: one schema = one renderer = a polished demo without bespoke React per idea.
All metric / series VALUES must come from real Selene shot data committed in step 3 — no mocks.
[12] RENDERER — create src/components/SeleneRunView.tsx with EXACTLY this body (no deps beyond react + tailwind):
import * as React from "react";
type Point = { label: string; values: Record<string, number> };
type Series = { id: string; kind: "histogram"|"bar"|"line"; title: string;
xLabel?: string; yLabel?: string; yKeys: string[]; points: Point[] };
type Metric = { name: string; value: number; unit?: string; good?: boolean };
export type SeleneRun = {
schemaVersion: 1; experiment: string; title: string; description: string;
kernel: { snippet: string; qubits: number; shotsPerRow: number };
verdict: { text: string; good: boolean };
metrics: Metric[]; series: Series[]; notes?: string;
};
const fmt = (n: number) => Math.abs(n) >= 100 ? n.toFixed(0) : Math.abs(n) >= 1 ? n.toFixed(2) : n.toFixed(3);
function Bars({ s }: { s: Series }) {
const max = Math.max(1, ...s.points.flatMap(p => s.yKeys.map(k => p.values[k] ?? 0)));
return (
<div className="space-y-1">
{s.points.map((p, i) => (
<div key={i} className="flex items-center gap-2 text-xs">
<div className="w-20 truncate text-muted-foreground">{p.label}</div>
<div className="flex-1 h-3 bg-muted rounded-sm overflow-hidden">
<div className="h-full bg-primary" style={{ width: `${(100*(p.values[s.yKeys[0]]??0))/max}%` }} />
</div>
<div className="w-12 text-right tabular-nums">{fmt(p.values[s.yKeys[0]]??0)}</div>
</div>
))}
</div>
);
}
function Line({ s }: { s: Series }) {
const W=320, H=120, P=20;
const ys = s.points.map(p => p.values[s.yKeys[0]] ?? 0);
const min = Math.min(...ys), max = Math.max(...ys), span = max - min || 1;
const pts = ys.map((y, i) => {
const x = P + (i*(W-2*P))/Math.max(1, ys.length-1);
const yy = H - P - ((y - min)/span)*(H - 2*P);
return `${x},${yy}`;
}).join(" ");
return (
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-32">
<polyline fill="none" stroke="currentColor" strokeWidth="2" points={pts} className="text-primary" />
</svg>
);
}
export function SeleneRunView({ run }: { run: SeleneRun }) {
return (
<div className="space-y-6">
<header>
<div className="text-xs uppercase tracking-wider text-muted-foreground">{run.experiment}</div>
<h2 className="text-2xl font-semibold">{run.title}</h2>
<p className="text-sm text-muted-foreground">{run.description}</p>
<div className={`mt-2 inline-block px-3 py-1 rounded-full text-xs ${run.verdict.good?"bg-emerald-500/15 text-emerald-400":"bg-amber-500/15 text-amber-400"}`}>
{run.verdict.text}
</div>
</header>
<section className="grid grid-cols-2 md:grid-cols-4 gap-3">
{run.metrics.map((m, i) => (
<div key={i} className="rounded-lg border border-border p-3">
<div className="text-[10px] uppercase tracking-wider text-muted-foreground">{m.name}</div>
<div className="text-xl font-semibold tabular-nums">{fmt(m.value)}<span className="text-xs text-muted-foreground ml-1">{m.unit}</span></div>
</div>
))}
</section>
<section className="space-y-6">
{run.series.map(s => (
<div key={s.id} className="rounded-lg border border-border p-4">
<div className="flex items-baseline justify-between mb-3">
<div className="text-sm font-medium">{s.title}</div>
<div className="text-[10px] text-muted-foreground">{s.xLabel} / {s.yLabel}</div>
</div>
{s.kind === "line" ? <Line s={s} /> : <Bars s={s} />}
</div>
))}
</section>
<footer className="text-[11px] text-muted-foreground">
kernel: {run.kernel.qubits} qubits · {run.kernel.shotsPerRow} shots/row
</footer>
</div>
);
}
Then in the route: import data from "@/data/quantum-results.json"; <SeleneRunView run={data as any} />.
Quantum trace tab: <pre>{data.kernel.snippet}</pre>.
[13] SKILL v2 GOTCHAS (must-follow)
- angle(x) is HALFTURNS, not radians. angle(0.5) == π/2 (S gate).
For a radian θ, write angle(θ / math.pi). Never pass raw radians.
- If a source formula contains an explicit π (e.g. t = π/(16·h)),
divide the π out BEFORE angle(): angle(1/16), NOT angle(math.pi/16).
- Only three Selene noise models exist:
from selene_sim.error_models import (
IdealErrorModel, DepolarizingErrorModel, SimpleLeakageErrorModel,
)
There is NO coherent / T1-T2 model — do not import one.
- Optional "noisy vs ideal" second pass (adds one extra series):
run the same kernel twice, once with IdealErrorModel() and once with
DepolarizingErrorModel(p_1q=1e-4, p_2q=1.29e-3, p_meas=0.9e-3),
emit both under series[].points so the chart shows the gap.
- Run the CLASSICAL baseline BEFORE writing any quantum code and keep its number.
An encoding bug that scores a perfect 1.000 on every input is caught by the
baseline, never by the circuit.
- Shot-noise pass bar is 4*sqrt(0.5/shots) — see [16]. Do NOT use 3*sqrt(p(1-p)/n);
it produces false FAILs whenever a probability sits near 0 or 1.
- Hardware footer line (render below the chart, one <div> only):
"Runs today on Helios (98 qubits). Roadmap: Sol → Apollo → Lumos."
[14] BUILD SAFETY (Lovable sandbox + production runtime)
- Results ship as a COMMITTED JSON file only. Do NOT create a
createServerFn, API route, or handler that shells out to Python or
reads a cache directory: production stubs child_process and blocks
arbitrary filesystem reads, so it builds and then fails live.
- If you install deps with `pip install --target .pydeps ...`, add
`.pydeps/` to .gitignore FIRST. An unignored vendored dep tree makes
the build time out and roll back, wasting credits.
- Make the sweep resumable: before moving to the next row, write it to
_cache_run/<row-tag>.json and skip rows whose cache file exists.
Assemble src/data/quantum-results.json from the cache at the end.
Keep the grid small (<= 100 rows, <= 256 shots/row) so one pass fits.
[15] IF THE TURN ROLLS BACK (Lovable orchestration)
- A big Guppy/Selene turn can fail with "An internal error occurred". That is
a task-transaction rollback: every file write from that turn is discarded.
Do not debug the app — nothing broke. Re-do the work in smaller gates.
- Author in ATOMIC GATES, one per message, each ending in a saved artifact:
(a) quantum/kernel.py + a smoke run of a handful of shots
(b) quantum/run.py resumable driver (per-row cache)
(c) the cached sweep -> src/data/quantum-results.json
(d) the React route reading the committed JSON
A rollback then costs one gate, not the whole build.
- Persistence canary: on a fresh session, make one trivial edit and end the
turn. If it persists, larger gates are safe.
[16] EVIDENCE & VERIFICATION (do this, it is what makes the demo credible)
- STEP 0, before any quantum code: compute the CLASSICAL baseline for your task
(cosine similarity, brute-force search, direct FFT — whatever the honest
non-quantum answer is). Print it, keep it, and show it next to the quantum
number in the UI. If the quantum result is identical to a trivial baseline on
every input, your encoding is degenerate — fix the encoding, not the chart.
- PASS BAR for any probability comparison (quantum shots vs NumPy reference):
import math
tol = 4.0 * math.sqrt(0.5 / shots) # shots = shots PER CELL
ok = abs(p_measured - p_expected) <= tol
At 256 shots tol ~= 0.088. Use this exact form. The textbook
3*sqrt(p(1-p)/n) form collapses to ~0 near p=0 or p=1 and reports false FAILs.
- The verdict.good flag in [11] MUST be computed from that comparison
(e.g. good = all(cells_ok)), never hand-written as true.
- ANGLE SANITY CELL (30 seconds, saves the whole sweep): before the full run,
do one 256-shot single-qubit check — prepare h(q); rz(q, angle(0.5)); h(q),
measure, and assert P(1) ~= 0.5 within tol. If it comes out 0 or 1 you passed
radians into angle() somewhere. Halfturns, always (see [13]).
- Report shots + seed + tolerance in the UI next to the verdict. A number
without its shot count and seed is a rumour, not a result.
[17] OPTIONAL — LIVE SHOT STREAM (stretch goal, ONLY if you still have credits)
Skip this unless the committed JSON demo from [1]-[12] is already working. It is
polish, not proof.
- Selene/Python cannot run in production. For a live "watch the shots land" panel,
port ONE small kernel (<= 4 qubits, gates H / RX / RZ / CX / measure) to a pure
TypeScript statevector mini-sim in src/lib/mini-sim.ts: complex amplitudes as two
Float64Array's, one closed-form unitary per gate, projective measure with collapse
(compute P(0), draw vs rng(), zero the dead branch, renormalise).
- Mirror the Guppy gate ORDER exactly and return the same labelled keys your
Python driver passes to output(...). Same kernel, two runtimes.
- Stream it from src/routes/api/public/shots.ts as Server-Sent Events
(`data: ${JSON.stringify(shot)}\n\n`), seeded from a URL param so a session is
reproducible. The client shows a "Live-verified" badge once >= 500 streamed shots
land within the [16] tolerance of the analytic probability, "Drift" otherwise.
- HARD LIMIT: <= 4 qubits and no noise model. Anything bigger, anything needing real
compilation or a noise model, and anything that IS the claim stays in the committed
Selene JSON. The stream is a UX layer on top of an already-verified experiment.
[HOOK] ENTANGLEMENT — Bell / GHZ + witness.
Bell: h(q0); cx(q0, q1).
GHZ_n: h(q0); for j in 1..n-1: cx(q0, q[j]).
Measure all qubits twice — once in Z basis, once after h() on each → X basis.
Host: <ZZ…Z> = E[(-1)**parity_Z], <XX…X> = E[(-1)**parity_X].
GHZ witness: W = 0.5 - 0.5*(<ZZ…Z> + <XX…X>). W < 0 certifies genuine multipartite entanglement.
selene_run mapping:
metrics: [ {"name":"witness W","value":W,"unit":"","good":W<0},
{"name":"⟨ZZ…Z⟩","value":zz,"unit":""},
{"name":"⟨XX…X⟩","value":xx,"unit":""} ]
series: [ {"id":"z-parity","kind":"bar","title":"Z-basis parity counts",
"xLabel":"parity","yLabel":"shots","yKeys":["count"],
"points":[{"label":"even","values":{"count":z_even}},
{"label":"odd","values":{"count":z_odd}}]} ]Market sizing.
TAM
$16.0B
the global dance industry (~$5B; >2M studios worldwide).
SAM
$800M
the 5% of that market actively buying movement archive-adjacent software.
SOM
$8M
a realistic 1% capture of the serviceable slice in years 1–3 via the hackathon launch and creator-led distribution.
Indicative figures for hackathon pitches — refine with your own research before raising.
Adjacent entries.
choreography drafting
Pact Drafting
Co-create choreography drafting with a partner via entangled measurements that keep two contributions correlated in real time.
ensemble synchronizationBridge Synchronization
Co-create ensemble synchronization with a partner via entangled measurements that keep two contributions correlated in real time.
improvisation promptsLattice Prompt
Co-create improvisation prompts with a partner via entangled measurements that keep two contributions correlated in real time.
rehearsal schedulingBell Scheduling
Co-create rehearsal scheduling with a partner via entangled measurements that keep two contributions correlated in real time.