Houseki宝石

@johnmorrisdotca/houseki 0.1.1 · 7 entry points · 301 exports

@johnmorrisdotca/houseki

colourChains fallingTriplets gemSwap magneticBlocks nature stoneCollapse

namespace colourChains

import { colourChains } from "@johnmorrisdotca/houseki"; // or everything in it from "@johnmorrisdotca/houseki/colourChains"

Everything the @johnmorrisdotca/houseki/colourChains entry point exports, as one namespace.

namespace fallingTriplets

import { fallingTriplets } from "@johnmorrisdotca/houseki"; // or everything in it from "@johnmorrisdotca/houseki/fallingTriplets"

Everything the @johnmorrisdotca/houseki/fallingTriplets entry point exports, as one namespace.

namespace gemSwap

import { gemSwap } from "@johnmorrisdotca/houseki"; // or everything in it from "@johnmorrisdotca/houseki/gemSwap"

Everything the @johnmorrisdotca/houseki/gemSwap entry point exports, as one namespace.

namespace magneticBlocks

import { magneticBlocks } from "@johnmorrisdotca/houseki"; // or everything in it from "@johnmorrisdotca/houseki/magneticBlocks"

Everything the @johnmorrisdotca/houseki/magneticBlocks entry point exports, as one namespace.

namespace nature

import { nature } from "@johnmorrisdotca/houseki"; // or everything in it from "@johnmorrisdotca/houseki/nature"

Everything the @johnmorrisdotca/houseki/nature entry point exports, as one namespace.

namespace stoneCollapse

import { stoneCollapse } from "@johnmorrisdotca/houseki"; // or everything in it from "@johnmorrisdotca/houseki/stoneCollapse"

Everything the @johnmorrisdotca/houseki/stoneCollapse entry point exports, as one namespace.

@johnmorrisdotca/houseki/falling-triplets

Action actionsForTick advanceTicks applyAction CampaignLevel campaignManifest CampaignManifest ChallengeGoal ChallengeGoalChain ChallengeGoalEmpty ChallengeGoalTargets ChallengeHint ChallengeOptions ChallengeSettings Colour compactBoard createChallenge createGame createInputScheduler createLevel CreateOptions decodeGame DifficultyMetrics encodeGame findMatches GameEvent GameState Gem getLevel getTutorial landingY legalActions levelManifest LocalizedText Mode nextInt nextUint32 Phase Piece queueInputEdge RecordedAction releaseAllActions restartGame seedState setHeldAction Settings statusOf Transition Triplet TutorialDefinition tutorialManifest TutorialStep Wave

type Action

type Action = { readonly kind: 'left' | 'right' | 'soft-drop' | 'hard-drop' | 'place' | 'cycle-forward' | 'cycle-backward' | 'pause' | 'resume' | 'hint' | 'tick' };

function actionsForTick

actionsForTick(state: InputSchedulerState): readonly [readonly Action[], InputSchedulerState]

Resolves canonical actions in the design's per-tick order.

function advanceTicks

advanceTicks(state: GameState, ticks: number): Transition

Advances fixed 60 Hz game ticks. Arcade timing is applied to the active piece.

function applyAction

applyAction(state: GameState, action: Action): Transition

Applies and records one immutable action; rejected actions preserve state identity.

type CampaignLevel

