Credits
Players pay for building in credits. One credit is one cent of a plan's face value, and everything a player uses is charged at twice what it costs the platform. The prices are in src/lib/credit-prices.ts (PLANS, MARKUP, MODEL_PRICES and the asset prices), the ledger in src/lib/credits.ts, the usage screen in src/lib/usage.ts.
Usage is always recorded and taken off the balance, so the usage screen is right whether or not credits are enforced. CRESCENT_METERING=on enforces them.
| Metering off | Metering on | |
|---|---|---|
| The builder's Claude calls | Read from the run's log when the run ends (recordBuilderUsage) | Charged by the edge's meter, call by call |
| Nova, box time | Charged as they happen | Same |
| Art, sounds and 3D models | Made without asking, then recorded | Wait for the player's yes in the confirm box; paid up front, failures refunded |
| At zero | Nothing is refused | Builds stop and ship their last checkpoint; chat answers 402 |
What is charged
| What | How |
|---|---|
| The builder's Claude calls | Per call, by model and tokens (input, output, cache reads and writes), plus web searches |
| Nova's model calls | Per step, by agent/hooks/meter.ts on eve's step.completed event |
| The run's sandbox | On every 15-second poll (pollRun) |
| Art, sounds and 3D models | Up front, after the player approves |
Not charged to the player: a game's cover art, and merge runs for community builds. Both are recorded at cost with no credits taken.
Monthly credits
Every balance goes back to the monthly allowance once a month, on the day of the month the account was made. Whatever was left does not carry over. The refill is lazy: refillIfDue runs whenever a balance is read or a call is authorized, and writes a monthly ledger row once per period.
| Variable | Default | What |
|---|---|---|
CRESCENT_MONTHLY_CREDITS | 1000 | What every balance refills to |
CRESCENT_STARTER_CREDITS | none | A one-time bonus on top of the first month |
CRESCENT_OWNER_USER_IDS | none | Accounts that also see the platform's cost and every player's usage |
The ledger
users.credit_balance holds the balance, and credit_ledger every change, in micro-credits (1 credit is 1,000,000). Each line also records what it cost the platform (cost_usd). Every change carries a unique key, so repeating one changes nothing: that is what makes the edge's and the workflow's retries safe. A balance never goes below zero.
npm run credits:grant -- <userId> <credits> [note] grants credits by hand.
The usage screen
GET /api/app/usage returns what is left of the month's credits, when they refill, and a log grouped for people: one entry per build (its Claude tokens per model, sandbox minutes, assets), one per day of chat with Nova, and refills, grants and refunds on their own. ?back=1 is the month before.
The builder's meter
With metering on, the sandbox never gets an Anthropic key. Claude Code gets ANTHROPIC_BASE_URL pointing at the edge (/llm/<runId>) and an llm token good for that run only (llmToken() in src/lib/realtime.ts). The edge Worker holds the real key (edge/src/index.ts, edge/src/meter.ts).
- Before each call the edge asks the server (
POST /api/internal/meter/authorize). A run that has ended, a model not inMODEL_PRICESor a player at zero is refused. Otherwise the answer says how many output tokens the balance pays for, and that becomes the call'smax_tokens. - When Anthropic reports the input (the stream's first event), the edge charges it (
POST /api/internal/meter/charge). If that used up the balance, the call is cut off. - When the call ends, however it ends, the edge charges the output: Anthropic's final count, or an estimate from the text that went by if the call was cut off.
At zero the next call gets a 402 with billing_error, which makes Claude Code exit. The next poll finds the balance gone, stops the script and ships the last checkpoint. Nova gets [Build stopped].
Only /v1/messages and /v1/messages/count_tokens are reachable through the meter. The meter lives at the edge rather than in the box because the builder has full permissions in its box and follows player-written requests, so anything counted or kept there could be read or changed.
Nova
POST /eve/v1/app/chats/:chatId/messages answers 402 with code: "out_of_credits" at zero, and make_game and change_game will not start a build. The meter hook charges each step and ends the turn when that used up the balance. Moonspell's own system messages to Nova, such as [Build ready], are charged but never cancelled, because the player still has to hear what happened.
The confirm box
Art, sounds and 3D models wait for the player when metering is on. After the builder's pass the workflow prices the builder's list (quoteAssets), stores the quote on the run, publishes an assets event and waits on the hook run-assets:<runId> for up to ten minutes. The app shows what would be made, the price and the balance.
- Yes:
POST /api/app/runs/:id/assetswith{approve: true}takes the whole price up front, or answers 402 when the balance is short. When the assets are done,settleAssetsreturns the share of whatever failed. - No, or no answer: the version ships with its placeholders, and nothing is charged.
Going live
The edge needs the server's meter routes, and the server needs the edge's meter, so the order matters:
- The edge Worker needs a Workers plan with more CPU per request than the free plan, since it reads each stream.
- Apply the migrations:
npm run db:migrate. - Set the edge secrets
ANTHROPIC_API_KEYandINTERNAL_API_SECRET(the server's value) and deploy the edge. - Deploy the server with
CRESCENT_METERINGstill off, so the routes exist. - Grant credits to testers, then set
CRESCENT_METERING=onand redeploy.
To turn it off, unset CRESCENT_METERING and redeploy. Runs go back to the key on the server, and balances stay as they are.