import type { Balance, Entitlements, EquippedEmote, InventoryItem, LeaderboardEntry, LeaderboardOrder, MarketplaceListing, ProductGrant, PurchaseStatus } from '../protocol.js'; import type { DevShellAchievementCriteria } from './criteria.js'; export declare const DEV_SHELL_STATE_VERSION = 1; /** The simulated player. `helix dev --fresh` mints a new id so `player:{userId}` keys behave. */ export type DevShellIdentity = { id: string; username: string; displayName: string | null; }; export declare const DEV_SHELL_DEFAULT_IDENTITY: DevShellIdentity; export declare const DEV_SHELL_DEFAULT_WALLET: Balance; /** One world product, shaped like the future `helix.registry.json` row the CLI seeds from. */ export type DevShellProduct = { key: string; title: string; priceLix: number; description?: string | null; thumbnailUrl?: string | null; maxPerUser?: number | null; grants?: ProductGrant[]; }; /** Seed for the simulated catalog. `listings` backs marketplace.getListings + exact `listing:` refs; a world that * only sells its own products can leave it out. */ export type DevShellProductsSeed = { products: DevShellProduct[]; listings?: MarketplaceListing[]; }; /** A registered achievement, shaped like the registration row. Rows with a `criteria` unlock through the * simulator's evaluator; rows without one only unlock via the debug menu (the room/platform lanes). */ export type DevShellAchievement = { id?: string; key: string; name: string; description?: string | null; points?: number; hidden?: boolean; iconUrl?: string | null; unlockMode?: string; criteria?: DevShellAchievementCriteria | null; }; export type DevShellDocument = { value: unknown; version: number; updatedAt: string; }; export type DevShellBoard = { order: LeaderboardOrder; size: number; entries: LeaderboardEntry[]; }; export type DevShellPurchaseRecord = { idempotencyKey: string; ref: string; status: PurchaseStatus; priceLix: number; itemId?: string | null; balanceAfterLix?: number; balanceLix?: number; shortfallLix?: number; ownedCount?: number; maxPerUser?: number | null; reason?: string; at: string; }; export type DevShellState = { v: typeof DEV_SHELL_STATE_VERSION; userId: string; username: string; displayName: string | null; wallet: Balance; entitlements: Entitlements; inventory: InventoryItem[]; /** The world's data store, keyed exactly as the backend keys it (`player:{userId}`, …). */ documents: Record; achievements: { registry: DevShellAchievement[]; earned: string[]; }; leaderboards: Record; purchases: DevShellPurchaseRecord[]; /** Criteria evidence with no simulated writer of its own: the shell page ticks playtime, the debug * menu bumps analytics (analytics.track is a direct-fetch surface that never reaches a shell). */ playtimeSeconds: number; analyticsCounts: Record; /** * The simulated equipped emote wheel. OPTIONAL so a state file written before this field existed * still loads (the reader falls back to DEV_SHELL_SIMULATED_EMOTES) — a dev state blob is a * developer's saved session, not something worth invalidating over a field with a safe default. * Hand-editable in .helix/dev-state.json, which is the point: it is how a creator points the wheel * at their own clips without a backend. */ emotes?: EquippedEmote[]; }; export declare function createDevShellState(seed?: { identity?: Partial; wallet?: Partial; achievements?: DevShellAchievement[]; }): DevShellState; /** * The loadout `helix dev` answers `emotes.getEquipped` with — DELIBERATELY NOT REAL, and shaped so * nobody can mistake it for their own account's wheel. * * A creator running `helix dev` is signed into nothing; there is no account to read a wheel from. The * honest simulation is therefore a stub that is obviously a stub: every `itemId` is a `dev-shell-…` * string where the platform uses a uuid, and every label is prefixed `SIM ·`, so the wording on the * petal itself says what it is. Three entries, not eight, for the same reason — a full wheel would * look like data. * * KEEP THE MARKER AND THE NAMES SHORT, and keep them WORD-SEPARATED. A petal is a small disc that * clamps to two lines, so a single token wider than the disc is split mid-word by the browser no * matter what the wrap policy is. The prefix was `SIMULATED ·` and rendered as `SIMULATE` / `D · Idle` * — the marker meant to say "this is not real" instead read as a rendering bug. `SIM ·` fits on one * line, so the label wraps at the space and stays legible. * * The CLIPS are the humanoid-character system's own bundled locomotion animations, which reinforces * the point: they are not emotes at all, they are the runtime's own clips standing in for emotes, and * they look like it when they play. They come off the same public package CDN the world's runtime * already streams every one of its clips from during `helix dev`, so this adds no dependency the * session did not already have. If that URL ever 404s the wheel still OPENS and still shows the * petals — only playback is lost — because the runtime treats a clip load failure as a clip problem, * not as a failure to read the loadout. */ export declare const DEV_SHELL_SIMULATED_EMOTES: readonly EquippedEmote[]; /** Deep copy through JSON — the same trip the state makes to disk, so a non-serializable value fails here. */ export declare function cloneDevShellState(state: DevShellState): DevShellState; /** Accept a state loaded from disk (possibly older or hand-edited) without crashing the shell. */ export declare function normalizeDevShellState(input: unknown, identity?: DevShellIdentity): DevShellState; /** SDK-identical ref spelling: bare authored keys become pkey:, and every other ref is narrow. */ export declare function devShellProductRef(keyOrRef: string): string; export declare function findDevShellProduct(products: DevShellProduct[], ref: string): DevShellProduct | null; export declare function findDevShellListing(listings: MarketplaceListing[], ref: string): MarketplaceListing | null; /** Add (or top up) an inventory row. Quantities accumulate, exactly as a repeated grant does. */ export declare function addInventoryItem(state: DevShellState, item: { itemId: string; title?: string; quantity?: number; kind?: string; category?: string; }): void; /** What applying a grant list touched, so the caller pushes only the events production would push. */ export type DevShellGrantEffects = { inventory: boolean; entitlements: boolean; }; export declare function applyDevShellGrants(state: DevShellState, grants: ProductGrant[] | undefined, productKey: string | null, titleOf: (itemId: string) => string | undefined): DevShellGrantEffects; /** Expire passes whose window has closed, so a read never reports an active pass the backend would not. */ export declare function refreshDevShellPasses(state: DevShellState): void; export declare function devShellEntitlementsSnapshot(state: DevShellState): Entitlements;