@stage5/lumine 0.3.31 → 0.3.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/admin-craft.js +235 -0
- package/lib/admin-receipts.js +25 -0
- package/lib/admin.js +495 -3
- package/lib/commands.js +41 -1
- package/lib/thumbnail.js +22 -15
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +35 -4
- package/sdk/LUMINE_ADMIN.md +183 -1
package/lib/commands.js
CHANGED
|
@@ -2478,7 +2478,14 @@ export function parseArgs(args) {
|
|
|
2478
2478
|
"show",
|
|
2479
2479
|
"foreground",
|
|
2480
2480
|
"open",
|
|
2481
|
+
"dryRun",
|
|
2482
|
+
"confirmEmailRule",
|
|
2483
|
+
"includePrefix",
|
|
2484
|
+
"allowSharedNetwork",
|
|
2481
2485
|
]);
|
|
2486
|
+
// A flag that may stand alone (true) or carry a value: `--confirm` confirms
|
|
2487
|
+
// an AI bucket change; `meetup slot <crewId> --confirm <slotIndex>` picks a slot.
|
|
2488
|
+
const optionalValueFlags = new Set(["confirm"]);
|
|
2482
2489
|
|
|
2483
2490
|
for (let i = 0; i < rest.length; i += 1) {
|
|
2484
2491
|
const arg = rest[i];
|
|
@@ -2499,6 +2506,12 @@ export function parseArgs(args) {
|
|
|
2499
2506
|
: inlineValue;
|
|
2500
2507
|
} else if (booleanFlags.has(camelKey)) {
|
|
2501
2508
|
raw[camelKey] = true;
|
|
2509
|
+
} else if (
|
|
2510
|
+
optionalValueFlags.has(camelKey) &&
|
|
2511
|
+
// a following flag (`--json`, `-h`) is never taken as the value
|
|
2512
|
+
(rest[i + 1] === undefined || rest[i + 1].startsWith("-"))
|
|
2513
|
+
) {
|
|
2514
|
+
raw[camelKey] = true;
|
|
2502
2515
|
} else {
|
|
2503
2516
|
const nextValue = rest[i + 1];
|
|
2504
2517
|
if (
|
|
@@ -2732,6 +2745,13 @@ export function parseArgs(args) {
|
|
|
2732
2745
|
: "",
|
|
2733
2746
|
adminBucketId: raw.bucketId ? String(raw.bucketId) : "",
|
|
2734
2747
|
adminDeviceKey: raw.deviceKey ? String(raw.deviceKey) : "",
|
|
2748
|
+
adminRuleId: raw.ruleId ? String(raw.ruleId) : "",
|
|
2749
|
+
adminIp: raw.ip ? String(raw.ip) : "",
|
|
2750
|
+
adminMessage: raw.message === undefined ? undefined : String(raw.message),
|
|
2751
|
+
adminDryRun: Boolean(raw.dryRun),
|
|
2752
|
+
adminConfirmEmailRule: Boolean(raw.confirmEmailRule),
|
|
2753
|
+
adminIncludePrefix: Boolean(raw.includePrefix),
|
|
2754
|
+
adminAllowSharedNetwork: Boolean(raw.allowSharedNetwork),
|
|
2735
2755
|
adminLabel: raw.label ? String(raw.label) : "",
|
|
2736
2756
|
adminUserIds: raw.userIds ? String(raw.userIds) : "",
|
|
2737
2757
|
adminUser: raw.user ? String(raw.user) : "",
|
|
@@ -2782,10 +2802,20 @@ export function parseArgs(args) {
|
|
|
2782
2802
|
adminEditionId: raw.editionId ? String(raw.editionId) : "",
|
|
2783
2803
|
adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
|
|
2784
2804
|
adminFile: raw.file ? String(raw.file) : "",
|
|
2805
|
+
// admin craft revise --item <id|name> [--why] [--emoji] [--category]
|
|
2806
|
+
// (kept raw: a flag given without a text value is refused in
|
|
2807
|
+
// admin-craft.js rather than sent as the string "true")
|
|
2808
|
+
adminCraftItem: raw.item,
|
|
2809
|
+
adminCraftWhy: raw.why,
|
|
2810
|
+
adminCraftEmoji: raw.emoji,
|
|
2811
|
+
adminCraftCategory: raw.category,
|
|
2812
|
+
adminCraftPair: raw.pair,
|
|
2785
2813
|
adminPort: raw.port ? String(raw.port) : "",
|
|
2786
2814
|
adminForeground: Boolean(raw.foreground),
|
|
2787
2815
|
adminOpen: Boolean(raw.open),
|
|
2788
2816
|
adminConfigFile: raw.config ? String(raw.config) : "",
|
|
2817
|
+
// admin review propose --thumbnail <image|workspace>
|
|
2818
|
+
adminThumbnail: raw.thumbnail ? String(raw.thumbnail) : "",
|
|
2789
2819
|
adminReviewedBuildVersion: raw.reviewedVersion
|
|
2790
2820
|
? String(raw.reviewedVersion)
|
|
2791
2821
|
: "",
|
|
@@ -3177,6 +3207,15 @@ export function printHelp() {
|
|
|
3177
3207
|
lumine admin ai-bucket accounts add --bucket-id <id> --user-ids <id,id,...> [--note <text>] [--json]
|
|
3178
3208
|
lumine admin ai-bucket device block-signup --bucket-id <id> --user <source-user-id> --device-key <inspection-key> --user-ids <reviewed-device-users> --note <reason> [--json]
|
|
3179
3209
|
lumine admin ai-bucket note set --bucket-id <id> --note <text> [--json]
|
|
3210
|
+
lumine admin ai-bucket inspect --bucket-id <id> --reason <management reason> [--include-private-evidence] [--json]
|
|
3211
|
+
lumine admin ai-bucket restrict --bucket-id <id> --mode none|signup|full [--message <text>] [--allow-shared-network] (--dry-run | --confirm) [--json]
|
|
3212
|
+
lumine admin ai-bucket rule add --bucket-id <id> --type user --user <id> [--note <text>] (--dry-run | --confirm) [--json]
|
|
3213
|
+
lumine admin ai-bucket rule add --bucket-id <id> --type email --email <address> [--note <text>] (--dry-run | --confirm-email-rule --confirm) [--json]
|
|
3214
|
+
lumine admin ai-bucket rule add --bucket-id <id> --type ip --ip <address> --scope signup|full [--include-prefix] [--allow-shared-network] [--note <text>] (--dry-run | --confirm) [--json]
|
|
3215
|
+
lumine admin ai-bucket rule add --bucket-id <id> --type device --user <source-user-id> --device-key <inspect-key> --scope signup|full --user-ids <reviewed-device-users> --note <reason> (--dry-run | --confirm) [--json]
|
|
3216
|
+
lumine admin ai-bucket rule disable --bucket-id <id> --rule-id <id> [--note <text>] (--dry-run | --confirm) [--json]
|
|
3217
|
+
(every real change needs --confirm; --dry-run shows exactly what would change and writes nothing;
|
|
3218
|
+
a shared network (academy, school) or an unmeasured prefix is refused unless --allow-shared-network, only on Mikey's explicit word)
|
|
3180
3219
|
lumine admin ai-email-policy get --email <address> [--json]
|
|
3181
3220
|
lumine admin ai-email-policy set --email <address> --mode <automatic|separate_accounts> --note <text> [--json]
|
|
3182
3221
|
lumine admin daily-run start [--scope full|featured|newspaper] [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
|
|
@@ -3202,6 +3241,7 @@ export function printHelp() {
|
|
|
3202
3241
|
lumine admin english import <verdicts.json> --pass a|b | adjudicate <file> | add-questions <file> | relevel <file> | mastery <userId> --reason <why> | backfill-distractors | summary | review [--limit <n>] | accept|retire <questionId> | retag <questionId> --skill <code> (English skill tags and bank upkeep for Grammarbles; content written by Claude Code agents)
|
|
3203
3242
|
lumine admin reward-review list [--status pending|approved|all] [--cursor <id>] [--json]
|
|
3204
3243
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3244
|
+
lumine admin craft seed --build <id> --file <pairs.json> [--confirm] | stats --build <id> [--limit <1-100>] | revise --build <id> --item <id|name> [--why <text>] [--emoji <emoji>] [--category <field>] [--confirm] | retire --build <id> --item <id|name> --reason <text> [--confirm] | unfizzle --build <id> [--since <iso>] [--pair <a+b>] [--confirm] (Twinkle.craft curation: dry run without --confirm)
|
|
3205
3245
|
lumine admin storage list [--status pending|approved|rejected|all] [--cursor <id>] | show <user-id-or-username> | approve <request-id> [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <request-id> [--reason <text>] | grant <user-id-or-username> --size <500MB|1GB|2GB|n MB> [--reason <text>]
|
|
3206
3246
|
lumine admin meetup list [--status review|active|completed|all] | approve-crew <crewId> | approve-grownup <crewId> | request-changes <crewId> --note <text> | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | parent-reply <crewId> <memberUserId> --note <text> | funnel [--days <1-90>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | show <crewId> | approve-plan <crewId> [--note <text>] | send-back <crewId> --note <text> | approve <crewId> --attended <userId,userId,...> [--note <text>] | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch "<name>" [--note <text>] | slot <crewId> --confirm <slotIndex> | resend-email <crewId> (Bridge Builder meetup quest)
|
|
3207
3247
|
lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text> (Bridge Builder story pages: publishes only what every tagged member's parent allowed)
|
|
@@ -3216,7 +3256,7 @@ export function printHelp() {
|
|
|
3216
3256
|
lumine admin chat-reports suspend --user <id> --note <why> [--json] (full ban: signs them out)
|
|
3217
3257
|
lumine admin reward-review show <review-id> [--dir <path>] [--json]
|
|
3218
3258
|
lumine admin reward-review approve <review-id> [--config <rules.json>] [--reason <text>] [--json] (approval publishes the approved version)
|
|
3219
|
-
lumine admin reward-review propose <review-id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--json]
|
|
3259
|
+
lumine admin reward-review propose <review-id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>] [--json]
|
|
3220
3260
|
lumine admin reward-review reject|revoke <review-id> --reason <text> [--json]
|
|
3221
3261
|
lumine admin recommendations list [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--content-types comment,dailyReflection] [--unviewed|--viewed] [--cursor <cursor>] [--json]
|
|
3222
3262
|
lumine admin builds candidates [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--limit <number>] [--json]
|
package/lib/thumbnail.js
CHANGED
|
@@ -134,6 +134,27 @@ export async function thumbnailSet(options) {
|
|
|
134
134
|
if (!filePath) {
|
|
135
135
|
throw new Error("Usage: lumine thumbnail set <file> (jpg, png, or webp)");
|
|
136
136
|
}
|
|
137
|
+
const { buffer, contentType } = await readThumbnailFile(filePath);
|
|
138
|
+
|
|
139
|
+
const { buildId, auth, build } = await resolveThumbnailContext(options);
|
|
140
|
+
const proceed = await confirmThumbnailOverwrite({ options, build, buildId });
|
|
141
|
+
if (!proceed) return;
|
|
142
|
+
|
|
143
|
+
const { thumbnailUrl } = await commitThumbnailImage({
|
|
144
|
+
options,
|
|
145
|
+
auth,
|
|
146
|
+
buildId,
|
|
147
|
+
buffer,
|
|
148
|
+
contentType,
|
|
149
|
+
});
|
|
150
|
+
console.log(`Thumbnail set for Build #${buildId}:`);
|
|
151
|
+
console.log(` ${thumbnailUrl}`);
|
|
152
|
+
console.log("Tip: thumbnails display ~16:9; the image was uploaded uncropped.");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// A local jpg/png/webp checked against the thumbnail limits, ready for
|
|
156
|
+
// commitThumbnailImage (also used by `lumine admin review propose --thumbnail`).
|
|
157
|
+
export async function readThumbnailFile(filePath) {
|
|
137
158
|
const absolutePath = path.resolve(filePath);
|
|
138
159
|
const extension = path.extname(absolutePath).toLowerCase();
|
|
139
160
|
const contentType = THUMBNAIL_CONTENT_TYPE_BY_EXTENSION[extension];
|
|
@@ -159,21 +180,7 @@ export async function thumbnailSet(options) {
|
|
|
159
180
|
`Thumbnail file is too big (${formatBytes(buffer.length)}; max ${formatBytes(THUMBNAIL_MAX_FILE_SIZE_BYTES)}).`,
|
|
160
181
|
);
|
|
161
182
|
}
|
|
162
|
-
|
|
163
|
-
const { buildId, auth, build } = await resolveThumbnailContext(options);
|
|
164
|
-
const proceed = await confirmThumbnailOverwrite({ options, build, buildId });
|
|
165
|
-
if (!proceed) return;
|
|
166
|
-
|
|
167
|
-
const { thumbnailUrl } = await commitThumbnailImage({
|
|
168
|
-
options,
|
|
169
|
-
auth,
|
|
170
|
-
buildId,
|
|
171
|
-
buffer,
|
|
172
|
-
contentType,
|
|
173
|
-
});
|
|
174
|
-
console.log(`Thumbnail set for Build #${buildId}:`);
|
|
175
|
-
console.log(` ${thumbnailUrl}`);
|
|
176
|
-
console.log("Tip: thumbnails display ~16:9; the image was uploaded uncropped.");
|
|
183
|
+
return { buffer, contentType };
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
function decodeImageDataUrl(dataUrl) {
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Updated: 2026-10-
|
|
5
|
-
Generated: 2026-10-
|
|
3
|
+
Version: 1.65.1
|
|
4
|
+
Updated: 2026-10-13
|
|
5
|
+
Generated: 2026-10-12T17:14:00.482Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -36,7 +36,7 @@ Generated: 2026-10-10T06:47:00.045Z
|
|
|
36
36
|
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (userDailyXP, userDailyCoins, optional userDailyClaims; there is no app-wide daily or lifetime budget, only what one learner can earn per day; an award larger than the learner's remaining room today pays what fits, XP and Coins clamped separately; a capped award still counts as claimed, including a one-time maxLifetimeClaims reward, and its receipt carries cappedFrom) and rules [{ id, title, howTo?, category? (section header, up to 40 characters), series? (key for repeat rules that share category and amounts), xp, coins, verifier: 'numeric-quiz' | 'completion' | 'generated-quiz', maxAttempts?, retry?: { xpPercent, coinsPercent, paidAttempts? }, minSeconds? (completion), progression?: 'dated' | 'until-earned' (quiz), bank? (generated-quiz): { steps: [percent, ...] rising to 100, startStep?, instructions, topics: [{ key, title, description }] } }]. generated-quiz rules need no question sheet. Wire the matching Twinkle.rewards calls with those literal rule ids. Questions and answer keys NEVER go in project files (published source is readable by every player): quiz rules get them from the private question sheet uploaded with `lumine rewards sheet <file.json>` ({ rules: { <ruleId>: { questions?, sets? } } }); `lumine rewards check` validates both together. A review request freezes the code and proposes rewards.json merged with the sheet; the administrator reads the code, checks the amounts and whether the app is exploitable, may change any amount, and approves. Creators are kids and teens: show approval status and one Send for review action; do not ask them to fill in technical forms. Every code update that retains rewards needs a new approval before publishing. Removing the SDK automatically clears its gate. Apps read amounts, tries and sets from getStatus, never from their own file.
|
|
37
37
|
- Verifiers: 'numeric-quiz' pays for server-checked numeric answers (retry share, attempt limits, dated sets or until-earned sets that stay up until somebody earns them, after-answer guides). 'completion' pays when the app reports an activity finished — a cleared stage, a finished round — at least minSeconds after start({ ruleId }); the server checks only the elapsed time, once per learner per site day (UTC midnight), and the budgets. Call start when the activity begins and claim({ challengeId }) with no answers when it ends; keep completion amounts and userDailyXP small enough that a player scripting the calls would not matter, because nothing else is verified. 'generated-quiz' pays from a question bank the server fills itself: for one declared topic at the learner's step of the ladder, the server writes a question with GPT-6.1 Sol (billed to that learner's AI Energy), solves it again independently and keeps it only when both answers agree; every learner who has not seen a question gets it before anything new is written. `xp`/`coins` are the top step's amounts and each step pays its `steps` percent of them (then the usual retry share). Two first-try answers in a row climb a step; two misses in a row step down. Learners may earn many bank questions a day, bounded by userDailyXP/userDailyCoins. The reviewer approves the ladder, amounts, topics and instructions, not each question; learner reports and solve rates hide bad questions. Use the rewards bank methods (getBankStatus, startBankQuestion, generateBankQuestion, claimBankQuestion, reportBankQuestion); start and claim refuse bank rules. 'archive-rematch' is the daily rematch: one retired until-earned puzzle of the app a day, chosen by the server (celebrated, high-rated solves first; never repeated within 60 days) and answered from its frozen approved sheet. Declare at most one, with no questions, sets, progression, series, minSeconds or completionProof, e.g. { "id": "rematch-daily", "title": "Daily rematch", "xp": 20000, "coins": 1000, "verifier": "archive-rematch", "retry": { "xpPercent": 50, "coinsPercent": 0, "paidAttempts": 3 }, "maxAttempts": null }. Only players who never met that puzzle (no official receipt, wrong answer or challenge while it was live, no Vault practice) may answer it; its receipt does not use userDailyClaims but counts toward userDailyXP/userDailyCoins. Use getRematch and claimRematch; start and claim refuse it.
|
|
38
38
|
- Numeric quiz answers are verified on the server; client scores, privateDb state, timers and completion booleans are not verified reward evidence. Limits reset at UTC midnight. Rules are earned once per viewer per UTC day; attempt limits and retry payouts come from the approved rule. Challenges expire at the UTC day boundary. Budgets apply across release changes.
|
|
39
|
-
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release (a receipt capped by the daily budget still counts, so a one-time reward claimed near the cap pays only what fits); completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer) study-record-v1 (a private study record reviewed by JEV, billed to the learner’s AI Energy), and Groove Lab's groove-lab-song-v1 (a song the learner published today, checked on the server for length, notes and originality) and groove-lab-heard-v1 (three established accounts finished the learner's songs today), groove-lab-listen-v1 (the learner listened to most of someone else's published song: start binds the song, the server clock covers three quarters of it up to two minutes, and the listener's own groove-lab-listens row for it was written today; each song pays a listener once a day across listening rules) and groove-lab-own-listen-v1 (the same for the learner's own song; own listens never count toward groove-lab-heard-v1), Ashen Vigil's vigil-guest-coplay-v1 (a new guest spent 10 minutes with the learner in a private world room, measured by the world relay; each guest pays once) and minecraft-first-link-v1 (the learner is the first Twinkle account ever linked to that Minecraft player; each player pays once), and writing-words-v1 with minWords (counted words the learner wrote today as chapters in their own books registered in the app's shared books topic; each chapter is read once by a small model for genuine writing and language, English words count fully and other languages half, and the same text counts once), reading-quiz-v1 (the learner passed a server-graded three-question quiz on a chapter of someone else's registered book after its reading time; each chapter pays a reader once, one attempt per chapter per day) and reading-readers-v1 with minReaders (that many established accounts passed a quiz on the learner's chapters today). Profiles are platform-owned; an app cannot invent a verifier or authorize its own reward.
|
|
39
|
+
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release (a receipt capped by the daily budget still counts, so a one-time reward claimed near the cap pays only what fits); completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer) study-record-v1 (a private study record reviewed by JEV, billed to the learner’s AI Energy), and Groove Lab's groove-lab-song-v1 (a song the learner published today, checked on the server for length, notes and originality) and groove-lab-heard-v1 (three established accounts finished the learner's songs today), groove-lab-listen-v1 (the learner listened to most of someone else's published song: start binds the song, the server clock covers three quarters of it up to two minutes, and the listener's own groove-lab-listens row for it was written today; each song pays a listener once a day across listening rules) and groove-lab-own-listen-v1 (the same for the learner's own song; own listens never count toward groove-lab-heard-v1), Ashen Vigil's vigil-guest-coplay-v1 (a new guest spent 10 minutes with the learner in a private world room, measured by the world relay; each guest pays once) and minecraft-first-link-v1 (the learner is the first Twinkle account ever linked to that Minecraft player; each player pays once), and writing-words-v1 with minWords (counted words the learner wrote today as chapters in their own books registered in the app's shared books topic; each chapter is read once by a small model for genuine writing and language, English words count fully and other languages half, and the same text counts once), reading-quiz-v1 (the learner passed a server-graded three-question quiz on a chapter of someone else's registered book after its reading time; each chapter pays a reader once, one attempt per chapter per day) and reading-readers-v1 with minReaders (that many established accounts passed a quiz on the learner's chapters today). Twinkle.craft apps also have craft-target-v1 (the learner's run of merges reaches today's target in this app's universe; every merge is a known pair and every result is one the learner made through Twinkle.craft.merge), craft-par-v1 (the same run reaches it in at most par merges) and craft-discovery-v1 (the oldest World First of the learner's that no rule has paid yet; each World First pays once ever; optional minWordLevel basic|elementary|intermediate|advanced|epic makes tiers: either every craft-discovery-v1 rule has one or none does; each World First pays under exactly one tier, the highest configured tier at or below its Word Master level, so a series per tier can pay rarer words more and the order of claims never matters; an item whose level is still pending pays under no tier yet). Profiles are platform-owned; an app cannot invent a verifier or authorize its own reward.
|
|
40
40
|
|
|
41
41
|
## AI decision design
|
|
42
42
|
- Twinkle.ai.decide runs JEV, a model for narrow decisions over supplied text or structured state, and it is free for viewers: decisions are covered by Twinkle and never use their AI Energy. Whenever an app makes a judgment that ordinary code can only approximate, consider JEV first. Proven patterns: an opponent or boss coach that reads the player's habits (favourite lane, repeated trick, cheese move) and picks a counter-plan from a fixed list, as in Red Square's boss and Open Football's opponent coach; NPCs that understand what a player types and choose an action; classifying or scoring user submissions; ranking supplied candidates; and picking the next exercise or difficulty from evidence about the learner. Any app with an AI opponent, NPC, or adaptive difficulty should decide explicitly whether JEV improves it. These are examples to reason from, not a keyword checklist or a requirement to add AI to every app.
|
|
@@ -1517,6 +1517,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1517
1517
|
- Completion rules take no answers: claim({ challengeId }) when the activity is finished. build_reward_too_fast (HTTP 409) means fewer than minSeconds passed since start; show nothing and let play continue. In preview mode the receipt carries preview: true and nothing is paid.
|
|
1518
1518
|
- A completionProof rule also requires the signed completionToken from a successful rewards.progress response. The server simulates the registered game physics and must reach the goal. A timer, forged position, client win flag, altered inventory or token from another viewer, challenge or release cannot authorize payment. maxLifetimeClaims is enforced from receipts in the same award transaction; every receipt counts, including one capped by the daily budget (cappedFrom). build_reward_lifetime_claims_reached means all rewards for this rule have been collected; do not retry it.
|
|
1519
1519
|
- For study-record-v1, the server verifies the settled private review row and its viewer, app, exact release, challenge and day binding. It never trusts the client’s decision, requested award amount or AI answer. Each successful daily study record is claimable once; display only canonical receipt/balances. For groove-lab-song-v1, pass completionToken as the published song's sharedDb entry id; for groove-lab-heard-v1, pass none; for vigil-guest-coplay-v1, pass the guest's world guestId (from world events) or the co-play record id; for minecraft-first-link-v1, pass nothing or the Minecraft UUID to use; for reading-quiz-v1, pass completionToken as the chapter commentId and answers as the three chosen option indexes (all three must be right; a wrong attempt cannot be retried that day); for groove-lab-listen-v1 and groove-lab-own-listen-v1, pass the same song entry id after listenSeconds of foreground playback; for reading-readers-v1, pass nothing; for writing-words-v1, pass nothing (the server counts today's chapters itself and refuses with a plain reason, e.g. "320 of 500 counted words today" or "still being checked"). The server reads the app's own groove-lab-songs, groove-lab-song-parts and groove-lab-listens rows and refuses with build_reward_completion_proof_required and a plain reason when the song or listeners do not qualify.
|
|
1520
|
+
- For craft-target-v1 and craft-par-v1, pass completionToken as JSON.stringify({ v: 1, merges: [[a, b], ...] }): the item ids of the run's successful merges in order, starting from the starters (at most 300 merges, 32 KB). The server replays it against this app's known pairs, needs today's target (Twinkle.craft.getState().today) to appear, every result to be in the learner's collection, and at least one second per merge since start; it refuses an unknown pair, an item the run had not made yet, an over-par run (par rule) or a run on another day. For craft-discovery-v1, pass nothing: the server pays the oldest unpaid World First (receipt slot craft:<itemId>) and puts its name on the bounty story ("discovered <item>"). With tiers, a rule pays only World Firsts of its own tier (refused with a plain reason when the learner has none).
|
|
1520
1521
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1521
1522
|
- Returns: { mode: "live", metric, period, limit, dayKey, from, available: { xp, coins }, entries: [{ rank, userId, username, profilePicUrl, xp, coins, claims, lastAt }], me: { rank, xp, coins, claims } | null } | { mode: "preview", metric, period, available, entries: [], me: null, message }
|
|
1522
1523
|
- Standings of who earned the most XP or Coins in THIS app, computed by Twinkle from its own receipts (never from anything the app submits). period 'day' is today (site day, UTC), 'week' the last 7 site days, 'all' (default) every day since approval. limit defaults to 20, max 100.
|
|
@@ -1652,6 +1653,36 @@ spawnPet(asset.kind, asset.params, asset.palette, asset.tier.effects);
|
|
|
1652
1653
|
- The asset's name is fixed: it is shown on the card page and cannot be renamed. To let owners name their pet, keep an in-game nickname in state (for example state.nickname, trimmed and length-limited by your app) and show it in the game. After a sale the new owner can set their own nickname the same way.
|
|
1653
1654
|
- expectedRevision is the stateRevision you last read; a mismatch fails with code card_craft_state_conflict (details.stateRevision), and a card the viewer no longer owns fails with card_craft_not_owner.
|
|
1654
1655
|
|
|
1656
|
+
### Twinkle.craft
|
|
1657
|
+
- await Twinkle.craft.getState() | scopes: craft:read
|
|
1658
|
+
- Returns: { starters: [item], counts: { mine, world }, today: { day, target: item | null, par: number | null, done: boolean }, reason?: "sign_in_required" }
|
|
1659
|
+
- item is { id, name, emoji, category, wordLevel: "basic" | "elementary" | "intermediate" | "advanced" | "epic" | null, isStarter }. wordLevel is Word Master's level of the name's rarest word (null while it is being looked up); show it by name, never as a number.
|
|
1660
|
+
- Starters are always in the player's hands and are not in list(). counts.mine is the player's collection without starters; counts.world is every item in this universe.
|
|
1661
|
+
- today is Today's Star: the same target for everyone in this app on this site day (UTC), reachable from the starters in par merges or fewer (par is the server's best plan, an upper bound). target is null while the universe is too young. done is true once the player has the target.
|
|
1662
|
+
- Signed-out viewers get the same shape, empty: starters [], counts 0, today with target null, plus reason: "sign_in_required". counts.world may be up to a minute old.
|
|
1663
|
+
- await Twinkle.craft.merge({ a, b, requestId? }) | scopes: craft:write
|
|
1664
|
+
- Returns: { status: "ok" | "fizzle", item: item | null, why: string | null, newForMe: boolean, worldFirst: boolean, newPair: boolean, aiUsagePolicy? }
|
|
1665
|
+
- a and b are item ids the player has (starters or their collection); the order does not matter and an item can merge with itself. A known pair answers at once and costs nothing. A new pair asks the AI (usually a few seconds; the SDK waits up to 120 s), costs the player a little AI Energy (newPair: true) and is then known for everyone. fizzle means the pair makes nothing; it is stored too, so it is also free afterwards.
|
|
1666
|
+
- why is one true sentence explaining the result ("Lava cools in water and hardens into rock."): show it, it is the learning part. worldFirst: true means this player is the first in this universe to make the item; their name is on it for good.
|
|
1667
|
+
- Limits: 300 new pairs per player per day (known pairs are unlimited), 20,000 items per collection, and about 1 merge a second. Errors carry code: craft_item_not_owned, craft_item_not_found, craft_in_progress (someone else is discovering the same pair; retry shortly), craft_daily_limit, craft_collection_full, craft_universe_full, craft_generation_failed / craft_generation_timeout (the AI was unavailable or too slow and the pair stays new; try again), craft_check_unavailable (HTTP 503: Twinkle's safety check is briefly down; nothing was stored and the pair stays new, so try again in a minute), craft_app_not_found / craft_forbidden (the app was deleted or the viewer can no longer open it), craft_throttled, and the AI Energy codes of Twinkle.ai.
|
|
1668
|
+
- Pass the same requestId when retrying after a lost reply. Keep a run's successful merges as [a, b] ids for Twinkle.rewards.claim (craft-target-v1 / craft-par-v1).
|
|
1669
|
+
- A result that is a starter (Water + Water = Water) is not added to the collection: newForMe is false. An error can carry aiUsagePolicy when an earlier AI attempt already used Energy.
|
|
1670
|
+
- Items can be taken down by Twinkle moderators: a retired item leaves the player's collection (list, item, getState counts) and its pairs answer fizzle. Treat ids you cached as possibly gone: a merge with one returns craft_item_not_found.
|
|
1671
|
+
- Example: const result = await Twinkle.craft.merge({ a: water.id, b: fire.id });
|
|
1672
|
+
if (result.status === 'ok') showCard(result.item, result.why, result.worldFirst);
|
|
1673
|
+
- await Twinkle.craft.list({ cursor?, limit? }) | scopes: craft:read
|
|
1674
|
+
- Returns: { items: [{ id, name, emoji, category, wordLevel, firstAt, isWorldFirst }], cursor: number | null }
|
|
1675
|
+
- The player's collection in this app, oldest item id first; limit 1-200 (default 100). firstAt is when this player got the item; isWorldFirst is true for items they discovered first in the universe.
|
|
1676
|
+
- await Twinkle.craft.item({ id }) | scopes: craft:read
|
|
1677
|
+
- Returns: { item: { id, name, emoji, category, wordLevel, isStarter, why, firstDiscoverer: { id, username } | null, firstAt, holders, recipes: [[{ id, name, emoji }, { id, name, emoji }]] } | null }
|
|
1678
|
+
- The discovery card of an item the player has (null otherwise, so undiscovered items never leak). why is the explanation from the merge that first made it, holders how many players have it, recipes up to five known pairs that make it whose inputs the player has.
|
|
1679
|
+
- await Twinkle.craft.journey({ day? }) | scopes: craft:read
|
|
1680
|
+
- Returns: { day, journey: { target: item, par, steps: [{ a, b, result }] } | null, reason?: "no_target" | "reach_target_first" | "sign_in_required" }
|
|
1681
|
+
- The server's par plan for a day (YYYY-MM-DD, default today), each step { id, name, emoji } items. For today it opens only after the player made the target today (reason reach_target_first before that); past days are open too, except a past day whose target is today's target (same rule as today).
|
|
1682
|
+
- await Twinkle.craft.rankings() | scopes: craft:read
|
|
1683
|
+
- Returns: { allTime: { top: [{ rank, userId, username, profilePicUrl, worldFirsts }], me: { rank: number | null, worldFirsts } }, month: { month, top, me: { rank: number | null, worldFirsts } } }
|
|
1684
|
+
- World First rankings in this app: the top 30 discoverers all time and this month (UTC), and the viewer's own rank (null with no World Firsts). The top lists may be up to a minute old. Signed-out viewers get empty top lists and me: { rank: null, worldFirsts: 0 }.
|
|
1685
|
+
|
|
1655
1686
|
### Twinkle.network
|
|
1656
1687
|
- async getHome({}) | scopes: sharedDb:read
|
|
1657
1688
|
- Returns: { name, buildId, url, communities, counts }
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -131,7 +131,8 @@ canonical bucket. Use it to distinguish quota bookkeeping from moderation;
|
|
|
131
131
|
the note itself changes no access, ban, or identity rules.
|
|
132
132
|
|
|
133
133
|
The commands above are quota bookkeeping only. They cannot ban accounts, block signup,
|
|
134
|
-
add IP/device/risk-key rules, or infer an account family
|
|
134
|
+
add IP/device/risk-key rules, or infer an account family (the explicit
|
|
135
|
+
restricted-bucket commands below can, on Mikey's instruction). Identification
|
|
135
136
|
remains a human/LLM evidence judgment and must be explicitly requested by
|
|
136
137
|
Mikey; routine administrator runs still escalate suspected alternate accounts
|
|
137
138
|
and never auto-enforce. `accounts add` and `note set` still accept only an
|
|
@@ -159,6 +160,85 @@ raw device ID. The private audit records the request before evidence is read
|
|
|
159
160
|
and records successful completion separately. Routine reviews still never
|
|
160
161
|
auto-enforce; this action requires Mikey's explicit instruction for the device.
|
|
161
162
|
|
|
163
|
+
### Restricted buckets: inspect, rules and restrictions
|
|
164
|
+
|
|
165
|
+
These are the operator path for buckets with a restriction (and for adding
|
|
166
|
+
email, IP and device rules to any bucket). They use the same shared operations
|
|
167
|
+
as the Management page, so each change has the same side effects there
|
|
168
|
+
(current-day usage re-attribution, ban cache, member notification and kicks,
|
|
169
|
+
live quota updates). Use them only on Mikey's explicit instruction for that
|
|
170
|
+
bucket, rule or restriction.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
lumine admin ai-bucket inspect --bucket-id 4 --reason "todo 79 review" --json
|
|
174
|
+
lumine admin ai-bucket restrict --bucket-id 4 --mode signup --dry-run
|
|
175
|
+
lumine admin ai-bucket restrict --bucket-id 4 --mode signup --confirm
|
|
176
|
+
lumine admin ai-bucket rule add --bucket-id 4 --type user --user 18559 --dry-run
|
|
177
|
+
lumine admin ai-bucket rule add --bucket-id 4 --type user --user 18559 --confirm
|
|
178
|
+
lumine admin ai-bucket rule add --bucket-id 4 --type email \
|
|
179
|
+
--email family@example.com --confirm-email-rule --confirm
|
|
180
|
+
lumine admin ai-bucket rule add --bucket-id 4 --type ip --ip 58.151.12.59 \
|
|
181
|
+
--scope signup [--include-prefix] --confirm
|
|
182
|
+
lumine admin ai-bucket rule add --bucket-id 4 --type device --user 18559 \
|
|
183
|
+
--device-key 0123456789abcdef --scope signup --user-ids 18559,20001 \
|
|
184
|
+
--note "Mikey approved this reviewed device" --confirm
|
|
185
|
+
lumine admin ai-bucket rule disable --bucket-id 4 --rule-id 77 --dry-run
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`inspect` works for unrestricted and restricted buckets. It returns the
|
|
189
|
+
canonical bucket (label, note, restriction `none|signup|full`, message), every
|
|
190
|
+
active rule (id, kind, scope, effective restriction, match), the canonical
|
|
191
|
+
members, every account a full restriction would reach, and for each member
|
|
192
|
+
(first 25) their devices (inspection keys, never raw IDs) and network
|
|
193
|
+
addresses with how many accounts share each. A device or address used by many
|
|
194
|
+
accounts (an academy, a school) is counted, marked shared and never listed or
|
|
195
|
+
treated as family evidence. Addresses and email-rule addresses (in evidence, rules and rule notes)
|
|
196
|
+
are masked unless `--include-private-evidence`; every other output, and the
|
|
197
|
+
audit, keeps them masked (with a keyed hash in the audit). `--reason` is required; the API commits an
|
|
198
|
+
access receipt before reading evidence.
|
|
199
|
+
|
|
200
|
+
`--dry-run` on `restrict`, `rule add` and `rule disable` is a read: it returns
|
|
201
|
+
the current and resulting restriction, the accounts newly affected or released,
|
|
202
|
+
the accounts whose sessions a full restriction drops, admin conflicts and any
|
|
203
|
+
blocker, and changes nothing. Device, IP and email previews read private
|
|
204
|
+
evidence, so they record an access receipt; the others record nothing.
|
|
205
|
+
|
|
206
|
+
A real run is audited (request receipt with the idempotency key and payload
|
|
207
|
+
fingerprint, then the completed result) and returns the bucket read back from
|
|
208
|
+
the database and checked (`verified: true`); the CLI refuses a result that
|
|
209
|
+
does not confirm the requested state. A failure after the change started is
|
|
210
|
+
recorded as failed; a replay with the same idempotency key returns the stored
|
|
211
|
+
result instead of running again.
|
|
212
|
+
|
|
213
|
+
- `restrict` always needs `--confirm`. `signup` blocks only new accounts:
|
|
214
|
+
existing logins and existing bans (`users.banned`) stay as they are. `full`
|
|
215
|
+
drops members' sessions and is refused while any member is an admin. `none`
|
|
216
|
+
lifts the restriction; each member gets its effective ban state pushed again.
|
|
217
|
+
Without `--message` the current message is kept. The preview and the
|
|
218
|
+
result list every IP, prefix and device rule whose effective restriction
|
|
219
|
+
changes, with how many accounts it reaches (exact IPs from site activity;
|
|
220
|
+
prefixes and devices from a year of AI usage, a lower bound). A rule that
|
|
221
|
+
gets stronger on a shared network (more than 8 accounts) or whose reach
|
|
222
|
+
can't be measured refuses the change unless `--allow-shared-network` is
|
|
223
|
+
passed; use it only on Mikey's explicit word. The CLI prints the override.
|
|
224
|
+
Saving a full bucket again (even only a new message) re-kicks its members,
|
|
225
|
+
as on the Management page; the preview lists them.
|
|
226
|
+
- `rule add` / `rule disable` always need `--confirm` too: on an unrestricted
|
|
227
|
+
bucket a rule still pools AI Energy, on a restricted one it changes who is
|
|
228
|
+
blocked. On a fully restricted bucket an account or email rule re-kicks every
|
|
229
|
+
account the bucket reaches (as on the Management page); the preview lists them.
|
|
230
|
+
Email rules cover every account with that verified address and also need
|
|
231
|
+
`--confirm-email-rule`. IP and device rules need `--scope`; IP prefix rules
|
|
232
|
+
(`--include-prefix`, the whole /24 or /64) are opt-in, their reach measured
|
|
233
|
+
as above. A shared exact address or prefix, or an unmeasured prefix under a
|
|
234
|
+
full restriction, is refused unless `--allow-shared-network`. An address or
|
|
235
|
+
prefix rule held by another bucket is refused, never moved. Device rules
|
|
236
|
+
keep the `device block-signup` guards: the reviewed `--user-ids` must equal
|
|
237
|
+
the accounts seen on the device, all inside the bucket; a device more than
|
|
238
|
+
9 accounts used is shared (counted, not listed, refused). Disabling an exact
|
|
239
|
+
IP rule leaves its prefix rules active (the preview names them).
|
|
240
|
+
`rule disable` keeps `--note` in the audit receipt only.
|
|
241
|
+
|
|
162
242
|
### Shared verified-email policies
|
|
163
243
|
|
|
164
244
|
A verified address proves that an account can use that mailbox; it does not
|
|
@@ -1320,10 +1400,19 @@ lumine admin reward-review approve 2 --config rules.json \
|
|
|
1320
1400
|
lumine admin reward-review show 2 --dir /private/tmp/reward-review-2 --json # then edit that directory…
|
|
1321
1401
|
lumine admin reward-review propose 2 --dir /private/tmp/reward-review-2 \
|
|
1322
1402
|
--config rules.json --reason "Moved the claim after the stage clears" --json # …and offer it
|
|
1403
|
+
lumine admin reward-review propose 2 --dir /private/tmp/reward-review-2 --config rules.json \
|
|
1404
|
+
--title "Word Forge" --thumbnail cover.png --json # …also offering a new name and thumbnail
|
|
1323
1405
|
lumine admin reward-review reject 2 --reason "Rewards fire on game over; nothing is earned" --json
|
|
1324
1406
|
lumine admin reward-review revoke 2 --reason "Farmable; pausing until redesigned" --json
|
|
1325
1407
|
```
|
|
1326
1408
|
|
|
1409
|
+
A proposal can also offer the app a new name (`--title`, at most 100
|
|
1410
|
+
characters) and a new thumbnail (`--thumbnail <jpg|png|webp>`, uploaded to your
|
|
1411
|
+
proposal workspace copy first; `--thumbnail workspace` offers the one already
|
|
1412
|
+
set on that copy, e.g. from the website). Both are frozen with the offer and
|
|
1413
|
+
applied together with the files and rules when the creator accepts. An offer
|
|
1414
|
+
may change only the name or thumbnail; the files can then stay as submitted.
|
|
1415
|
+
|
|
1327
1416
|
`show --dir` writes the exact reviewed snapshot (private files, including the
|
|
1328
1417
|
app's own `rewards.json`) so the agent can read it like a pulled workspace. The
|
|
1329
1418
|
result's `config` is the proposal: the declared economy with the sheet's
|
|
@@ -1503,6 +1592,99 @@ lumine admin roles --min-level 3 # only the admin types' holders
|
|
|
1503
1592
|
lumine admin roles --min-level 0 --limit 200 --json
|
|
1504
1593
|
```
|
|
1505
1594
|
|
|
1595
|
+
### Twinkle.craft universes: seed, audit, revise (any time; added 2026-10-12)
|
|
1596
|
+
|
|
1597
|
+
An app using `Twinkle.craft` (Infinity Craft, build 1499) has its own
|
|
1598
|
+
universe of items and pairs. Players' new pairs are answered by the AI on
|
|
1599
|
+
their own Energy; we seed and curate the rest with Claude Code agents, not
|
|
1600
|
+
API calls (Mikey 10-12): an initial seed, then expansion and a daily audit,
|
|
1601
|
+
Math Lab style. No daily run is needed. `seed` and `revise` are dry runs
|
|
1602
|
+
unless `--confirm` is given.
|
|
1603
|
+
|
|
1604
|
+
```bash
|
|
1605
|
+
lumine admin craft stats --build 1499 [--limit 50] # sizes, frontier, today's star, newest AI answers, kept flagged names
|
|
1606
|
+
lumine admin craft seed --build 1499 --file pairs.json # ALWAYS first: what would be added, skipped, rejected, warned
|
|
1607
|
+
lumine admin craft seed --build 1499 --file pairs.json --confirm # only after reading every warning
|
|
1608
|
+
lumine admin craft revise --build 1499 --item Obsidian --why "Lava that cools very fast turns into glassy rock." --confirm
|
|
1609
|
+
```
|
|
1610
|
+
|
|
1611
|
+
- `pairs.json` is up to 500 pairs, in order: `[{ "a": "Water", "b": "Fire",
|
|
1612
|
+
"result": { "name": "Steam", "emoji": "♨️", "category": "science", "why":
|
|
1613
|
+
"Fire heats water until it turns into vapour." } }]` (or `{ "pairs": [...] }`).
|
|
1614
|
+
`a` and `b` are item names: starters (Water, Fire, Earth, Wind, Tree,
|
|
1615
|
+
Nature), items already in the universe, or results earlier in the same file.
|
|
1616
|
+
- Every result passes the same checks as AI answers: 1-3 English words of
|
|
1617
|
+
letters (32 characters), exactly one emoji, a category from nature, weather,
|
|
1618
|
+
earth, space, life, body, science, energy, materials, food, tools, places,
|
|
1619
|
+
ideas, and one plain-text why of at most 140 characters (no links or
|
|
1620
|
+
markup). No AI or JEV call runs, so the content is ours to get right: real,
|
|
1621
|
+
common English nouns or ideas, no people, characters or brands, child-safe,
|
|
1622
|
+
and a true why that teaches the link.
|
|
1623
|
+
- **Always dry-run a seed first and read every warning before `--confirm`.**
|
|
1624
|
+
A warning means a word of the name is on Word Master's censored list; the
|
|
1625
|
+
pair is still written on `--confirm`, so drop or rename any pair whose name
|
|
1626
|
+
could read rudely before confirming.
|
|
1627
|
+
- A pair that already has an answer is skipped, never overwritten. Each
|
|
1628
|
+
chunk of 100 is one audited transaction (`craft.seed`); a re-run is safe.
|
|
1629
|
+
Word levels (Word Master's basic..epic) are looked up as for player items.
|
|
1630
|
+
- Seeded items have no World First: the first player to make one claims it
|
|
1631
|
+
(name on the item, rankings, the discovery reward). Seeded pairs count for
|
|
1632
|
+
Today's Star like any answered pair and never count toward the admin's
|
|
1633
|
+
daily new-pair limit.
|
|
1634
|
+
- `stats` lists the newest AI-made pairs (`source: ai`, newest first, `--limit`
|
|
1635
|
+
1-100, default 20) with each result's name, emoji, category, word level and
|
|
1636
|
+
why. Audit them daily; fix a wrong or weak explanation with `revise`.
|
|
1637
|
+
- `stats` also lists **kept flagged names**: AI items JEV kept although a word
|
|
1638
|
+
matched the censored list (name, matched word, JEV's score, when). Read
|
|
1639
|
+
each one as a child would; `retire` any that reads rudely.
|
|
1640
|
+
- `revise` changes an item's `--why` (the explanation on its card: the pair
|
|
1641
|
+
that first made it), `--emoji` or `--category`, through the same checks. The
|
|
1642
|
+
name never changes: players hold it. Audited as `craft.revise`.
|
|
1643
|
+
- Expansion: `stats` shows the frontier (items no pair uses yet, and untried
|
|
1644
|
+
pairs); seed pairs that use frontier items so Today's Star has fresh targets.
|
|
1645
|
+
|
|
1646
|
+
Take-down and repair (added 2026-10-13):
|
|
1647
|
+
|
|
1648
|
+
```bash
|
|
1649
|
+
lumine admin craft retire --build 1499 --item "<name or id>" --reason "Not suitable for children" # dry run
|
|
1650
|
+
lumine admin craft retire --build 1499 --item "<name or id>" --reason "Not suitable for children" --confirm
|
|
1651
|
+
lumine admin craft unfizzle --build 1499 [--since 2026-10-13T00:00:00Z] [--pair Water+Fire] # dry run
|
|
1652
|
+
lumine admin craft unfizzle --build 1499 --since 2026-10-13T00:00:00Z --confirm
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1655
|
+
- `retire` takes a bad item down everywhere: it leaves collections, item
|
|
1656
|
+
cards, rankings, journeys and the daily target (today's star is re-picked);
|
|
1657
|
+
the pairs that make it fizzle and no AI answer or seed can make it again;
|
|
1658
|
+
its unpaid World First no longer pays (a paid one stays paid); its name
|
|
1659
|
+
leaves bounty story lines. Who, when and why are stored on the item and in
|
|
1660
|
+
the admin audit (`craft.retire`). Starters cannot be retired. There is no
|
|
1661
|
+
un-retire.
|
|
1662
|
+
- Word Master's censored list (`content_words_censored`) is a Wordle filter,
|
|
1663
|
+
not a moderation list, so a hit never decides anything on its own. A name
|
|
1664
|
+
with a listed word (whole words only, simple inflections both ways: -s,
|
|
1665
|
+
-es, -ed, -ing; hyphens count as spaces) is always judged by JEV as a
|
|
1666
|
+
whole, told which word to look at, and must clear a stricter bar. Crystal
|
|
1667
|
+
Ball, Sperm Whale, Pork Loin or Blue-Footed Booby pass when JEV finds them
|
|
1668
|
+
innocent; a rude reading is regenerated once, then the pair fizzles.
|
|
1669
|
+
Only JEV itself judges such a name: if JEV is down and its fallback model
|
|
1670
|
+
answers, the merge takes the outage path below (the answer is kept and
|
|
1671
|
+
re-checked once JEV is back). There is no allowlist. For seeds a hit is a
|
|
1672
|
+
**warning** in the report (printed as `warning …` lines, and in `warnings`
|
|
1673
|
+
with `--json`), never a rejection.
|
|
1674
|
+
- During a safety-check outage players get `craft_check_unavailable`. A
|
|
1675
|
+
pair's answer is paid for once and re-checked free for 24 h from when the
|
|
1676
|
+
model wrote it; after that a new try generates fresh. (If JEV later judges
|
|
1677
|
+
the kept answer low, the one normal regeneration is billed as usual.) Each
|
|
1678
|
+
server pauses new runs for a minute after an outage (per pair, or
|
|
1679
|
+
everywhere after three); the pause never stops the free re-check of a kept
|
|
1680
|
+
answer or a run that has already paid.
|
|
1681
|
+
- `unfizzle` repairs pairs that fizzled because the safety check itself
|
|
1682
|
+
failed (an outage before 2026-10-13; a check outage now fails the merge
|
|
1683
|
+
without storing anything). It resets only fizzles whose stored checks all
|
|
1684
|
+
errored, never a real low verdict or a censored word; the next player to
|
|
1685
|
+
try the pair asks again. `--since` and `--pair` narrow it; up to 5,000
|
|
1686
|
+
fizzles are scanned per run.
|
|
1687
|
+
|
|
1506
1688
|
### Reward question-bank seeding (every full daily review; added 2026-09-28)
|
|
1507
1689
|
|
|
1508
1690
|
`generated-quiz` rules fill a question bank on the server: a question is
|