mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 07:58:55 -07:00
[econ] currency WIP
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
-- Room currencies — a room's own money.
|
||||
--
|
||||
-- A creator mints one for their room ("MySpecialTokens"), hands it out for doing things in
|
||||
-- there, and sells things for it: finish the obstacle course, earn 50 tokens, spend them on a
|
||||
-- custom shirt at the shop by the door. Play money for one room, meaning nothing outside it.
|
||||
--
|
||||
-- A row here is the CURRENCY — its name, its coin art, how fast the room may hand it out.
|
||||
-- What a player has actually earned is `room_balance` (migration 0019). Owned by the `econ`
|
||||
-- worker; generated from src/room-currency-db.ts (ROOM_CURRENCY_SCHEMA_DDL) — keep in sync.
|
||||
--
|
||||
-- These are NOT balances and must never reach the `balance` table, which is keyed by
|
||||
-- (account_id, currency_type) alone. That key cannot say WHICH room's currency a row is, so
|
||||
-- one room's tokens would spend in every other room — see CurrencyType in src/balance-db.ts,
|
||||
-- where RoomCurrency is left out of the spendable set for exactly this reason.
|
||||
--
|
||||
-- Every currency here is CurrencyType 300 (RoomCurrency) — that is what the type MEANS — and
|
||||
-- is told apart from the others by its own `currency_id`, so the type is not a column.
|
||||
--
|
||||
-- `currency_id` is a GUID rather than an autoincrement: it is how the client names this
|
||||
-- currency everywhere afterwards, and a room's currencies are minted independently of every
|
||||
-- other room's, so an id that says nothing about ordering is the honest one.
|
||||
--
|
||||
-- `limit_amount`, not `limit` — LIMIT is a SQL keyword, and a column that has to be quoted at
|
||||
-- every use is a column that will eventually not be. It holds the most of this currency the
|
||||
-- room may award PER DAY: a faucet rate, not a cap on what a player may hold. Nothing enforces
|
||||
-- it yet — no day's awards are tracked anywhere — so it is stored and served and otherwise
|
||||
-- inert.
|
||||
--
|
||||
-- `shape` and `color` are the client's own indices into its coin art (the create body sends
|
||||
-- `Shape=0&Color=19`); nothing interprets them, they are stored and served back. `image_name`
|
||||
-- is separate and stays empty until a room can upload custom coin art — the client's model
|
||||
-- carries both, so the column is here rather than in a later migration.
|
||||
--
|
||||
-- NOTE: `image_name` was NOT NULL DEFAULT '' here, and 0018 makes it nullable — the client
|
||||
-- serves null, not an empty string, for a currency with no custom coin art. This file is left
|
||||
-- as it shipped; the current shape of the table is 0017 + 0018.
|
||||
CREATE TABLE IF NOT EXISTS room_currency (
|
||||
currency_id TEXT PRIMARY KEY,
|
||||
room_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
limit_amount INTEGER NOT NULL,
|
||||
shape INTEGER NOT NULL,
|
||||
color INTEGER NOT NULL,
|
||||
image_name TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL,
|
||||
modified_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Every read is "this room's currencies" — the whole access pattern.
|
||||
CREATE INDEX IF NOT EXISTS idx_room_currency_room ON room_currency (room_id);
|
||||
@@ -0,0 +1,45 @@
|
||||
-- Make `room_currency.image_name` nullable, and turn the empty strings already stored into
|
||||
-- nulls. Owned by the `econ` worker; 0017 created the table with `image_name TEXT NOT NULL
|
||||
-- DEFAULT ''`. Generated from src/room-currency-db.ts (ROOM_CURRENCY_SCHEMA_DDL, which mirrors
|
||||
-- the table as it stands AFTER this migration) — keep in sync.
|
||||
--
|
||||
-- The client's own model has `ImageName` as a nullable string and serves NULL for a currency
|
||||
-- with no custom coin art; an empty string is a different value to its decoder, so a currency
|
||||
-- created before this read back as having art named "". Nothing can upload coin art yet, so
|
||||
-- every row is affected and every one of them should be null.
|
||||
--
|
||||
-- A rebuild rather than an ALTER: SQLite cannot drop a NOT NULL constraint in place. This is
|
||||
-- the standard four-step swap — build the new shape, copy across, drop the old, rename — and
|
||||
-- the index goes with the dropped table, so it is recreated at the end rather than left to be
|
||||
-- noticed later by a slow query. `NULLIF(image_name, '')` is the conversion: it maps '' to
|
||||
-- NULL and leaves a real name alone, so a row that somehow already carries art keeps it.
|
||||
--
|
||||
-- Safe to re-run in spirit but not in fact — it is a migration, applied once, and the rename
|
||||
-- would fail on a second pass because `room_currency_new` no longer exists.
|
||||
CREATE TABLE room_currency_new (
|
||||
currency_id TEXT PRIMARY KEY,
|
||||
room_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
limit_amount INTEGER NOT NULL,
|
||||
shape INTEGER NOT NULL,
|
||||
color INTEGER NOT NULL,
|
||||
image_name TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
modified_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO room_currency_new (
|
||||
currency_id, room_id, name, description, limit_amount, shape, color, image_name, created_at, modified_at
|
||||
)
|
||||
SELECT
|
||||
currency_id, room_id, name, description, limit_amount, shape, color, NULLIF(image_name, ''), created_at, modified_at
|
||||
FROM room_currency;
|
||||
|
||||
DROP TABLE room_currency;
|
||||
|
||||
ALTER TABLE room_currency_new RENAME TO room_currency;
|
||||
|
||||
-- Recreated because dropping the old table took its index with it. Same index 0017 defined:
|
||||
-- every read is "this room's currencies".
|
||||
CREATE INDEX IF NOT EXISTS idx_room_currency_room ON room_currency (room_id);
|
||||
@@ -0,0 +1,28 @@
|
||||
-- How much of a room's own money each player has earned — the other half of room currencies
|
||||
-- (the currencies themselves are `room_currency`, migration 0017). Written by
|
||||
-- `POST /api/roomcurrencies/v1/awardCurrency/bulk`, which is how a room pays a player for
|
||||
-- finishing its obstacle course. Owned by the `econ` worker; generated from
|
||||
-- src/room-currency-db.ts (ROOM_BALANCE_SCHEMA_DDL) — keep in sync.
|
||||
--
|
||||
-- Keyed by CURRENCY, not by currency type, and that difference is the whole point: the
|
||||
-- account-scoped `balance` table's (account_id, currency_type) key cannot say WHICH room's
|
||||
-- currency a row is, so one room's tokens would spend in every other room. Room currencies
|
||||
-- are left out of that table for exactly this reason (see CurrencyType in src/balance-db.ts)
|
||||
-- and live here instead, keyed by the `currency_id` that names one room's currency uniquely.
|
||||
--
|
||||
-- The room is not a column: `room_currency` already says which room a currency belongs to,
|
||||
-- and repeating it here would be a second place for that to be wrong.
|
||||
--
|
||||
-- A missing row is a balance of zero. Nothing is inserted until a player is first awarded
|
||||
-- some, so a room's currency costs a row per player who has actually earned it rather than
|
||||
-- one per player who has walked in.
|
||||
CREATE TABLE IF NOT EXISTS room_balance (
|
||||
currency_id TEXT NOT NULL,
|
||||
player_id INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (currency_id, player_id)
|
||||
);
|
||||
|
||||
-- "What does this player hold across the room?" walks the room's currencies, so the reverse
|
||||
-- lookup earns its own index rather than a scan of every holder of every currency.
|
||||
CREATE INDEX IF NOT EXISTS idx_room_balance_player ON room_balance (player_id);
|
||||
@@ -0,0 +1,19 @@
|
||||
-- The purchase offers a room sells its own currency through — "5 SuperTokens for 500 Rec
|
||||
-- Center Tokens" — served by `GET /api/roomcurrencies/v1/getPurchaseOffersBatch?ids=`. Owned
|
||||
-- by the `econ` worker; 0017 created the table. Generated from src/room-currency-db.ts
|
||||
-- (ROOM_CURRENCY_SCHEMA_DDL, which mirrors the table as it stands after 0017 + 0018 + this)
|
||||
-- — keep in sync.
|
||||
--
|
||||
-- A JSON column rather than a table of its own. Offers are a small, ordered list that is only
|
||||
-- ever read, written and replaced AS A WHOLE, for one currency at a time — the client shows
|
||||
-- them as that currency's shop and reorders them together. A table would buy per-offer
|
||||
-- queries nothing asks for, and cost a join on the one read that matters.
|
||||
--
|
||||
-- Each offer holds `CurrencyPurchaseOfferId`, `Order`, `Name`, `CurrencyAmount`, `Price` and
|
||||
-- `ModifiedAt`. It does NOT hold `CurrencyId`: the row it lives on already says which currency
|
||||
-- these are offers for, and the read projects it back in. Storing it would be a second place
|
||||
-- for that to be wrong.
|
||||
--
|
||||
-- NULL, not '[]', for a currency with no offers — a currency nobody has built a shop for has
|
||||
-- no list, and the read treats null and an unparseable value alike as "no offers".
|
||||
ALTER TABLE room_currency ADD COLUMN purchase_offers TEXT;
|
||||
Reference in New Issue
Block a user