Credits & billing
Implementation: src/server/services/credits.ts. UI: /studio/billing, /studio/[projectId]/budget.
Append-only ledger
Balance is always SUM(delta) over CreditLedger. There is no stored balance column and rows are never mutated.
This costs one aggregate per balance read and buys: a permanent audit trail, per-generation cost tracing from day one, visible spend-and-refund pairs, and no possibility of a balance drifting out of sync with its history.
Reserve → settle
enqueue() estimate cost, DEBIT it immediately
processGeneration() run
success settle(reserved, actual) → refunds the difference
failure refund(reserved) in full
Debiting up front is what makes concurrent requests safe — two simultaneous generations cannot both pass a balance check and overdraw.
Pricing
Roughly, per second of video: Economy 3, Balanced 9, Cinema 18.
Prices are cost × margin, so the pricing itself teaches the intended workflow. Locking a composition on the cheap tier and finishing on the expensive one is ~6× cheaper than iterating in Cinema — the number does the arguing.
Cost transparency
Part 4.8 makes this a competitive feature, not a nicety. Every path shows cost before spending:
- Generate button:
Generate · ~54 credits - Director: "Regenerating the ending = 6 shots ≈ 240 credits in Cinema / 40 in Economy — proceed?"
- Cascade gate: total plus per-node breakdown
- Script Studio: per-scene cost across all three modes, before a frame is shot
- Storyboard gate: still cost and video cost stated separately
Guardrails
- Dedupe — identical requests within 10 minutes reuse the prior result. Critically, dedupe still satisfies the request: if the target shot has no output, the earlier asset is re-linked. Returning "reused, no credits spent" alongside an empty shot is a bug, and was one.
- Draft:final ratio — target ≥5:1. Surfaced on the project overview with an explanation when it drops below.
- Cost per finished minute — a first-class analytic. "Finished" means a render exists, not that status is currently
rendered; a shot queued for regeneration after a cascade still has footage.
Optimize Cost
proposeCostOptimization proposes per-shot downgrades with an honest, per-shot quality impact string — not an averaged reassurance.
It never proposes downgrading a close-up of a locked character with dialogue. That is precisely where cheap models fail visibly, and a tool that suggests it once will not be trusted again.
Savings are a percentage of the projected cost of the shots considered, not of credits already spent. Measuring against spend produces nonsense early in a project, when little is rendered but the whole film is still ahead.
Plans
Free / Creator / Pro / Studio / Enterprise, defined in PLANS with resolution caps, watermarking, Universe limits, LoRA access, priority queue and Publishing Hub scope.
Free-tier publishing to the Theater is included by design — an empty catalog is worse than a free one, and both sides of the marketplace need supply before they pay.
Stripe is not wired. Plan selection is display-only until STRIPE_SECRET_KEY is set, and the UI says so. The ledger is fully live: generations really debit, refunds really return.