interface CampaignLevel { readonly id: string; readonly seed: string; readonly number: number; readonly title: LocalizedText; /** SHA-256 fingerprint of the colour- and mirror-canonical duplicate key. */ readonly canonicalKeyHash: string; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly goal: ChallengeGoal; readonly board: readonly (Gem | null)[]; readonly queue: readonly Tr…

const campaignManifest

campaignManifest: CampaignManifest

type CampaignManifest

interface CampaignManifest { readonly count: number; readonly candidatePoolCount: number; readonly generationRevision: string; readonly gradingVersion: string; readonly category: string; readonly curationPolicy: string; readonly orderingPolicy: string; readonly grading: Readonly<Record<string, unknown>>; readonly sampleBudget: number; readonly checksum: string; readonly levels: readonly CampaignLevel[]; }

type ChallengeGoal

type ChallengeGoal = ChallengeGoalTargets | ChallengeGoalChain | ChallengeGoalEmpty;

type ChallengeGoalChain

interface ChallengeGoalChain { readonly type: 'chain'; readonly minimumChain: number }

type ChallengeGoalEmpty

interface ChallengeGoalEmpty { readonly type: 'empty' }

type ChallengeGoalTargets

interface ChallengeGoalTargets { readonly type: 'targets'; readonly targetIds: readonly number[] }

type ChallengeHint

interface ChallengeHint { readonly x: number; readonly orientation: 0 | 1 | 2 }

type ChallengeOptions

interface ChallengeOptions { readonly seed?: string; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly board: readonly (Gem | null)[]; readonly queue: readonly Triplet[]; readonly goal: ChallengeGoal; readonly witness?: readonly ChallengeHint[] }

type ChallengeSettings

interface ChallengeSettings extends Settings { readonly mode: 'challenge'; readonly goal: ChallengeGoal['type']; readonly challengeBoard: readonly (Gem | null)[]; readonly challengeQueue: readonly Triplet[] }

type Colour

type Colour = 'red' | 'blue' | 'green' | 'gold' | 'purple' | 'teal';

A gem colour ID.

function compactBoard

compactBoard(board: readonly (Gem | null)[], width: number, height: number): readonly (Gem | null)[]

Compacts each column downward while preserving the top-to-bottom order of survivors.

function createChallenge

createChallenge(options: ChallengeOptions): GameState

Creates a challenge after validating its authored board, finite queue, goal, and witness.

function createGame

createGame(options?: CreateOptions): GameState

Creates a deterministic empty game with an active piece and three-piece preview.

function createInputScheduler

createInputScheduler(): InputSchedulerState

Creates an empty fixed-tick input scheduler state.

function createLevel

createLevel(id: string | number): GameState

Construct a fresh validated challenge state for a stable level ID or display number.

type CreateOptions

interface CreateOptions { readonly mode?: Exclude<Mode, 'challenge'>; readonly seed?: string; readonly preset?: 'compact' | 'narrow' | 'standard' | 'wide' | 'extraWide' | 'tall' | 'deep' | 'large'; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly pieceLimit?: number }

function decodeGame

decodeGame(text: string): GameState

Replays and validates a bounded save, then verifies its checkpoint against the derived state.

type DifficultyMetrics

interface DifficultyMetrics { readonly seededGoalSuccesses: number; readonly seededGoalSamples: number; readonly seededGoalSuccessRate: number; readonly legalChoiceBreadth: number; readonly goalPreservingChoices: number; readonly probedChoices: number; readonly goalPreservingChoiceShare: number; readonly forcedChoiceShare: number; readonly setupPiecesBeforePayoff: number; readonly requiredChainDepth: number; readonl…

function encodeGame

encodeGame(state: GameState): string

Encodes the authoritative settings, action recording, and derived checkpoint.

function findMatches

findMatches(board: readonly (Gem | null)[], width: number, height: number): readonly MatchWave[]

Finds the union of every maximal horizontal, vertical, and diagonal run of at least three.

type GameEvent

interface GameEvent { readonly type: string; readonly [key: string]: unknown }

type GameState

interface GameState { readonly game: 'falling-triplets'; readonly rules: 'triplets-1'; readonly settings: Settings; readonly board: readonly (Gem | null)[]; readonly phase: Phase; readonly pausedPhase?: Phase; readonly active: Piece | null; readonly next: readonly Triplet[]; readonly bag: readonly Colour[]; readonly randomState: number; readonly nextId: number; readonly score: number; readonly maxChain: number; read…

type Gem

interface Gem { readonly id: number; readonly colour: Colour; readonly target?: boolean }

A settled or active gem with a stable run-local identity.

function getLevel

getLevel(id: string | number): CampaignLevel | undefined

Find a level by stable content ID or its current one-based display number.

function getTutorial

getTutorial(id: string): TutorialDefinition | undefined

Find a lesson by its persistent ID.

function landingY

landingY(state: GameState): number | null

Returns the exact landing row for the current rigid triplet.

function legalActions

legalActions(state: GameState): readonly Action[]

Lists the core actions currently available to the player.

const levelManifest

levelManifest: readonly CampaignLevel[]

type LocalizedText

interface LocalizedText { readonly en: string; readonly ja: string }

type Mode

type Mode = 'relaxed' | 'arcade' | 'daily' | 'challenge';

function nextInt

nextInt(state: number, n: number): readonly [number, number]

Draws an unbiased integer and updated state using rejection sampling.

function nextUint32

nextUint32(state: number): readonly [number, number]

Advances the reference xorshift32 source.

type Phase

type Phase = 'falling' | 'clear-mark' | 'clear-remove' | 'gravity' | 'paused' | 'won' | 'lost' | 'finished';

type Piece

interface Piece { readonly gems: readonly [Gem, Gem, Gem]; readonly x: number; readonly y: number; readonly orientation: 0 | 1 | 2; readonly level: number; readonly descent: number; readonly lockTicks: number; readonly lockResets: number; readonly lockStarted: boolean }

function queueInputEdge

queueInputEdge(state: InputSchedulerState, action: Action): InputSchedulerState

Queues a one-shot cycle, drop, or pause edge for the next logical tick.

type RecordedAction

interface RecordedAction { readonly tick: number; readonly ordinal: number; readonly action: Exclude<Action, { readonly kind: 'tick' }> }

One accepted canonical action and its logical tick position.

function releaseAllActions

releaseAllActions(state: InputSchedulerState): InputSchedulerState

Releases every held action after blur, pause, or pointer cancellation.

function restartGame

restartGame(state: GameState): GameState

Recreates the same authored mode, seed, and challenge configuration from its original setup.

function seedState

seedState(seed: string): number

Hashes a string seed with UTF-8 FNV-1a and returns a nonzero uint32 state.

function setHeldAction

setHeldAction(state: InputSchedulerState, action: HoldAction, held: boolean): InputSchedulerState

Starts or releases one held action; opposite lateral holds cancel during scheduling.

type Settings

interface Settings { readonly mode: Mode; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string; readonly pieceLimit?: number; readonly goal?: 'targets' | 'chain' | 'empty'; readonly targetIds?: readonly number[]; readonly minimumChain?: number; readonly challengeBoard?: readonly (Gem | null)[]; readonly challengeQueue?: readonly Triplet[]; readonly witness?: readonl…

function statusOf

statusOf(state: GameState): Readonly<{ phase: GameState["phase"]; score: number; maxChain: number; reason?: string; }>

Returns a plain status summary suitable for UI labels.

type Transition

interface Transition { readonly state: GameState; readonly events: readonly GameEvent[]; readonly accepted: boolean; readonly reason?: string }

type Triplet

type Triplet = readonly [Colour, Colour, Colour];

type TutorialDefinition

interface TutorialDefinition { readonly id: string; readonly title: LocalizedText; readonly objective: LocalizedText; readonly setup: { readonly board: readonly (Gem | null)[]; readonly queue: readonly Triplet[]; readonly goal: ChallengeGoal; readonly witness: readonly ChallengeHint[]; }; readonly steps: readonly TutorialStep[]; readonly tags: readonly string[]; }

const tutorialManifest

tutorialManifest: readonly TutorialDefinition[]

type TutorialStep

interface TutorialStep { readonly instruction: LocalizedText; readonly action: string }

type Wave

interface Wave { readonly clearedIds: readonly number[]; readonly cells: readonly number[]; readonly chain: number; readonly points: number }

@johnmorrisdotca/houseki/colour-chains

Action actionsForTick advanceTicks applyAction applyScheduledActions campaignManifest Cell ChainCampaignLevel ChainCampaignManifest ChainContentData ChainDifficultyMetrics ChainTutorialDefinition ChainTutorialStep ChallengeGem ChallengeGoal ChallengeOptions Colour createChallenge createGame createInputScheduler createLevel CreateOptions decodeGame encodeGame GameEvent GameState GameStatus Gem getLevel getTutorial HeldControl InputCommand Landing landingCells legalActions levelManifest LocalizedText Mode Orientation Pair Phase PlayAction powerDropPreview Preset processInputTick queueInputEdge RecordedAction releaseAllInput restartGame setHeldInput Settings statusOf StoneChainsOptionsError Transition tutorialManifest Wave WeatherSchedule WitnessStep

type Action

type Action = PlayAction;

function actionsForTick

actionsForTick(game: GameState, input: InputSchedulerState): readonly [readonly Action[], InputSchedulerState]

Resolves this tick's inputs in lateral, rotation, drop, soft-drop, pause order.

function advanceTicks

advanceTicks(state: GameState, ticks: number): Transition

Advances deterministic 60 Hz simulation and resolution; Relaxed and Challenges do not force a falling clock.

function applyAction

applyAction(state: GameState, action: Action): Transition

Applies one immutable accepted action and records its logical-tick position.

function applyScheduledActions

applyScheduledActions(game: GameState, input: InputSchedulerState, actions: readonly Action[], applyAction: (state: GameState, action: Action): Transition) => readonly [GameState, InputSchedulerState, readonly Transition[]]

Applies a scheduler batch until the pair locks or the run pauses; accepted actions are recorded by the engine.

const campaignManifest

campaignManifest: ChainCampaignManifest

type Cell

interface Cell { readonly x: number; readonly y: number }

A visible or hidden board coordinate.

type ChainCampaignLevel

interface ChainCampaignLevel { readonly id: string; readonly number: number; readonly title: LocalizedText; readonly canonicalKeyHash: string; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string; readonly board: readonly (Gem | null)[]; readonly queue: readonly (readonly [Colour, Colour])[]; readonly goal: ChallengeGoal; readonly witness: readonly WitnessStep[]; re…

type ChainCampaignManifest

interface ChainCampaignManifest { readonly count: number; readonly candidatePoolCount: number; readonly generationRevision: string; readonly gradingVersion: string; readonly category: string; readonly curationPolicy: string; readonly orderingPolicy: string; readonly grading: Readonly<Record<string, number | string>>; readonly sampleBudget: number; readonly checksum: string; readonly levels: readonly ChainCampaignLev…

type ChainContentData

interface ChainContentData { readonly campaign: ChainCampaignManifest; readonly tutorials: readonly ChainTutorialDefinition[] }

type ChainDifficultyMetrics

interface ChainDifficultyMetrics { readonly placementProbes: number; readonly legalPlacements: number; readonly goalPreservingPlacements: number; readonly forcedPlacementShare: number; readonly seededPlayoutSamples: number; readonly seededPlayoutQueueDepth: number; readonly seededPlayoutSuccesses: number; readonly seededPlayoutSuccessRate: number; readonly setupPairsBeforePayoff: number; readonly requiredSetupPairs:…

type ChainTutorialDefinition

interface ChainTutorialDefinition { readonly id: string; readonly title: LocalizedText; readonly objective: LocalizedText; readonly setup: { readonly board: readonly (Gem | null)[]; readonly queue: readonly (readonly [Colour, Colour])[]; readonly goal: ChallengeGoal; readonly witness: readonly WitnessStep[] }; readonly steps: readonly ChainTutorialStep[]; readonly tags: readonly string[]; }

type ChainTutorialStep

interface ChainTutorialStep { readonly instruction: LocalizedText; readonly action: string }

type ChallengeGem

type ChallengeGem = Omit<Gem, 'magnetic'>;

Authored Challenge layouts do not permit Shizen-marked stones.

type ChallengeGoal

type ChallengeGoal = { readonly kind: 'clear-targets'; readonly targetIds: readonly number[] } | { readonly kind: 'minimum-chain'; readonly chain: number } | { readonly kind: 'empty-board' };

Challenge objective; target IDs refer to the visible starting board.

type ChallengeOptions

interface ChallengeOptions { readonly id: string; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly seed?: string; readonly board: readonly (ChallengeGem | null)[]; readonly queue: readonly (readonly [Colour, Colour])[]; readonly goal: ChallengeGoal; readonly witness?: readonly WitnessStep[] }

type Colour

type Colour = 'red' | 'blue' | 'green' | 'gold' | 'purple' | 'teal';

A gem colour used by Colour Chains.

function createChallenge

createChallenge(options: ChallengeOptions): GameState

Validates and creates a finite witnessed challenge. Hints use only the supplied verified pair placements.

function createGame

createGame(options?: CreateOptions): GameState

Creates a deterministic seeded game with one active pair and three previews.

function createInputScheduler

createInputScheduler(): InputSchedulerState

Creates an empty held-input scheduler.

function createLevel

createLevel(id: string | number): GameState

Create a fresh engine-validated challenge from a stable content ID or display number.

type CreateOptions

interface CreateOptions { readonly mode?: Exclude<Mode, 'challenge'>; readonly seed?: string; readonly dailyDate?: string; readonly preset?: Preset; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly pairLimit?: number; readonly nature?: boolean; readonly weather?: WeatherSchedule }

function decodeGame

decodeGame(text: string): GameState

Reconstructs a bounded action replay and rejects any checkpoint that differs from replay.

function encodeGame

encodeGame(state: GameState): string

Encodes canonical initial settings, accepted ordinal actions and a derived checkpoint.

type GameEvent

interface GameEvent { readonly type: string; readonly [key: string]: unknown }

A domain event emitted by one transition.

type GameState

interface GameState { readonly game: 'colour-chains'; readonly rules: 'chains-1'; readonly settings: Settings; readonly board: readonly (Gem | null)[]; readonly phase: Phase; readonly pausedPhase?: Exclude<Phase, 'paused'>; readonly active: Pair | null; readonly next: readonly (readonly [Colour, Colour])[]; readonly bag: readonly Colour[]; readonly randomState: number; readonly nextMagnetic?: readonly (readonly [boo…

An immutable Colour Chains rules state. Board rows include the three hidden rows above the well.

type GameStatus

interface GameStatus { readonly mode: Mode; readonly phase: Phase; readonly score: number; readonly maxChain: number; readonly allClears: number; readonly completedPairs: number; readonly assisted: boolean; readonly reason?: string }

Compact player status derived from the current state.

type Gem

interface Gem { readonly id: number; readonly colour: Colour; readonly magnetic?: true }

A settled or active stone with a stable run-local identity.

function getLevel

getLevel(id: string | number): ChainCampaignLevel | undefined

Find a campaign challenge by stable content ID or its current one-based number.

function getTutorial

getTutorial(id: string): ChainTutorialDefinition | undefined

Find a persistent interactive lesson by its stable ID.

type HeldControl

type HeldControl = 'left' | 'right' | 'soft-drop';

type InputCommand

type InputCommand = { readonly kind: 'hold'; readonly control: HeldControl; readonly pressed: boolean } | { readonly kind: 'edge'; readonly action: Extract<PlayAction, { kind: 'rotate-clockwise' | 'rotate-anticlockwise' | 'hard-drop' | 'place' | 'pause' | 'resume' }> };

type Landing

type Landing = readonly [Cell, Cell];

Destination cells for the two independently settled ghost stones.

function landingCells

landingCells(state: GameState): Landing | null

Returns the exact independent landing cells for the current rigid pair without consuming random values.

function legalActions

legalActions(state: GameState): readonly Action[]

Lists only actions accepted by the current game state.

const levelManifest

levelManifest: readonly ChainCampaignLevel[]

type LocalizedText

interface LocalizedText { readonly en: string; readonly ja: string }

type Mode

type Mode = 'relaxed' | 'arcade' | 'daily' | 'challenge';

type Orientation

type Orientation = 'up' | 'right' | 'down' | 'left';

The pivot-to-satellite direction, in clockwise order.

type Pair

interface Pair { readonly pivot: Cell; readonly orientation: Orientation; readonly gems: readonly [Gem, Gem]; readonly level: number; readonly gravityTicks: number; readonly groundedTicks: number; readonly resetCount: number; readonly groundedStarted: boolean }

The active rigid pair. The pivot always retains its own gem identity.

type Phase

type Phase = 'falling' | 'clear-mark' | 'clear-remove' | 'gravity' | 'paused' | 'won' | 'lost' | 'finished';

Observable game phases.

type PlayAction

type PlayAction = { readonly kind: 'left' } | { readonly kind: 'right' } | { readonly kind: 'down' } | { readonly kind: 'rotate-clockwise' } | { readonly kind: 'rotate-anticlockwise' } | { readonly kind: 'hard-drop' } | { readonly kind: 'place' } | { readonly kind: 'pause' } | { readonly kind: 'resume' } | { readonly kind: 'hint' };

function powerDropPreview

powerDropPreview(state: GameState): PowerDropPreview | null

Returns the Shizen hard-drop landing and one-step rebound preview without changing state.

type Preset

type Preset = 'narrow' | 'standard' | 'wide' | 'tall' | 'extraWide' | 'deep' | 'large';

Supported well presets.

function processInputTick

processInputTick(game: GameState, input: InputSchedulerState, apply: (state: GameState, action: Action): Transition, tick: (state: GameState, ticks: number) => Transition) => readonly [GameState, InputSchedulerState, readonly Transition[]]

Applies a complete deterministic held-input and simulation tick, releasing controls on lock or resolution.

function queueInputEdge

queueInputEdge(state: InputSchedulerState, action: EdgeAction): InputSchedulerState

Queues a one-shot rotation, drop, Place, pause or resume edge for the next logical tick.

type RecordedAction

interface RecordedAction { readonly tick: number; readonly ordinal: number; readonly action: PlayAction }

function releaseAllInput

releaseAllInput(state: InputSchedulerState): InputSchedulerState

Releases every held or queued control after focus loss, lock, resolution or pause.

function restartGame

restartGame(state: GameState): GameState

Restarts the same seed/date or original witnessed challenge board and queue.

function setHeldInput

setHeldInput(state: InputSchedulerState, control: HeldControl, pressed: boolean): InputSchedulerState

Starts or releases one held control. Repeated press edges do not reset its repeat counter.

type Settings

interface Settings { readonly mode: Mode; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string; readonly date?: string; readonly pairLimit?: number; readonly nature?: true; readonly weather?: WeatherSchedule; readonly challengeId?: string; readonly goal?: ChallengeGoal; readonly initialBoard?: readonly (ChallengeGem | null)[]; readonly queue?: readonly (readonly [Co…

Run settings fixed at game creation.

function statusOf

statusOf(state: GameState): GameStatus

Returns score, mode, chain and completion fields derived from state.

const StoneChainsOptionsError

StoneChainsOptionsError: typeof StoneChainsOptionsError

Typed setup and authored challenge error.

type Transition

interface Transition { readonly state: GameState; readonly events: readonly GameEvent[]; readonly accepted: boolean; readonly reason?: string }

A deterministic transition; rejected actions retain the exact input state.

const tutorialManifest

tutorialManifest: readonly ChainTutorialDefinition[]

type Wave

interface Wave { readonly chain: number; readonly cells: readonly number[]; readonly ids: readonly number[]; readonly points: number }

A scored simultaneous match wave.

type WeatherSchedule

type WeatherSchedule = 'frequent' | 'rare';

type WitnessStep

interface WitnessStep { readonly pivotX: number; readonly orientation: Orientation }

@johnmorrisdotca/houseki/stone-collapse

Action advanceTicks applyAction BoardPreset BoardShape campaignManifest ChallengeDefinition ChallengeGoal ChallengeOptions CollapseCampaignLevel CollapseCampaignManifest CollapseDifficultyMetrics CollapseTutorialDefinition CollapseTutorialStep createChallenge createGame createLevel CreateOptions decodeGame encodeGame GameEvent GameMode GamePhase GameState GameStatus getLevel getTutorial legalActions levelManifest LocalizedText RecordedAction requestHint restartGame Settings statusOf Stone StoneCollapseOptionsError StoneColour StoredTool ToolInventory Transition tutorialManifest undo UndoFrame

type Action

type Action = | { readonly kind: 'select'; readonly stoneId: number } | { readonly kind: 'confirm' } | { readonly kind: 'cancel' } | { readonly kind: 'undo' } | { readonly kind: 'hint' } | { readonly kind: 'select-tool'; readonly tool: StoredTool } | { readonly kind: 'target-tool'; readonly cell: number } | { readonly kind: 'confirm-tool' } | { readonly kind: 'cancel-tool' };

function advanceTicks

advanceTicks(state: GameState, ticks: number): Transition

Advances the observable 7/6/9 tick removal and gravity stages, bounded to 3600 ticks.

function applyAction

applyAction(state: GameState, action: Action): Transition

Applies a selection, confirmation, or cancellation without mutating its input.

type BoardPreset

type BoardPreset = 'compact' | 'standard' | 'wide' | 'tall' | 'extraWide' | 'deep' | 'large';

type BoardShape

type BoardShape = 'heart' | 'star' | 'hexagon';

const campaignManifest

campaignManifest: CollapseCampaignManifest

type ChallengeDefinition

interface ChallengeDefinition { readonly id: string; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string | number; readonly mask: readonly boolean[]; readonly initialBoard: readonly (Stone | null)[]; readonly goal: ChallengeGoal; readonly moveLimit?: number; /** Each entry is the complete stable-ID set of the next witnessed group. */ readonly witness: readonly (rea…

type ChallengeGoal

type ChallengeGoal = | { readonly kind: 'clear-all' } | { readonly kind: 'clear-targets'; readonly targetIds: readonly number[] } | { readonly kind: 'score-target'; readonly minimumScore: number };

type ChallengeOptions

interface ChallengeOptions { readonly id: string; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed?: string | number; readonly mask?: readonly boolean[]; readonly initialBoard: readonly (Stone | null)[]; readonly goal: ChallengeGoal; readonly moveLimit?: number; readonly witness: readonly (readonly number[])[]; }

type CollapseCampaignLevel

interface CollapseCampaignLevel { readonly id: string; readonly number: number; readonly title: LocalizedText; readonly canonicalKeyHash: string; readonly width: number; readonly height: number; readonly colourCount: 4; readonly mask?: readonly boolean[]; readonly seed: string; readonly board: readonly (Stone | null)[]; readonly goal: ChallengeGoal; readonly moveLimit: number; readonly witness: readonly (readonly nu…

type CollapseCampaignManifest

interface CollapseCampaignManifest { readonly count: number; readonly candidatePoolCount: number; readonly generationRevision: string; readonly gradingVersion: string; readonly category: string; readonly curationPolicy: string; readonly orderingPolicy: string; readonly grading: Readonly<Record<string, unknown>>; readonly sampleBudget: number; readonly checksum: string; readonly levels: readonly CollapseCampaignLevel…

type CollapseDifficultyMetrics

interface CollapseDifficultyMetrics { readonly seededPlayoutSamples: number; readonly seededPlayoutSuccesses: number; readonly seededPlayoutSuccessRate: number; readonly legalGroupChoices: number; readonly witnessedDecisionCount: number; readonly sampledOrderFailureShare: number; readonly forcedSafeGroupShare: number; readonly averageGroupChoices: number; readonly witnessMoves: number; readonly moveBudgetSlack: numb…

type CollapseTutorialDefinition

interface CollapseTutorialDefinition { readonly id: string; readonly title: LocalizedText; readonly objective: LocalizedText; readonly setup: { readonly width: number; readonly height: number; readonly colourCount: 4; readonly board: readonly (Stone | null)[]; readonly goal: ChallengeGoal; readonly moveLimit?: number; readonly witness: readonly (readonly number[])[] }; readonly steps: readonly CollapseTutorialStep[]…

type CollapseTutorialStep

interface CollapseTutorialStep { readonly instruction: LocalizedText; readonly action: string }

function createChallenge

createChallenge(options: ChallengeOptions): GameState

Creates a fixed witnessed challenge after validating its board, objective, move budget, and every witness group.

function createGame

createGame(options?: CreateOptions): GameState

Creates a seeded full board; an all-singleton draw retries before using a checked adjacent pair.

function createLevel

createLevel(id: string | number): GameState

Construct a fresh engine-validated challenge from a content ID or display number.

type CreateOptions

interface CreateOptions { readonly mode?: Exclude<GameMode, 'challenge'>; readonly preset?: BoardPreset; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly seed?: string | number; readonly shape?: BoardShape; /** Row-major custom active-cell mask. Supply width and height with a custom mask. */ readonly mask?: readonly boolean[]; /** Required for Daily; supplied by the host …

function decodeGame

decodeGame(text: string): GameState

Reconstructs and validates a bounded replay before accepting its checkpoint.

function encodeGame

encodeGame(state: GameState): string

Encodes the canonical initial configuration, accepted replay actions, and a derived checkpoint.

type GameEvent

interface GameEvent { readonly type: string; readonly [key: string]: unknown }

type GameMode

type GameMode = 'relaxed' | 'arcade' | 'daily' | 'challenge';

type GamePhase

type GamePhase = 'ready' | 'clear-mark' | 'clear-remove' | 'gravity' | 'won' | 'lost' | 'finished';

type GameState

interface GameState { readonly game: 'stone-collapse'; readonly rules: 'collapse-2'; readonly settings: Settings; readonly challenge: ChallengeDefinition | null; readonly initialBoard: readonly (Stone | null)[]; /** Row-major; masked and currently empty cells contain null. */ readonly board: readonly (Stone | null)[]; readonly phase: GamePhase; readonly selectedId: number | null; readonly selectedIds: readonly numbe…

type GameStatus

interface GameStatus { readonly mode: GameMode; readonly phase: GamePhase; readonly score: number; readonly moves: number; readonly removed: number; readonly remaining: number; readonly previewScore: number; readonly usedFallback: boolean; readonly assisted: boolean; readonly goal?: ChallengeGoal; readonly reason?: string; readonly inventory: ToolInventory; readonly toolProgress: number; readonly toolAwardCursor: 0 …

function getLevel

getLevel(id: string | number): CollapseCampaignLevel | undefined

Find a challenge by its persistent content ID or current one-based display number.

function getTutorial

getTutorial(id: string): CollapseTutorialDefinition | undefined

Find a persistent interactive lesson by its stable ID.

function legalActions

legalActions(state: GameState): readonly Action[]

Lists every selection and confirmation currently accepted by the rules.

const levelManifest

levelManifest: readonly CollapseCampaignLevel[]

type LocalizedText

interface LocalizedText { readonly en: string; readonly ja: string }

type RecordedAction

type RecordedAction = | { readonly kind: 'remove'; readonly move: number; readonly stoneId: number } | { readonly kind: 'select-tool'; readonly tool: StoredTool } | { readonly kind: 'target-tool'; readonly cell: number } | { readonly kind: 'confirm-tool'; readonly move: number } | { readonly kind: 'cancel-tool' } | { readonly kind: 'undo'; readonly move: number } | { readonly kind: 'hint'; readonly witnessIndex: num…

function requestHint

requestHint(state: GameState): Transition

Selects the next supplied witness group only while the run still matches its path; accepted hints mark the run assisted.

function restartGame

restartGame(state: GameState): GameState

Starts a fresh attempt from the identical seeded board or preserved challenge definition.

type Settings

interface Settings { readonly mode: GameMode; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string | number; readonly shape?: BoardShape; readonly mask: readonly boolean[]; readonly dailyDate?: string; readonly challengeId?: string; readonly goal?: ChallengeGoal; readonly moveLimit?: number; readonly tools: boolean; }

function statusOf

statusOf(state: GameState): GameStatus

Returns the stable public game status and the remaining-stone count.

type Stone

interface Stone { readonly id: number; readonly colour: StoneColour }

A stone keeps its run-local identity as it falls or changes column.

const StoneCollapseOptionsError

StoneCollapseOptionsError: typeof StoneCollapseOptionsError

Typed public-settings validation error.

type StoneColour

type StoneColour = 'red' | 'blue' | 'green' | 'gold' | 'purple' | 'teal';

A colour used by a stone.

type StoredTool

type StoredTool = 'bomb' | 'pick';

type ToolInventory

interface ToolInventory { readonly bomb: number; readonly pick: number }

type Transition

interface Transition { readonly state: GameState; readonly events: readonly GameEvent[]; readonly accepted: boolean; readonly reason?: string }

const tutorialManifest

tutorialManifest: readonly CollapseTutorialDefinition[]

function undo

undo(state: GameState): Transition

Restores the full pre-move state in Relaxed or Challenge practice and permanently marks the attempt assisted.

type UndoFrame

interface UndoFrame { readonly board: readonly (Stone | null)[]; readonly score: number; readonly moves: number; readonly removed: number; readonly randomState: number; readonly nextId: number; readonly usedFallback: boolean; readonly finishAdjustmentApplied: boolean; readonly selectedId: number | null; readonly selectedIds: readonly number[]; readonly previewScore: number; readonly witnessIndex: number; readonly in…

Data sufficient to restore the complete game immediately before a committed move.

@johnmorrisdotca/houseki/gem-swap

Action advanceTicks advanceTime applyAction BlackHolePortal BoardPreset BoardShape CAMPAIGN_CHECKSUM CAMPAIGN_COUNT CancelToolAction ChallengeRules ConfirmToolAction createGame CreateOptions decodeGame encodeGame GameEvent GameMode GameOutcome GamePhase GameState GameStatus Gem GEM_SWAP_CAMPAIGN GEM_SWAP_LESSONS GemColour GemSwapCampaignLevel GemSwapLesson GemSwapLessonStep GemSwapOptionsError GemSwapReviewStatus GENERATION_REVISION Goal GRADING_VERSION GRADING_WEIGHTS HintAction hintGame legalActions OrdinaryToolKind PlannedSpecial ReplayError ReplayOperation ReshuffleAction reshuffleGame restartGame SAMPLE_BUDGET Seal SelectToolAction Settings SpecialKind statusOf SwapAction TargetToolAction ToolInventory ToolKind Transition UndoAction undoGame validateChallengeWitness

type Action

type Action = SwapAction | SelectToolAction | TargetToolAction | ConfirmToolAction | CancelToolAction | UndoAction | HintAction | ReshuffleAction;

function advanceTicks

advanceTicks(state: GameState, ticks: number): Transition

Advances deterministic resolution ticks; wall-clock time is supplied separately by the host.

function advanceTime

advanceTime(state: GameState, milliseconds: number): Transition

Adds explicit host-measured elapsed time. Arcade runs end at 180 seconds; no clock is read here.

function applyAction

applyAction(state: GameState, action: Action): Transition

Applies one recorded player action. Rejected actions preserve state identity and are not recorded.

type BlackHolePortal

interface BlackHolePortal { readonly cell: number; readonly capacityRemaining: number; readonly movesRemaining: number; readonly consumedIds: readonly number[]; }

type BoardPreset

type BoardPreset = 'compact' | 'standard' | 'wide' | 'tall' | 'extraWide' | 'deep' | 'large';

type BoardShape

type BoardShape = 'heart' | 'star' | 'hexagon';

const CAMPAIGN_CHECKSUM

CAMPAIGN_CHECKSUM: "eb7c54a583db428d596b8fbdab0d63a9ad39ad5b9e39948dc4ccab84ba1196aa"

const CAMPAIGN_COUNT

CAMPAIGN_COUNT: 50

type CancelToolAction

interface CancelToolAction { readonly kind: 'cancel-tool' }

type ChallengeRules

interface ChallengeRules { readonly goals: readonly Goal[]; readonly moveLimit?: number; readonly seals?: readonly Seal[] }

type ConfirmToolAction

interface ConfirmToolAction { readonly kind: 'confirm-tool' }

function createGame

createGame(options?: CreateOptions): GameState

Creates a deterministic stable board with at least one legal normal swap.

type CreateOptions

interface CreateOptions { readonly preset?: BoardPreset; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly seed?: string | number; readonly tools?: boolean; readonly advancedTools?: boolean; readonly shape?: BoardShape; /** Row-major active-cell mask. Requires explicit custom dimensions. */ readonly mask?: readonly boolean[]; readonly mode?: GameMode; /** Required for Dail…

function decodeGame

decodeGame(serialized: string): GameState

Validates and reconstructs a state by replay; serialized board snapshots are never trusted.

function encodeGame

encodeGame(state: GameState): string

Encodes the initial rules plus accepted actions and logical inputs in canonical JSON.

type GameEvent

interface GameEvent { readonly type: string; readonly [key: string]: unknown }

type GameMode

type GameMode = 'relaxed' | 'arcade' | 'daily' | 'challenge';

type GameOutcome

type GameOutcome = 'won' | 'lost' | 'finished' | null;

type GamePhase

type GamePhase = 'ready' | 'clear-mark' | 'clear-remove' | 'gravity' | 'refill' | 'finished';

type GameState

interface GameState { readonly game: 'gem-swap'; readonly rules: 'swap-1'; readonly settings: Settings; readonly initialOptions: CreateOptions; readonly mode: GameMode; readonly dailyDate: string | null; readonly challenge: ChallengeRules | null; readonly seals: readonly Seal[]; readonly sealsCleared: number; readonly elapsedMs: number; readonly outcome: GameOutcome; readonly clearedByColour: Readonly<Record<GemColo…

type GameStatus

interface GameStatus { readonly phase: GamePhase; readonly score: number; readonly moves: number; readonly legalMoveCount: number; readonly usedFallback: boolean; readonly availableToolCount: number; readonly inventory: ToolInventory; readonly toolProgress: number; readonly toolAwardCursor: number; readonly assisted: boolean; readonly blackHoleCharges: 0 | 1; readonly blackHoleProgress: number; readonly blackHole: B…

type Gem

interface Gem { readonly id: number; readonly colour: GemColour; readonly kind?: SpecialKind }

A gem has a stable identity and stored colour until it is cleared. Missing kind means normal.

const GEM_SWAP_CAMPAIGN

GEM_SWAP_CAMPAIGN: readonly GemSwapCampaignLevel[]

const GEM_SWAP_LESSONS

GEM_SWAP_LESSONS: readonly GemSwapLesson[]

type GemColour

type GemColour = 'red' | 'blue' | 'green' | 'gold' | 'purple' | 'teal';

A colour used by a normal gem.

type GemSwapCampaignLevel

interface GemSwapCampaignLevel { readonly id: string; readonly number: number; readonly title: { readonly en: string; readonly ja: string }; readonly options: CreateOptions; readonly width: number; readonly height: number; readonly colourCount: number; readonly seed: number; readonly challenge: NonNullable<CreateOptions['challenge']>; readonly witness: readonly ReplayOperation[]; readonly canonicalKeyHash: string; r…

type GemSwapLesson

interface GemSwapLesson { readonly id: string; readonly title: { readonly en: string; readonly ja: string }; readonly initial: CreateOptions; readonly steps: readonly GemSwapLessonStep[]; readonly witness: readonly ReplayOperation[]; readonly reviewStatus: GemSwapReviewStatus }

type GemSwapLessonStep

type GemSwapLessonStep = { readonly kind: 'action'; readonly id: string; readonly action: Action; readonly accepted: boolean; readonly reason?: string; readonly event?: string; readonly cell?: number; readonly text: { readonly en: string; readonly ja: string } } | { readonly kind: 'ticks'; readonly id: string; readonly count: number; readonly event?: string; readonly outcome?: string; readonly cell?: number; readonl…

const GemSwapOptionsError

GemSwapOptionsError: typeof GemSwapOptionsError

Typed error for invalid settings or a board that cannot be generated safely.

type GemSwapReviewStatus

type GemSwapReviewStatus = 'human-review-pending';

const GENERATION_REVISION

GENERATION_REVISION: "gem-swap-campaign-1.3.0"

type Goal

type Goal = | { readonly kind: 'score'; readonly target: number } | { readonly kind: 'collect'; readonly colour: GemColour; readonly target: number } | { readonly kind: 'chain'; readonly target: number } | { readonly kind: 'seals' };

const GRADING_VERSION

GRADING_VERSION: "swap-choice-forgiveness-1"

const GRADING_WEIGHTS

GRADING_WEIGHTS: { seededPlayoutDifficulty: number; choiceDifficulty: number; objectiveCoordination: number; chainDepth: number; witnessLength: number; specialCombination: number; }

type HintAction

interface HintAction { readonly kind: 'hint' }

function hintGame

hintGame(state: GameState): Transition

Returns a deterministic legal swap hint and permanently marks the run assisted.

function legalActions

legalActions(state: GameState): readonly Action[]

Lists all legal swaps that would be accepted from the current stable state.

type OrdinaryToolKind

type OrdinaryToolKind = Exclude<ToolKind, 'black-hole'>;

type PlannedSpecial

interface PlannedSpecial { readonly cell: number; readonly kind: SpecialKind }

const ReplayError

ReplayError: typeof ReplayError

type ReplayOperation

type ReplayOperation = { readonly kind: 'action'; readonly action: Action } | { readonly kind: 'ticks'; readonly count: number } | { readonly kind: 'time'; readonly milliseconds: number };

type ReshuffleAction

interface ReshuffleAction { readonly kind: 'reshuffle' }

function reshuffleGame

reshuffleGame(state: GameState): Transition

Shuffles existing gem records in Relaxed play, preserving their colour and special counts.

function restartGame

restartGame(state: GameState): GameState

Starts the exact initial ruleset and seed again, discarding all run progress.

const SAMPLE_BUDGET

SAMPLE_BUDGET: 24

type Seal

interface Seal { readonly cell: number; readonly layers: number }

type SelectToolAction

interface SelectToolAction { readonly kind: 'select-tool'; readonly tool: ToolKind }

type Settings

interface Settings { readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string | number; readonly tools: boolean; readonly advancedTools: boolean; readonly shape?: BoardShape; readonly mask: readonly boolean[]; }

type SpecialKind

type SpecialKind = 'row-beam' | 'column-beam' | 'bomb' | 'colour-burst';

function statusOf

statusOf(state: GameState): GameStatus

type SwapAction

interface SwapAction { readonly kind: 'swap'; readonly from: number; readonly to: number }

Swaps two row-major cell addresses.

type TargetToolAction

interface TargetToolAction { readonly kind: 'target-tool'; readonly cell: number }

type ToolInventory

type ToolInventory = Readonly<Record<OrdinaryToolKind, number>>;

type ToolKind

type ToolKind = 'bomb' | 'row-clear' | 'colour-clear' | 'black-hole';

type Transition

interface Transition { readonly state: GameState; readonly events: readonly GameEvent[]; readonly accepted: boolean; readonly reason?: string }

type UndoAction

interface UndoAction { readonly kind: 'undo' }

function undoGame

undoGame(state: GameState): Transition

Restores the last committed swap in Relaxed or Challenge play and marks assistance.

function validateChallengeWitness

validateChallengeWitness(options: CreateOptions, witness: readonly ReplayOperation[]): { readonly valid: boolean; readonly state?: GameState; readonly reason?: string; }

Checks a complete authored witness through the public deterministic rules API.

@johnmorrisdotca/houseki/nature

applyEarthquake applyLightning applyMagneticPulse applyPowerDrop AttractionPairPreview createNatureState EarthquakeKind EarthquakeOptions EnvironmentEvent EnvironmentPreview EnvironmentSchedule EnvironmentTransition FaultPreview HorizontalDirection isEnvironmentTurnScheduled JumblePreview LightningOptions LightningPreview MagneticPreview MarkingOptions markMagneticStones NatureEntity NatureErrorCode NatureEvent NatureMove NatureOptions NatureOptionsError NaturePieceCell NatureRules NatureState NatureTransition PowerDropPreview PowerDropRequest previewEarthquake previewFault previewJumble previewLightning previewMagneticPulse previewPowerDrop QuarterTurn ReboundAttemptPreview ReboundDirectionMode

function applyEarthquake

applyEarthquake(state: NatureState, options: EarthquakeOptions): EnvironmentTransition

Commit one bounded environmental resolution; matching, gravity and score remain the host game's job.

function applyLightning

applyLightning(state: NatureState, options?: LightningOptions): EnvironmentTransition

Commit one lightning strike. It removes exposed stones only and consumes no scores or tools.

function applyMagneticPulse

applyMagneticPulse(state: NatureState): NatureTransition

Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.

function applyPowerDrop

applyPowerDrop(state: NatureState, request: PowerDropRequest): NatureTransition

Commit a previewed landing as one immutable operation. A failed preview leaves the original state untouched.

type AttractionPairPreview

interface AttractionPairPreview { readonly ids: readonly [number, number]; readonly axis: 'horizontal' | 'vertical'; readonly emptyGap: number; readonly midpoint: { readonly x: number; readonly y: number }; readonly anchoredId?: number; readonly moves: readonly NatureMove[]; }

function createNatureState

createNatureState(options: NatureOptions): NatureState

type EarthquakeKind

type EarthquakeKind = 'fault' | 'jumble' | 'combined';

type EarthquakeOptions

interface EarthquakeOptions { readonly kind: EarthquakeKind; readonly enabled?: boolean }

type EnvironmentEvent

type EnvironmentEvent = | { readonly type: 'fault-opened'; readonly path: readonly number[]; readonly fracturedCells: readonly number[]; readonly removedIds: readonly number[] } | { readonly type: 'jumble-completed'; readonly changed: boolean; readonly reason?: 'no-eligible-patch'; readonly patchCells: readonly number[]; readonly moves: readonly NatureMove[] } | { readonly type: 'lightning-struck'; readonly columns:…

type EnvironmentPreview

type EnvironmentPreview = EarthquakePreview | LightningPreview;

type EnvironmentSchedule

type EnvironmentSchedule = { readonly kind: 'frequent' } | { readonly kind: 'rare' } | { readonly kind: 'midlevel'; readonly turn: number } | { readonly kind: 'authored'; readonly turns: readonly number[] };

type EnvironmentTransition

interface EnvironmentTransition { readonly state: NatureState; readonly accepted: true; readonly events: readonly EnvironmentEvent[]; readonly preview: EnvironmentPreview }

type FaultPreview

interface FaultPreview { readonly path: readonly number[]; readonly fracturedCells: readonly number[]; readonly removedIds: readonly number[]; readonly cells: readonly (NatureEntity | null)[]; readonly activeCells: readonly boolean[]; readonly randomStateAfter: number }

type HorizontalDirection

type HorizontalDirection = 'left' | 'right';

function isEnvironmentTurnScheduled

isEnvironmentTurnScheduled(turn: number, schedule: EnvironmentSchedule): boolean

Pure one-based schedule lookup: frequent is every second turn; rare is every eighth turn.

type JumblePreview

interface JumblePreview { readonly patchCells: readonly number[]; readonly moves: readonly NatureMove[]; readonly changed: boolean; readonly reason?: 'no-eligible-patch'; readonly cells: readonly (NatureEntity | null)[]; readonly randomStateAfter: number }

type LightningOptions

interface LightningOptions { readonly enabled?: boolean; readonly columns?: readonly number[]; readonly columnCount?: 1 | 2 | 3 }

type LightningPreview

interface LightningPreview { readonly columns: readonly number[]; readonly removedCells: readonly number[]; readonly removedIds: readonly number[]; readonly cells: readonly (NatureEntity | null)[]; readonly randomStateAfter: number }

type MagneticPreview

interface MagneticPreview { readonly pairs: readonly AttractionPairPreview[]; readonly moves: readonly NatureMove[]; readonly cells: readonly (NatureEntity | null)[]; }

type MarkingOptions

interface MarkingOptions { readonly probability: number; readonly maximum?: number; readonly anchoredProbability?: number }

function markMagneticStones

markMagneticStones(state: NatureState, options: MarkingOptions): NatureTransition

Deterministically assign magnetic markings to settled stones without changing their IDs or colours.

type NatureEntity

interface NatureEntity { readonly id: number; readonly kind: 'stone' | 'obstacle'; readonly colour?: string; readonly magnetic?: boolean; readonly anchored?: boolean; }

Stable board occupant. Obstacles collide and block attraction but cannot move or attract.

type NatureErrorCode

type NatureErrorCode = 'invalid-nature-options' | 'invalid-grid-size' | 'invalid-seed' | 'invalid-mask' | 'invalid-cell' | 'duplicate-id' | 'invalid-marking-options' | 'invalid-power-drop' | 'piece-collision' | 'invalid-piece-position';

type NatureEvent

type NatureEvent = | { readonly type: 'magnetic-pulse'; readonly pairs: readonly AttractionPairPreview[]; readonly moves: readonly NatureMove[] } | { readonly type: 'magnetic-stones-marked'; readonly ids: readonly number[]; readonly randomState: number } | { readonly type: 'power-drop-landed'; readonly power: boolean; readonly rebound: boolean; readonly direction?: HorizontalDirection; readonly quarterTurn: QuarterT…

type NatureMove

interface NatureMove { readonly id: number; readonly from: { readonly x: number; readonly y: number }; readonly to: { readonly x: number; readonly y: number }; }

type NatureOptions

interface NatureOptions { readonly width: number; readonly height: number; readonly seed?: string; /** Row-major passability mask. False cells are permanent gaps and cannot hold pieces. */ readonly activeCells?: readonly boolean[]; /** Row-major initial occupants; false mask cells must be empty. */ readonly cells?: readonly (NatureEntity | null)[]; readonly magneticEnabled?: boolean; readonly reboundEnabled?: boolea…

const NatureOptionsError

NatureOptionsError: typeof NatureOptionsError

type NaturePieceCell

interface NaturePieceCell extends NatureEntity { readonly kind: 'stone'; readonly x: number; readonly y: number; }

type NatureRules

interface NatureRules { readonly magneticEnabled: boolean; readonly reboundEnabled: boolean; readonly reboundDirectionMode: ReboundDirectionMode; }

type NatureState

interface NatureState { readonly width: number; readonly height: number; readonly activeCells: readonly boolean[]; readonly cells: readonly (NatureEntity | null)[]; readonly seed: string; readonly randomState: number; readonly nextId: number; readonly committedPlacements: number; readonly rules: NatureRules; }

type NatureTransition

interface NatureTransition { readonly state: NatureState; readonly accepted: boolean; readonly reason?: string; readonly events: readonly NatureEvent[]; readonly preview?: MagneticPreview | PowerDropPreview; }

type PowerDropPreview

interface PowerDropPreview { readonly normalLanding: readonly NaturePieceCell[]; readonly attempts: readonly ReboundAttemptPreview[]; readonly rebound: boolean; readonly selectedDirection?: HorizontalDirection; readonly quarterTurn: QuarterTurn; readonly finalCells: readonly NaturePieceCell[]; readonly moves: readonly NatureMove[]; readonly randomStateAfter: number; }

type PowerDropRequest

interface PowerDropRequest { readonly piece: readonly NaturePieceCell[]; readonly pivotId: number; readonly power: boolean; readonly lastHorizontalDirection?: HorizontalDirection; /** A caller-selected quarter-turn applied with the one-cell rebound. */ readonly quarterTurn?: QuarterTurn; }

function previewEarthquake

previewEarthquake(state: NatureState, options: EarthquakeOptions): EarthquakePreview

Preview an earthquake, with Combined always fracturing before its one jumble attempt.

function previewFault

previewFault(state: NatureState): FaultPreview

Preview a bounded top-to-bottom fracture. The path keeps at least one column active on each side.

function previewJumble

previewJumble(state: NatureState): JumblePreview

Preview one seeded, nontrivial stone permutation inside a fully active connected rectangle no larger than 3×3.

function previewLightning

previewLightning(state: NatureState, options?: LightningOptions): LightningPreview

Preview seeded or explicitly selected columns; a mask gap or obstacle blocks the strike below it.

function previewMagneticPulse

previewMagneticPulse(state: NatureState): MagneticPreview

Preview one bounded deterministic attraction pulse without mutating the state.

function previewPowerDrop

previewPowerDrop(state: NatureState, request: PowerDropRequest): PowerDropPreview

Preview a single normal landing and, for a power drop, at most one validated one-cell rebound.

type QuarterTurn

type QuarterTurn = -1 | 0 | 1;

type ReboundAttemptPreview

interface ReboundAttemptPreview { readonly direction: HorizontalDirection; readonly quarterTurn: QuarterTurn; readonly valid: boolean; }

type ReboundDirectionMode

type ReboundDirectionMode = 'input-or-alternate' | 'seeded';

@johnmorrisdotca/houseki/magnetic-blocks

Action advanceTicks applyAction Block blockBonds blockCells Bond calmGravity canPlace Colour createGame CreateOptions decodeGame encodeGame Floor FloorSchedule GameEvent GameState GameStatus Gem Goal GRAVITY_TICKS impactSupportIds landingY legalActions MagneticBlocksOptionsError magneticGravity MARK_TICKS matchGroups Mode Phase RecordedAction REMOVE_TICKS removeIds restartGame Settings settle statusOf Transition

type Action

type Action = | { readonly kind: 'left' } | { readonly kind: 'right' } | { readonly kind: 'soft-drop' } | { readonly kind: 'rotate-clockwise' } | { readonly kind: 'rotate-anticlockwise' } | { readonly kind: 'hard-drop' } | { readonly kind: 'land' } | { readonly kind: 'pause' } | { readonly kind: 'resume' } | { readonly kind: 'set-floor-override'; readonly floor: Floor } | { readonly kind: 'cancel-floor-override' };

function advanceTicks

advanceTicks(state: GameState, ticks: number): Transition

Advances fixed 60 Hz time, resolving phases in all modes and falling/locking only in Arcade.

function applyAction

applyAction(state: GameState, action: Action): Transition

Applies one movement, rotation, floor-switch or placement action without mutation.

type Block

interface Block { readonly x: number; readonly y: number; readonly orientation: 0 | 1 | 2 | 3; readonly gems: readonly [Gem, Gem, Gem, Gem]; readonly descent: number; readonly lockTicks: number; readonly lockResets: number; readonly lockStarted: boolean }

function blockBonds

blockBonds(block: Block): readonly Bond[]

function blockCells

blockCells(block: Block, width: number): readonly { readonly index: number; readonly gem: Gem; }[]

Returns stable-gem addresses after rotating the square block around its centre.

type Bond

interface Bond { readonly a: number; readonly b: number }

function calmGravity

calmGravity(initial: readonly (Gem | null)[], bonds: readonly Bond[], width: number, height: number, mask: readonly boolean[]): readonly (Gem | null)[]

Settles bonded connected components one cell per deterministic bottom/ID priority step.

function canPlace

canPlace(block: Block, board: readonly (Gem | null)[], width: number, height: number, mask: readonly boolean[]): boolean

type Colour

type Colour = 'red' | 'blue' | 'green' | 'gold' | 'purple' | 'teal';

A standard gem colour used by a magnetic block.

function createGame

createGame(options?: import("./types.js").CreateOptions): GameState

Creates an immutable seeded 2×2-block game with a fixed queue and explicit floor schedule.

type CreateOptions

interface CreateOptions { readonly mode?: Mode; readonly width?: number; readonly height?: number; readonly colourCount?: 4 | 5 | 6; readonly seed?: string | number; readonly mask?: readonly boolean[]; readonly initialBoard?: readonly (Gem | null)[]; readonly bonds?: readonly Bond[]; readonly queue?: readonly (readonly [Colour, Colour, Colour, Colour])[]; readonly pieceLimit?: number; readonly schedule?: FloorSchedu…

function decodeGame

decodeGame(text: string): GameState

Replays bounded canonical input and verifies every saved checkpoint field.

function encodeGame

encodeGame(state: GameState): string

Encodes canonical settings, accepted tick-stamped actions and a verified replay checkpoint.

type Floor

type Floor = 'calm' | 'magnetic';

type FloorSchedule

type FloorSchedule = | { readonly kind: 'frequent' } | { readonly kind: 'occasional' } | { readonly kind: 'infrequent' } | { readonly kind: 'mid-level'; readonly placement: number } | { readonly kind: 'fixed'; readonly floor: Floor } | { readonly kind: 'authored'; readonly magneticPlacements: readonly number[]; readonly defaultFloor?: Floor };

type GameEvent

interface GameEvent { readonly type: string; readonly [key: string]: unknown }

type GameState

interface GameState { readonly game: 'magnetic-blocks'; readonly rules: 'magnetic-blocks-1'; readonly settings: Settings; readonly board: readonly (Gem | null)[]; readonly bonds: readonly Bond[]; readonly active: Block | null; readonly queue: readonly (readonly [Colour, Colour, Colour, Colour])[]; readonly randomState: number; readonly nextId: number; readonly phase: Phase; readonly pausedPhase?: Exclude<Phase, 'pau…

type GameStatus

interface GameStatus { readonly phase: Phase; readonly score: number; readonly moves: number; readonly placements: number; readonly floor: Floor; readonly nextFloor: Floor; readonly floorSwitchCharges: number; readonly pendingFloorOverride: Floor | null; readonly maxChain: number; readonly reason?: string }

type Gem

interface Gem { readonly id: number; readonly colour: Colour }

type Goal

type Goal = { readonly kind: 'clear-all' } | { readonly kind: 'clear-targets'; readonly targetIds: readonly number[] };

const GRAVITY_TICKS

GRAVITY_TICKS: 9

function impactSupportIds

impactSupportIds(block: Block, board: readonly (Gem | null)[], width: number, height: number, mask: readonly boolean[]): readonly number[]

Collects the occupied one-cell support layer under both block columns at first contact.

function landingY

landingY(block: Block, board: readonly (Gem | null)[], width: number, height: number, mask: readonly boolean[]): number

function legalActions

legalActions(state: GameState): readonly Action[]

Lists accepted controls for the current falling or resolution phase.

const MagneticBlocksOptionsError

MagneticBlocksOptionsError: typeof MagneticBlocksOptionsError

function magneticGravity

magneticGravity(initial: readonly (Gem | null)[], width: number, height: number, mask: readonly boolean[]): readonly (Gem | null)[]

Magnetic floor removes all bonds and settles each column segment independently.

const MARK_TICKS

MARK_TICKS: 7

function matchGroups

matchGroups(board: readonly (Gem | null)[], width: number, height: number, mask: readonly boolean[]): readonly (readonly number[])[]

Computes deterministic orthogonal colour components of at least four gems.

type Mode

type Mode = 'relaxed' | 'arcade';

type Phase

type Phase = 'falling' | 'clear-mark' | 'clear-remove' | 'gravity' | 'paused' | 'won' | 'lost' | 'finished';

type RecordedAction

interface RecordedAction { readonly tick: number; readonly ordinal: number; readonly action: Action }

const REMOVE_TICKS

REMOVE_TICKS: 6

function removeIds

removeIds(board: readonly (Gem | null)[], bonds: readonly Bond[], indices: readonly number[]): { readonly board: readonly (Gem | null)[]; readonly bonds: readonly Bond[]; readonly ids: readonly number[]; }

function restartGame

restartGame(state: GameState): GameState

Restarts the same seed, custom starting board, fixed queue, rules and floor options.

type Settings

interface Settings { readonly mode: Mode; readonly width: number; readonly height: number; readonly colourCount: 4 | 5 | 6; readonly seed: string | number; readonly mask: readonly boolean[]; readonly schedule: FloorSchedule; readonly floorSwitch: boolean; readonly magneticImpact: boolean; readonly pieceLimit: number; readonly initialBoard: readonly (Gem | null)[]; readonly bonds: readonly Bond[]; readonly initialQue…

function settle

settle(initial: readonly (Gem | null)[], bonds: readonly Bond[], floor: Floor, width: number, height: number, mask: readonly boolean[]): { readonly board: readonly (Gem | null)[]; readonly bonds: readonly Bond[]; }

function statusOf

statusOf(state: GameState): GameStatus

Returns a compact immutable status including current and upcoming floor state.

type Transition

interface Transition { readonly state: GameState; readonly events: readonly GameEvent[]; readonly accepted: boolean; readonly reason?: string }