@johnmorrisdotca/houseki 0.0.0-stage → 0.1.0

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.
Files changed (118) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -2
  4. package/dist/colour-chains/challenge.d.ts +3 -0
  5. package/dist/colour-chains/challenge.js +120 -0
  6. package/dist/colour-chains/content-data.d.ts +2 -0
  7. package/dist/colour-chains/content-data.js +9534 -0
  8. package/dist/colour-chains/content-types.d.ts +80 -0
  9. package/dist/colour-chains/content-types.js +1 -0
  10. package/dist/colour-chains/content.d.ts +11 -0
  11. package/dist/colour-chains/content.js +28 -0
  12. package/dist/colour-chains/engine.d.ts +19 -0
  13. package/dist/colour-chains/engine.js +493 -0
  14. package/dist/colour-chains/input.d.ts +24 -0
  15. package/dist/colour-chains/input.js +77 -0
  16. package/dist/colour-chains/match.d.ts +5 -0
  17. package/dist/colour-chains/match.js +48 -0
  18. package/dist/colour-chains/persistence.d.ts +7 -0
  19. package/dist/colour-chains/persistence.js +96 -0
  20. package/dist/colour-chains/random.d.ts +7 -0
  21. package/dist/colour-chains/random.js +49 -0
  22. package/dist/colour-chains/types.d.ts +191 -0
  23. package/dist/colour-chains/types.js +1 -0
  24. package/dist/colour-chains/validation.d.ts +16 -0
  25. package/dist/colour-chains/validation.js +80 -0
  26. package/dist/colour-chains.d.ts +8 -0
  27. package/dist/colour-chains.js +7 -0
  28. package/dist/falling-triplets/content-data.d.ts +7258 -0
  29. package/dist/falling-triplets/content-data.js +18073 -0
  30. package/dist/falling-triplets/content-types.d.ts +72 -0
  31. package/dist/falling-triplets/content-types.js +1 -0
  32. package/dist/falling-triplets/content.d.ts +11 -0
  33. package/dist/falling-triplets/content.js +39 -0
  34. package/dist/falling-triplets/engine.d.ts +26 -0
  35. package/dist/falling-triplets/engine.js +541 -0
  36. package/dist/falling-triplets/input.d.ts +18 -0
  37. package/dist/falling-triplets/input.js +41 -0
  38. package/dist/falling-triplets/match.d.ts +9 -0
  39. package/dist/falling-triplets/match.js +45 -0
  40. package/dist/falling-triplets/random.d.ts +9 -0
  41. package/dist/falling-triplets/random.js +44 -0
  42. package/dist/falling-triplets/types.d.ts +134 -0
  43. package/dist/falling-triplets/types.js +1 -0
  44. package/dist/falling-triplets.d.ts +7 -0
  45. package/dist/falling-triplets.js +6 -0
  46. package/dist/gem-swap/black-hole.d.ts +7 -0
  47. package/dist/gem-swap/black-hole.js +54 -0
  48. package/dist/gem-swap/board.d.ts +25 -0
  49. package/dist/gem-swap/board.js +89 -0
  50. package/dist/gem-swap/content.d.ts +75 -0
  51. package/dist/gem-swap/content.js +68 -0
  52. package/dist/gem-swap/engine.d.ts +25 -0
  53. package/dist/gem-swap/engine.js +689 -0
  54. package/dist/gem-swap/persistence.d.ts +17 -0
  55. package/dist/gem-swap/persistence.js +107 -0
  56. package/dist/gem-swap/practice.d.ts +4 -0
  57. package/dist/gem-swap/practice.js +57 -0
  58. package/dist/gem-swap/random.d.ts +9 -0
  59. package/dist/gem-swap/random.js +32 -0
  60. package/dist/gem-swap/specials.d.ts +13 -0
  61. package/dist/gem-swap/specials.js +300 -0
  62. package/dist/gem-swap/tools.d.ts +3 -0
  63. package/dist/gem-swap/tools.js +20 -0
  64. package/dist/gem-swap/types.d.ts +200 -0
  65. package/dist/gem-swap/types.js +1 -0
  66. package/dist/gem-swap.d.ts +5 -0
  67. package/dist/gem-swap.js +3 -0
  68. package/dist/index.d.ts +7 -0
  69. package/dist/index.js +7 -0
  70. package/dist/magnetic-blocks/engine.d.ts +11 -0
  71. package/dist/magnetic-blocks/engine.js +263 -0
  72. package/dist/magnetic-blocks/persistence.d.ts +7 -0
  73. package/dist/magnetic-blocks/persistence.js +104 -0
  74. package/dist/magnetic-blocks/physics.d.ts +30 -0
  75. package/dist/magnetic-blocks/physics.js +155 -0
  76. package/dist/magnetic-blocks/random.d.ts +6 -0
  77. package/dist/magnetic-blocks/random.js +19 -0
  78. package/dist/magnetic-blocks/types.d.ts +158 -0
  79. package/dist/magnetic-blocks/types.js +1 -0
  80. package/dist/magnetic-blocks/validation.d.ts +23 -0
  81. package/dist/magnetic-blocks/validation.js +121 -0
  82. package/dist/magnetic-blocks.d.ts +5 -0
  83. package/dist/magnetic-blocks.js +4 -0
  84. package/dist/nature/attraction.d.ts +5 -0
  85. package/dist/nature/attraction.js +136 -0
  86. package/dist/nature/environment.d.ts +91 -0
  87. package/dist/nature/environment.js +272 -0
  88. package/dist/nature/marking.d.ts +3 -0
  89. package/dist/nature/marking.js +39 -0
  90. package/dist/nature/random.d.ts +3 -0
  91. package/dist/nature/random.js +19 -0
  92. package/dist/nature/rebound.d.ts +5 -0
  93. package/dist/nature/rebound.js +98 -0
  94. package/dist/nature/state.d.ts +3 -0
  95. package/dist/nature/state.js +22 -0
  96. package/dist/nature/types.d.ts +124 -0
  97. package/dist/nature/types.js +1 -0
  98. package/dist/nature/validation.d.ts +10 -0
  99. package/dist/nature/validation.js +117 -0
  100. package/dist/nature.d.ts +8 -0
  101. package/dist/nature.js +7 -0
  102. package/dist/stone-collapse/content-data.d.ts +14218 -0
  103. package/dist/stone-collapse/content-data.js +25217 -0
  104. package/dist/stone-collapse/content-types.d.ts +77 -0
  105. package/dist/stone-collapse/content-types.js +1 -0
  106. package/dist/stone-collapse/content.d.ts +11 -0
  107. package/dist/stone-collapse/content.js +28 -0
  108. package/dist/stone-collapse/engine.d.ts +19 -0
  109. package/dist/stone-collapse/engine.js +570 -0
  110. package/dist/stone-collapse/persistence.d.ts +7 -0
  111. package/dist/stone-collapse/persistence.js +230 -0
  112. package/dist/stone-collapse/types.d.ts +216 -0
  113. package/dist/stone-collapse/types.js +1 -0
  114. package/dist/stone-collapse/validation.d.ts +21 -0
  115. package/dist/stone-collapse/validation.js +135 -0
  116. package/dist/stone-collapse.d.ts +6 -0
  117. package/dist/stone-collapse.js +4 -0
  118. package/package.json +96 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Add original Falling Triplets, Colour Chains, Stone Collapse and Gem Swap engines with immutable state, seeded play, validated settings and canonical saves.
6
+ - Include 300 graded challenges and three bilingual lessons per game.
7
+ - Provide family-style keyboard, keypad and touch demos, configurable boards, stored tools, goal progress and shared animation preferences.
8
+ - Add optional Black Hole powers, Shizen magnetic stones and bounded rebound, and optional Arashi jumble/lightning schedules.
9
+ - Include an experimental Magnetic Blocks engine with bonded squares, magnetic floor schedules, a Floor Switch and impact drops.
10
+ - Publish typed entry points, a generated API reference, MIT license and reproducible package checks.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Morris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,80 @@
1
- # Temporary Holding Version
1
+ # Houseki · 宝石
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![Checks](https://github.com/johnmorrisdotca/houseki/actions/workflows/check.yml/badge.svg)](https://github.com/johnmorrisdotca/houseki/actions/workflows/check.yml) [![MIT licence](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
4
+
5
+ Original gem and stone puzzle games with immutable TypeScript rules and a shared English/Japanese player.
6
+
7
+ **Initial release:** four playable games with tested rules, campaigns and demos. Physical-device feel, fluent Japanese copy and human difficulty review remain ongoing. Magnetic Blocks and nature variants are experimental. Website integration is maintained separately.
8
+
9
+ | Game | Distinctive mechanic |
10
+ | --- | --- |
11
+ | Falling Triplets | Cycle a falling vertical triplet; clear horizontal, vertical and diagonal lines |
12
+ | Stone Collapse | Select connected groups; plan removal order and column compression |
13
+ | Colour Chains | Rotate falling pairs; build connected groups and cascading chains |
14
+ | Gem Swap | Swap neighbours; create and combine specials to complete objectives |
15
+
16
+ An optional experimental `magnetic-blocks` entry point provides bonded 2×2 squares, magnetic floor schedules, a one-use Floor Switch and impact drops. [Play its demo](https://johnmorrisdotca.github.io/houseki/blocks.html). It has no graded campaign yet. The `nature` entry point exposes deterministic attraction, rebound and environment utilities.
17
+
18
+ ![Falling Triplets demo](https://raw.githubusercontent.com/johnmorrisdotca/houseki/main/docs/images/falling-triplets-desktop.png)
19
+
20
+ ![Colour Chains demo](https://raw.githubusercontent.com/johnmorrisdotca/houseki/main/docs/images/colour-chains-desktop.png)
21
+
22
+ ![Gem Swap demo](https://raw.githubusercontent.com/johnmorrisdotca/houseki/main/docs/images/gem-swap-desktop.png)
23
+
24
+ ![Stone Collapse demo](https://raw.githubusercontent.com/johnmorrisdotca/houseki/main/docs/images/stone-collapse-desktop.png)
25
+
26
+ [API reference](https://johnmorrisdotca.github.io/houseki/api.html) is generated locally by `pnpm site`; the hosted page becomes available after deployment. [Incremental draft PR](https://github.com/johnmorrisdotca/houseki/pull/1).
27
+
28
+ ## Design and progression
29
+
30
+ The [design pack](https://github.com/johnmorrisdotca/houseki/blob/main/docs/design/README.md) specifies rules, controls, materials, accessibility, generation, grading, persistence and acceptance checks. [Level generation](https://github.com/johnmorrisdotca/houseki/blob/main/docs/design/LEVEL-GENERATION.md) follows a deliberate complete count and measured progression: generate and prove original boards, remove duplicates, grade player-facing decisions, then sort and number from easy to hard. Stable content IDs preserve saved progress across future ordering changes.
31
+
32
+ The shared player uses the family's ivory, brass and felt palette, permanent colour symbols and configurable boards. Each set of three interactive lessons is separate from the graded campaign. Physical-device feel testing and fluent Japanese copy review are recorded independently from automated checks.
33
+
34
+ ## Local development
35
+
36
+ Requires Node.js 22 or newer and pnpm.
37
+
38
+ ```sh
39
+ pnpm install
40
+ pnpm build
41
+ pnpm test
42
+ pnpm demo
43
+ ```
44
+
45
+ The local demo instructions print its address. The first page plays Falling Triplets; `chains.html` plays Colour Chains; `tools.html` plays Gem Swap and Stone Collapse with optional stored-tool trays. The campaigns contain 100 Falling Triplets, 50 Colour Chains, 100 Stone Collapse and 50 Gem Swap challenges, ordered by measured difficulty. Finite goals show progress and remaining pieces or moves. All players share the persisted default-on Animation setting and respect reduced motion. See [CONTRIBUTING.md](https://github.com/johnmorrisdotca/houseki/blob/main/CONTRIBUTING.md) for contribution conventions and [the implementation plan](https://github.com/johnmorrisdotca/houseki/blob/main/docs/design/IMPLEMENTATION-PLAN.md) for bounded review milestones.
46
+
47
+ ## Engine example
48
+
49
+ The currently reviewed Falling Triplets entry point is independent of DOM, storage and network access:
50
+
51
+ ```ts
52
+ import { createGame, applyAction, advanceTicks } from '@johnmorrisdotca/houseki/falling-triplets';
53
+
54
+ let game = createGame({ mode: 'relaxed', seed: 'first-game' });
55
+ game = applyAction(game, { kind: 'cycle-forward' }).state;
56
+ game = applyAction(game, { kind: 'place' }).state;
57
+ game = advanceTicks(game, 22).state;
58
+ ```
59
+
60
+ The package exposes an entry point for each game. The generated API reference lists every exported function, type and setting.
61
+
62
+ ## Licence
63
+
64
+ [MIT](LICENSE), copyright John Morris. Original game content and presentation are maintained in this repository. Shared family files preserve their existing copyright notices. Report vulnerabilities through [SECURITY.md](https://github.com/johnmorrisdotca/houseki/blob/main/SECURITY.md).
65
+
66
+ ## Stored tools
67
+
68
+ [Stored-tool rules](https://github.com/johnmorrisdotca/houseki/blob/main/docs/design/STORED-TOOLS.md) distinguish inventory from special gems on the board. Gem Swap offers Bomb, Row clear and Colour clear; Stone Collapse offers Bomb and Pick. Select a tool and an occupied square, inspect the highlighted effect, then confirm or cancel. Ordinary clears earn capped inventory; using tools marks the run assisted. Daily and existing authored challenges keep their declared tool-free rules. Gem Swap also offers an optional earned [Black Hole](https://github.com/johnmorrisdotca/houseki/blob/main/docs/design/BLACK-HOLE.md): consume nearby gems with a visibly shrinking capacity and bounded lifetime. Enable it before starting a casual board.
69
+
70
+ ## Package entry points
71
+
72
+ Import a game directly from `@johnmorrisdotca/houseki/falling-triplets`, `/stone-collapse`, `/colour-chains` or `/gem-swap`. The root entry groups the same APIs as `fallingTriplets`, `stoneCollapse`, `colourChains` and `gemSwap` namespaces. Engines do not depend on the DOM or external services.
73
+
74
+ ```ts
75
+ import { colourChains } from '@johnmorrisdotca/houseki';
76
+ const firstChallenge = colourChains.createLevel(1);
77
+ const objective = firstChallenge.settings.goal;
78
+ ```
79
+
80
+ Run `npm run check:package` to build, pack and install the actual tarball into a temporary consumer and check every game entry point. `npm run test:browser` verifies desktop and phone player controls, witnessed campaign wins, progress, tool use, persistence of animation preferences and reduced motion.
@@ -0,0 +1,3 @@
1
+ import type { ChallengeOptions, GameState } from './types.js';
2
+ /** Validates and creates a finite witnessed challenge. Hints use only the supplied verified pair placements. */
3
+ export declare function createChallenge(options: ChallengeOptions): GameState;
@@ -0,0 +1,120 @@
1
+ import { applyAction, advanceTicks, initialChallengeState } from './engine.js';
2
+ import { StoneChainsOptionsError, validateChallengeGoal, validateSize } from './validation.js';
3
+ const COLOUR_NAMES = ['red', 'blue', 'green', 'gold', 'purple', 'teal'];
4
+ const ORIENTATIONS = ['up', 'right', 'down', 'left'];
5
+ function stable(value) {
6
+ if (Array.isArray(value))
7
+ return `[${value.map(stable).join(',')}]`;
8
+ if (value && typeof value === 'object')
9
+ return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stable(item)}`).join(',')}}`;
10
+ return JSON.stringify(value) ?? 'null';
11
+ }
12
+ function solveWitness(start, witness) {
13
+ let state = start;
14
+ let work = 0;
15
+ const act = (kind) => {
16
+ const result = applyAction(state, { kind });
17
+ if (!result.accepted)
18
+ throw new StoneChainsOptionsError('invalid-witness', `Witness action ${kind} was rejected: ${result.reason}`);
19
+ state = result.state;
20
+ if (++work > 100_000)
21
+ throw new StoneChainsOptionsError('witness-work-limit', 'Challenge witness exceeds bounded verification work');
22
+ };
23
+ for (let stepIndex = 0; stepIndex < witness.length; stepIndex++) {
24
+ const step = witness[stepIndex];
25
+ if (state.phase !== 'falling' || !state.active)
26
+ break;
27
+ const rotateToward = (target) => {
28
+ for (let count = 0; state.active.orientation !== target && count < 4; count++)
29
+ act('rotate-clockwise');
30
+ if (state.active.orientation !== target)
31
+ throw new StoneChainsOptionsError('invalid-witness', `Witness step ${stepIndex + 1} cannot reach its orientation`);
32
+ };
33
+ for (let count = 0; state.active.pivot.x !== step.pivotX && count < 12; count++)
34
+ act(state.active.pivot.x < step.pivotX ? 'right' : 'left');
35
+ if (state.active.pivot.x !== step.pivotX)
36
+ throw new StoneChainsOptionsError('invalid-witness', `Witness step ${stepIndex + 1} cannot reach its pivot column`);
37
+ rotateToward(step.orientation);
38
+ for (let count = 0; state.active.pivot.x !== step.pivotX && count < 12; count++)
39
+ act(state.active.pivot.x < step.pivotX ? 'right' : 'left');
40
+ if (state.active.pivot.x !== step.pivotX)
41
+ throw new StoneChainsOptionsError('invalid-witness', `Witness step ${stepIndex + 1} cannot finish at its pivot column`);
42
+ act('hard-drop');
43
+ while (['clear-mark', 'clear-remove', 'gravity'].includes(state.phase) && work < 100_000) {
44
+ const result = advanceTicks(state, 3600);
45
+ state = result.state;
46
+ work += 3600;
47
+ }
48
+ if (state.phase === 'lost' || state.phase === 'finished')
49
+ throw new StoneChainsOptionsError('invalid-witness', `Witness ends before its goal after pair ${stepIndex + 1}`);
50
+ if (state.phase === 'won') {
51
+ if (stepIndex !== witness.length - 1)
52
+ throw new StoneChainsOptionsError('witness-after-goal', 'Witness must stop at the first completed goal');
53
+ return state;
54
+ }
55
+ }
56
+ if (state.phase !== 'won')
57
+ throw new StoneChainsOptionsError('witness-does-not-win', 'Witness does not complete the challenge goal within its finite queue');
58
+ return state;
59
+ }
60
+ /** Validates and creates a finite witnessed challenge. Hints use only the supplied verified pair placements. */
61
+ export function createChallenge(options) {
62
+ if (!options || typeof options !== 'object' || Array.isArray(options))
63
+ throw new StoneChainsOptionsError('invalid-challenge', 'Challenge options must be an object');
64
+ const allowed = ['id', 'width', 'height', 'colourCount', 'seed', 'board', 'queue', 'goal', 'witness'];
65
+ if (Object.keys(options).some(key => !allowed.includes(key)))
66
+ throw new StoneChainsOptionsError('unknown-challenge-option', 'Challenge options contain unsupported fields');
67
+ if (typeof options.id !== 'string' || !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(options.id))
68
+ throw new StoneChainsOptionsError('invalid-challenge-id', 'Challenge ID must be stable lowercase text');
69
+ const width = options.width ?? 6;
70
+ const height = options.height ?? 12;
71
+ validateSize(width, height);
72
+ const colourCount = options.colourCount ?? 4;
73
+ if (colourCount !== 4 && colourCount !== 5 && colourCount !== 6)
74
+ throw new StoneChainsOptionsError('invalid-colour-count', 'Challenge colour count must be 4, 5 or 6');
75
+ const seed = options.seed ?? `challenge:${options.id}`;
76
+ if (typeof seed !== 'string' || seed.length < 1 || seed.length > 200)
77
+ throw new StoneChainsOptionsError('invalid-seed', 'Seed must contain 1–200 characters');
78
+ if (!Array.isArray(options.board) || options.board.length !== width * height)
79
+ throw new StoneChainsOptionsError('invalid-challenge-board', 'Challenge board must contain width × height visible cells');
80
+ if (!Array.isArray(options.queue) || options.queue.length < 2 || options.queue.length > 12)
81
+ throw new StoneChainsOptionsError('invalid-challenge-queue', 'Challenge queue must contain 2–12 pairs');
82
+ const allowedColours = COLOUR_NAMES.slice(0, colourCount);
83
+ const ids = new Set();
84
+ const board = options.board.map((gem, index) => {
85
+ if (gem === null)
86
+ return null;
87
+ if (!gem || typeof gem !== 'object' || Array.isArray(gem) || Object.keys(gem).some(key => key !== 'id' && key !== 'colour') || !Number.isSafeInteger(gem.id) || gem.id < 1 || gem.id > 0x7fff_ffff || ids.has(gem.id) || !allowedColours.includes(gem.colour))
88
+ throw new StoneChainsOptionsError('invalid-challenge-gem', `Invalid challenge gem at cell ${index}`);
89
+ ids.add(gem.id);
90
+ return Object.freeze({ id: gem.id, colour: gem.colour });
91
+ });
92
+ for (let x = 0; x < width; x++) {
93
+ let gap = false;
94
+ for (let y = height - 1; y >= 0; y--) {
95
+ if (board[y * width + x] === null)
96
+ gap = true;
97
+ else if (gap)
98
+ throw new StoneChainsOptionsError('unstable-challenge-board', `Starting column ${x} is not gravity-stable`);
99
+ }
100
+ }
101
+ const queue = options.queue.map((pair, index) => {
102
+ if (!Array.isArray(pair) || pair.length !== 2 || pair.some(colour => !allowedColours.includes(colour)))
103
+ throw new StoneChainsOptionsError('invalid-challenge-pair', `Invalid queue pair ${index + 1}`);
104
+ return Object.freeze([pair[0], pair[1]]);
105
+ });
106
+ const goal = validateChallengeGoal(options.goal, ids);
107
+ if (!Array.isArray(options.witness) || options.witness.length < 1 || options.witness.length > queue.length)
108
+ throw new StoneChainsOptionsError('invalid-witness', 'A challenge requires a complete witness of 1–12 pair placements');
109
+ const witness = options.witness.map((step, index) => {
110
+ if (!step || typeof step !== 'object' || Array.isArray(step) || Object.keys(step).some(key => key !== 'pivotX' && key !== 'orientation') || !Number.isInteger(step.pivotX) || step.pivotX < 0 || step.pivotX >= width || !ORIENTATIONS.includes(step.orientation))
111
+ throw new StoneChainsOptionsError('invalid-witness-step', `Invalid witness placement ${index + 1}`);
112
+ return Object.freeze({ pivotX: step.pivotX, orientation: step.orientation });
113
+ });
114
+ const settings = Object.freeze({ mode: 'challenge', width, height, colourCount, seed, challengeId: options.id, goal, initialBoard: Object.freeze(board), queue: Object.freeze(queue), witness: Object.freeze(witness) });
115
+ const state = initialChallengeState(settings, settings.initialBoard, settings.queue);
116
+ const solved = solveWitness(state, witness);
117
+ if (solved.phase !== 'won' || stable(solved.settings) !== stable(settings))
118
+ throw new StoneChainsOptionsError('invalid-witness', 'Challenge witness validation failed');
119
+ return state;
120
+ }
@@ -0,0 +1,2 @@
1
+ import type { ChainContentData } from './content-types.js';
2
+ export declare const contentData: ChainContentData;