@kradle/challenges-sdk 0.12.0 → 0.13.1

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 (85) hide show
  1. package/CHANGELOG.md +67 -1
  2. package/LLM_README.md +285 -192
  3. package/MIGRATION-0.13.md +109 -0
  4. package/README.md +97 -88
  5. package/dist/engine.d.ts +8 -0
  6. package/dist/engine.d.ts.map +1 -0
  7. package/dist/engine.js +17 -0
  8. package/dist/engine.js.map +1 -0
  9. package/dist/index.d.ts +19 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +42 -16
  12. package/dist/index.js.map +1 -1
  13. package/dist/runtime/builder.d.ts +32 -0
  14. package/dist/runtime/builder.d.ts.map +1 -0
  15. package/dist/runtime/builder.js +357 -0
  16. package/dist/runtime/builder.js.map +1 -0
  17. package/dist/runtime/errors.d.ts +61 -0
  18. package/dist/runtime/errors.d.ts.map +1 -0
  19. package/dist/runtime/errors.js +71 -0
  20. package/dist/runtime/errors.js.map +1 -0
  21. package/dist/runtime/reducer.d.ts +205 -0
  22. package/dist/runtime/reducer.d.ts.map +1 -0
  23. package/dist/runtime/reducer.js +2025 -0
  24. package/dist/runtime/reducer.js.map +1 -0
  25. package/dist/runtime/rules.d.ts +29 -0
  26. package/dist/runtime/rules.d.ts.map +1 -0
  27. package/dist/runtime/rules.js +23 -0
  28. package/dist/runtime/rules.js.map +1 -0
  29. package/dist/runtime/sandbox.d.ts +2 -1
  30. package/dist/runtime/sandbox.d.ts.map +1 -1
  31. package/dist/runtime/sandbox.js +7 -4
  32. package/dist/runtime/sandbox.js.map +1 -1
  33. package/dist/runtime/time.d.ts +33 -0
  34. package/dist/runtime/time.d.ts.map +1 -0
  35. package/dist/runtime/time.js +53 -0
  36. package/dist/runtime/time.js.map +1 -0
  37. package/dist/runtime/types.d.ts +963 -0
  38. package/dist/runtime/types.d.ts.map +1 -0
  39. package/dist/runtime/types.js +25 -0
  40. package/dist/runtime/types.js.map +1 -0
  41. package/dist/runtime/wire.d.ts +858 -0
  42. package/dist/runtime/wire.d.ts.map +1 -0
  43. package/dist/runtime/wire.js +28 -0
  44. package/dist/runtime/wire.js.map +1 -0
  45. package/dist/runtime/world.d.ts +58 -3
  46. package/dist/runtime/world.d.ts.map +1 -1
  47. package/dist/runtime/world.js +12 -0
  48. package/dist/runtime/world.js.map +1 -1
  49. package/dist/structures.d.ts +119 -0
  50. package/dist/structures.d.ts.map +1 -0
  51. package/dist/structures.js +279 -0
  52. package/dist/structures.js.map +1 -0
  53. package/dist/test/harness.d.ts +133 -29
  54. package/dist/test/harness.d.ts.map +1 -1
  55. package/dist/test/harness.js +191 -66
  56. package/dist/test/harness.js.map +1 -1
  57. package/dist/test/helpers.d.ts +3 -2
  58. package/dist/test/helpers.d.ts.map +1 -1
  59. package/dist/test/helpers.js +2 -2
  60. package/dist/test/helpers.js.map +1 -1
  61. package/package.json +30 -7
  62. package/dist/runtime/examples/capture-the-flag.d.ts +0 -33
  63. package/dist/runtime/examples/capture-the-flag.d.ts.map +0 -1
  64. package/dist/runtime/examples/capture-the-flag.js +0 -79
  65. package/dist/runtime/examples/capture-the-flag.js.map +0 -1
  66. package/dist/runtime/examples/climb.d.ts +0 -34
  67. package/dist/runtime/examples/climb.d.ts.map +0 -1
  68. package/dist/runtime/examples/climb.js +0 -54
  69. package/dist/runtime/examples/climb.js.map +0 -1
  70. package/dist/runtime/examples/four-bridges.d.ts +0 -107
  71. package/dist/runtime/examples/four-bridges.d.ts.map +0 -1
  72. package/dist/runtime/examples/four-bridges.js +0 -294
  73. package/dist/runtime/examples/four-bridges.js.map +0 -1
  74. package/dist/runtime/examples/herding.d.ts +0 -59
  75. package/dist/runtime/examples/herding.d.ts.map +0 -1
  76. package/dist/runtime/examples/herding.js +0 -181
  77. package/dist/runtime/examples/herding.js.map +0 -1
  78. package/dist/runtime/examples/skywars.d.ts +0 -116
  79. package/dist/runtime/examples/skywars.d.ts.map +0 -1
  80. package/dist/runtime/examples/skywars.js +0 -253
  81. package/dist/runtime/examples/skywars.js.map +0 -1
  82. package/dist/runtime/sdk.d.ts +0 -1038
  83. package/dist/runtime/sdk.d.ts.map +0 -1
  84. package/dist/runtime/sdk.js +0 -1271
  85. package/dist/runtime/sdk.js.map +0 -1
@@ -0,0 +1,109 @@
1
+ # Migration to 0.13: from the object spec to the builder
2
+
3
+ 0.13 replaces `defineChallenge(config, spec)` with one object of handlers by a root declaration and steps. This guide maps the old API to the new one. The rules of a challenge do not change; where they run does.
4
+
5
+ ## The shape
6
+
7
+ ```ts
8
+ // no-compile
9
+ // 0.12
10
+ export default defineChallenge(config, {
11
+ life: "respawn",
12
+ duration: minutes(2),
13
+ player: () => ({ pigs: 0 }),
14
+ start(g) { ... },
15
+ on: { entityRemoved(g, e) { ... } },
16
+ end: (g) => ...,
17
+ winners: Win.scoreAtLeast(2),
18
+ onOver(g, { winners }) { ... },
19
+ });
20
+
21
+ // 0.13
22
+ const challenge = defineChallenge(config, {
23
+ state: { player: () => ({ pigs: 0 }) },
24
+ life: "respawn",
25
+ duration: "2m",
26
+ });
27
+ export default challenge
28
+ .start((g) => { ... })
29
+ .on({ entityRemoved(g, e) { ... } })
30
+ .outcome({ scores: (_g, p) => p.state.pigs, endWhen: (g) => ..., winners: winBy.scoreAtLeast(2) })
31
+ .onOver((g, result) => { ... });
32
+ ```
33
+
34
+ The root declaration holds data and the state initializers: `state`, `life`, `duration`, `world`, `zones`, `timers`, `votes` (with their options), `phases` (the names), `initialPhase`, `markers`. Every handler is a step.
35
+
36
+ ## The table
37
+
38
+ | 0.12 | 0.13 |
39
+ |---|---|
40
+ | `game: () => ({...})`, `player: () => ({...})`, `players: {...}` at the top level | `state: { game, player, players }`; the initializers receive `(init)` / `(p, init)` with `init = { params, random, players, locations }` and the observed player (`p.pos` is set) |
41
+ | `g.game.x`, `p.x` (custom fields on the player) | `g.state.x`, `p.state.x` |
42
+ | `p.score = n` | `g.setScore(p, n)` / `g.addScore(p, d)`, or `outcome.scores: (g, p) => ...` (derived after every event; then the writes are refused) |
43
+ | `end: (g) => bool` | `outcome.endWhen` |
44
+ | `winners`, `endState` | `outcome.winners`, `outcome.endState` |
45
+ | `Win.topScore()` | `winBy.highestScore()`. One default changed: every eligible player at score 0 wins (0.12 gave no winner below `min: 1`). The eligible set is the active players in both versions. `winBy.highestScore({ min: 1 })` keeps the old rule. `{ among: (g) => g.players }` is the form of an old custom `eligible: () => true`; it is not the old default. |
46
+ | `Win.scoreAtLeast(n)` | `winBy.scoreAtLeast(n)`. The eligible set is the active players now; `winBy.scoreAtLeast(n, { among: (g) => g.players })` keeps an eliminated player who reached `n`. |
47
+ | `Win.*({ eligible: (p) => ... })` | `{ among: (g) => g.players.filter((p) => ...) }` |
48
+ | `End.allEliminated` | `(g) => g.activePlayers.length === 0` |
49
+ | `onOver(g, { winners })` | `.onOver((g, result) => ...)`: a notification context (messages, metrics), `result.winners`, `result.endState`, `result.reason`, `result.isWinner(p)` |
50
+ | `judgeContext: (g) => ...` | `.judgeContext((g, result) => ...)` |
51
+ | `setup(g) {...}`, `start(g) {...}`, `at: [...]`, `every: [...]`, `on: {...}` | `.setup(fn)`, `.start(fn)`, `.at([...])`, `.every([...])`, `.on({...})` (several `.on()` calls add up) |
52
+ | `phases: { A: { duration, next, enter, exit, at, every, on } }` | `phases: ["A", "B"]` + `initialPhase` in the root; `.phase("A", { duration?, next, until?, enter?, exit?, at?, every?, on? })` per phase; `next` is required |
53
+ | `g.goTo("B")` | `g.startPhase("B")`; applied after the event's handlers returned |
54
+ | `g.goTo("END")`, `g.end(endState?)` | `g.endGame(endState?)`: an unconditional end. `g.endPhase()` is new: it follows the phase's `next`, which may be another phase |
55
+ | a phase without `duration` = unbounded | a phase without `duration` and `until` lasts one tick; use `until` or leave it manual (no `.phase()` step) |
56
+ | `g.active` | `g.activePlayers`; `p.isActive` |
57
+ | `g.role(r)`, `g.one(r)` | `g.playersWithRole(r)`, `g.playerWithRole(r)` (throws unless exactly one) |
58
+ | `p.number` | `p.seatNumber` |
59
+ | `g.others` | removed |
60
+ | `g.count(p, item)` | `g.countItems(p, item)` |
61
+ | `g.contents(name \| at)`, `containers: {...}` in the spec | `g.containerContents(at)` for any container; no declaration |
62
+ | `g.rand(lo, hi)`, `g.pick`, `g.shuffle` | `g.random.int(lo, hi)`, `g.random.pick`, `g.random.shuffle`, `g.random.weighted`; `init.random` in the initializers |
63
+ | `g.setblock(at, block, nbt?)` | `g.setBlock(at, block)` with `block` an id or `{ id, props?, blockEntity? }` |
64
+ | `g.give(p, item, count, nbt)` | `g.give(p, item, count?)` with `item` an id or `{ id, count?, name?, lore?, enchantments?, damage?, snbt? }` |
65
+ | `g.effect(p, id, seconds, amplifier)` | `g.effect(p, { id, duration, amplifier? })` |
66
+ | `g.title(to, title, subtitle, times)` | `g.title(to, { title, subtitle?, fadeIn?, stay?, fadeOut? })` |
67
+ | `g.log(player \| null, message, data)` | `g.log(message, { player?, data? })` |
68
+ | `g.store(name, value, player?)` | `g.setMetric(name, value, { player? })` (the wire action is `metric`) |
69
+ | `g.blockStateAt(at)` (a string) | `g.blockStateAt(at)` (`{ id, props }`); `g.blockMatches(at, match)`. The block events carry `{ id, props }` too, and a function `BlockMatch` receives it, not a string |
70
+ | `g.findBlocks(box, match, limit)` | `g.findBlocks(box, match, { limit })` |
71
+ | `g.summon(type, at, nbt)` | `g.summon(type, at, { nbt })` (a string third argument is ignored) |
72
+ | `g.worldBorder({ seconds: n })` | `g.worldBorder({ over: seconds(n) })` or `{ over: "10s" }` (`seconds` is ignored) |
73
+ | `g.container(at, [{ item, count, nbt, slot? }])` | `g.container(at, [{ id, count, snbt }])`: the items fill the slots in order; there is no slot selection |
74
+ | `p.slots[i].item` | `p.slots[i].id` |
75
+ | `onOver(g, { winnerPlayers })` | `result.isWinner(p)` over `g.players` |
76
+ | `g.dt` | removed: keep the last tick in the state and use `g.tick` differences |
77
+ | `g.phase === "END"` after the end | the last phase stays in `g.phase`; the end is `g.status === "OVER"` (`result.phase` names it) |
78
+ | `g.phaseRemaining` without a duration (`Number.MAX_SAFE_INTEGER`) | `null` |
79
+ | `g.blockCount`, `g.findBlocks` on an unloaded chunk (zero, empty) | they throw `block_unknown`; pass `{ partial: true }` for the old behavior; `g.regionLoaded(box)` |
80
+ | `votes: { room: { final, phases: [...], open } }` + `votingOptions` in `config.ts` | `votes: { room: { options: [...], final?, allowEliminated?, accept? } }` in the root; `g.openVote` / `g.closeVote` / `g.resetVote` in the phase; `votingOptions` leaves `config.ts` |
81
+ | `g.openVote(id)` (a reset) | `g.resetVote(id)`; `g.openVote(id)` opens a round (throws on an open one) |
82
+ | `on.move` with `maxY`, `minY` | `on.move` with `from`, `to` (one sample per tick) |
83
+ | `announce: { deaths, gameOver }`; the SDK's "=== GAME OVER ===" lines | removed: the SDK prints nothing; say it in `onOver` |
84
+ | `mirror: {...}` | removed: the mirror holds every loaded chunk |
85
+ | `effectFailed { kind, detail }` | `{ kind, detail, effect?, batch? }`; `g.batch(name, fn)` + `on.batchDone` |
86
+ | `ContextOf<typeof config, typeof state>`, `typeof challenge.ctx` | `CtxOf<typeof challenge>`, `ReadCtxOf`, `SetupCtxOf`, `PlayerIn<G>`, from the root const |
87
+ | `game.state.status === "LOBBY"` (in a test) | `game.status === "CONNECTING"` (the author context has no `status` field in 0.12; a rule reads `g.status` now) |
88
+ | `Game.begin(seed, roster)` without positions | `begin` supplies a position per participant (`at` to choose); `g.state` is the shared data, `g.reducerState` the whole state; `Fake.has` is `setInventory`; `g.all("store")` is `g.all("metric")` |
89
+
90
+ ## Helpers
91
+
92
+ Name the root call in a const and type the helpers from it. A helper typed from a whole chain expression and used inside that chain is circular.
93
+
94
+ ```ts
95
+ // no-compile
96
+ const challenge = defineChallenge(config, decl);
97
+ type G = CtxOf<typeof challenge>;
98
+ type R = ReadCtxOf<typeof challenge>;
99
+ const classify = (g: R) => (g.state.done ? "done" : "timeout");
100
+ export default challenge.start(begin).outcome({ endState: classify });
101
+ ```
102
+
103
+ A root predicate (`votes.room.accept`) cannot call a helper typed from the challenge: type it independently.
104
+
105
+ ## Checks the compiler now does
106
+
107
+ - A phase name, a zone, a timer, a vote id, an option or an end state that is not declared does not compile.
108
+ - A pure callback (`outcome.*`, `judgeContext`, `until`, a functional `duration` or `next`, `accept`) cannot write state or queue an effect.
109
+ - `p.score` is read-only; observations are read-only.
package/README.md CHANGED
@@ -21,7 +21,7 @@ kradle challenge run my-challenge # a run in the cloud
21
21
  ```
22
22
 
23
23
  A manual installation is one dependency: `npm install @kradle/challenges-sdk@next`
24
- (0.11 stays under the npm tag `next` until the CLI release installs it).
24
+ (the reducer SDK stays under the npm tag `next` until the CLI release installs it).
25
25
  Add `"type": "module"` to the project's `package.json`.
26
26
 
27
27
  ## Local development
@@ -42,120 +42,129 @@ Then run `kradle challenge test <slug>` in the consumer project.
42
42
 
43
43
  ## Writing a challenge
44
44
 
45
- One call. Every type is inferred from the initializers. Helper functions get
46
- their context type from the state object with `ContextOf`, before the
47
- challenge exists.
45
+ One root call with the data and the state initializers, then steps for the
46
+ behavior. Every type is inferred from the root: the phase names, the zones,
47
+ the timers, the vote options, the roles and the end states of the config.
48
+ Helpers take their context type from the root const with `CtxOf`.
48
49
 
49
50
  ```ts
50
- import { type ChallengeConfig, type ContextOf, defineChallenge, minutes, type PlayerIn, Win } from "@kradle/challenges-sdk";
51
+ import { type ChallengeConfig, type CtxOf, defineChallenge, type PlayerIn, winBy } from "@kradle/challenges-sdk";
51
52
 
52
53
  export const config = {
53
54
  name: "Hide and Seek",
54
55
  challengeConfig: {},
55
56
  roles: { hider: { description: "hides" }, seeker: { description: "seeks" } },
57
+ endStates: { all_found: "Every hider was found", time_up: "Time ran out" },
56
58
  } as const satisfies ChallengeConfig;
57
59
 
58
- const state = {
59
- game: () => ({ found: [] as string[] }),
60
- // State per role; `player` instead of `players` gives every role the same fields.
61
- players: { hider: () => ({ hiddenAt: null as number | null }), seeker: () => ({ finds: 0 }) },
62
- zones: { base: { from: { x: -5, y: 60, z: -5 }, to: { x: 5, y: 70, z: 5 } } },
63
- };
64
- type G = ContextOf<typeof config, typeof state>;
60
+ const challenge = defineChallenge(config, {
61
+ state: {
62
+ game: () => ({ found: [] as string[] }),
63
+ // State per role; `player` instead of `players` gives every role the same fields.
64
+ players: { hider: () => ({ hidden: false }), seeker: () => ({ tags: 0 }) },
65
+ },
66
+ duration: "5m",
67
+ life: "respawn",
68
+ world: { time: "day" },
69
+ zones: { base: { from: { x: -5, y: -60, z: -5 }, to: { x: 5, y: -55, z: 5 } } },
70
+ });
71
+ type G = CtxOf<typeof challenge>;
65
72
 
73
+ /** A helper: the seeker tags a hider. */
66
74
  function tag(g: G, seeker: PlayerIn<G>, hider: PlayerIn<G>): void {
67
- if (seeker.role !== "seeker" || hider.role !== "hider") return; // the role narrows the fields
68
- seeker.finds += 1;
69
- seeker.score = seeker.finds;
70
- g.game.found.push(hider.name);
71
- g.eliminate(hider);
75
+ if (seeker.role !== "seeker" || hider.role !== "hider") return;
76
+ seeker.state.tags += 1;
77
+ g.state.found.push(hider.name);
78
+ g.say(`${seeker.name} found ${hider.name}!`);
72
79
  }
73
80
 
74
- export default defineChallenge(config, {
75
- ...state,
76
- duration: minutes(3),
77
- life: "respawn",
78
- start(g) {
79
- for (const h of g.role("hider")) g.tell(h, "Hide! The seekers are released in 30 seconds.");
80
- },
81
- on: {
82
- zoneLeft(g, { player, zone }) {
83
- if (zone === "base" && player.role === "hider") player.hiddenAt = g.elapsed;
84
- },
85
- damaged(g, { player, attacker }) {
86
- if (attacker) tag(g, attacker, player);
87
- },
88
- },
89
- end: (g) => g.role("hider").every((h) => h.eliminated),
90
- winners: Win.topScore({ min: 1 }),
91
- });
81
+ export default challenge
82
+ .start((g) => {
83
+ for (const p of g.playersWithRole("hider")) g.give(p, "minecraft:ender_pearl", 2);
84
+ g.say("Hiders, run! Seekers start in 30 seconds.");
85
+ })
86
+ .at([{ at: "30s", run: (g) => g.say("Seekers, go!") }])
87
+ .on({
88
+ damaged: (g, { player, attacker }) => attacker && tag(g, attacker, player),
89
+ zoneEntered: (g, { player, zone }) => zone === "base" && g.tell(player, "You are safe here."),
90
+ })
91
+ .outcome({
92
+ scores: (_g, p) => (p.role === "seeker" ? p.state.tags : 0),
93
+ endWhen: (g) => g.state.found.length === g.playersWithRole("hider").length,
94
+ winners: winBy.highestScore({ among: (g) => g.playersWithRole("seeker") }),
95
+ endState: (g) => (g.state.found.length === g.playersWithRole("hider").length ? "all_found" : "time_up"),
96
+ });
92
97
  ```
93
98
 
94
- `@kradle/challenges-sdk` is the API. The concepts: roles, phases, timers, votes, end states, a judge. The state
95
- is plain JSON, and a run replays from its event log.
99
+ `p.state` is the challenge's; every other field of a player (`pos`, `health`,
100
+ `inventory`, `score`, ...) is an observation the SDK writes. The score changes
101
+ through `outcome.scores` (derived after every event) or `g.setScore` /
102
+ `g.addScore`, not both. Phases are declared by name in the root
103
+ (`phases: ["BUILD", "FIGHT"]`) and get their behavior in `.phase(name, {...})`
104
+ steps: a `duration`, a `next`, an `until` predicate, `enter`, `exit`, timed
105
+ steps and handlers. `g.startPhase`, `g.endPhase` and `g.endGame` apply after
106
+ the callback returns.
96
107
 
97
108
  ## Reading the world
98
109
 
99
- The rules read the world through a mirror the engine keeps up to date: every
100
- player (`g.players`, `g.others`, with `pos`, `yaw`, `pitch`, `headYaw`,
101
- `entityId`, `uuid`, `equipment`, `invisible`), every entity
102
- (`g.entities({ box, type, tag })`), every loaded chunk (`g.blockAt` for the id,
103
- `g.blockStateAt` for the full state, `g.blockCount`, `g.findBlocks`), the
104
- world time (`g.worldTime`), and each participant's inventory (`p.inventory`,
105
- `p.slots`, `g.count(p, item)`). Nothing is declared to the engine for that,
106
- except the chests a challenge reads: `containers: { delivery: { x, y, z } }` in
107
- the spec makes the engine report their contents; `g.contents("delivery")` gives
108
- the item counts and `on.container(g, { at, block, items, changes, by })` fires
109
- on every change. A report for an undeclared position is ignored.
110
- Zones are named boxes the SDK evaluates from the positions every tick;
111
- `g.inZone(p, box)` takes any box too. The `mirror` option narrows what the
112
- engine streams when a world is large. `src/runtime/examples/herding.ts` scores
113
- sheep inside a pen from the entity mirror and watches a fence gap through the
114
- block mirror.
115
-
116
- `typeof x.ctx` on a challenge is the same type; use it in tests. In the file that
117
- defines the challenge it works only for helpers called from inside a rule
118
- (`endState: (g) => classify(g)`): a helper passed as the property value
119
- (`endState: classify`) would make the challenge's type circular.
120
- `onOver(g, result)` runs once the result is final: `result.winners` (names), `result.winnerPlayers`, `result.isWinner(player)` and `result.endState`.
110
+ The engine mirrors every loaded chunk, every entity and every container into
111
+ the state; the SDK answers from that mirror:
121
112
 
122
- ## Testing
113
+ - `g.blockAt(at)` (the id), `g.blockStateAt(at)` (`{ id, props }`), `g.blockMatches(at, match)`
114
+ - `g.blockCount(box, match)`, `g.findBlocks(box, match)`: they throw when a chunk of the box is not loaded, unless `{ partial: true }`
115
+ - `g.entities({ box?, type?, tag? })`, `g.entity(id)`
116
+ - `g.containerContents(at)`: any chest or other container the mirror holds
117
+ - `p.pos`, `p.inventory`, `p.zones`; `g.inZone(p, zone)`, `g.countItems(p, item)`
123
118
 
124
- Test without Minecraft with `@kradle/challenges-sdk/test`. It exports `Game`, `Fake`, `sandboxed`, and two helpers: `scores(game)` (the score per player) and `near(a, b, blocks)` (a distance check on each axis, x and z):
119
+ The handlers report the changes: `blockPlaced`, `blockBroken`, `blocksChanged`
120
+ (every cause, a fill as one entry), `container`, `inventory`, `move`,
121
+ `zoneEntered`, `zoneLeft`, `entitySpawned`, `entityChanged`, `entityRemoved`.
125
122
 
126
- ```js
127
- const game = new Game(climb).begin(1, { alice: "climber", bob: "climber" });
128
- game.player("alice").moveTo({ y: -40 });
129
- game.at("2m");
130
- assert.deepEqual(game.state.winners, ["alice"]);
131
- game.replay();
123
+ ### Structures
124
+
125
+ `@kradle/challenges-sdk/structures` turns text layers and a palette into a
126
+ template. `placeStructure` builds it in the world; `compareStructure` reads
127
+ the mirror and reports the cells that match, mismatch or sit in an unloaded
128
+ chunk; `rotation: "any"` finds the orientation the players chose.
129
+
130
+ ```ts
131
+ import { compareStructure, defineStructure, type StructureReads } from "@kradle/challenges-sdk/structures";
132
+
133
+ const PILLAR = defineStructure({
134
+ layers: [["#"], ["#"], ["T"]],
135
+ palette: { "#": { place: "minecraft:stone_bricks" }, T: { place: "minecraft:torch" } },
136
+ });
137
+ export const pillarDone = (g: StructureReads, at: { x: number; y: number; z: number }) => compareStructure(g, PILLAR, at).complete;
132
138
  ```
133
139
 
134
- The arena runs the reducer inside a SES compartment: no clock, no random, no
135
- timers, no `process`, no network. The harness alone does not apply those limits.
136
- `kradle challenge test <slug>` builds the challenge and runs its tests against the
137
- built bundle inside that sandbox. Wrap the import with `sandboxed()`: the test
138
- then uses the bundle under the command, and the plain import elsewhere.
140
+ ## Testing
139
141
 
140
- ```js
141
- import { Game, sandboxed } from "@kradle/challenges-sdk/test";
142
- import climbSource from "./challenge.ts";
142
+ `@kradle/challenges-sdk/test` is a stand-in arena. A test feeds events, reads
143
+ the actions, and checks the state; `replay()` proves determinism.
143
144
 
144
- const climb = await sandboxed(climbSource);
145
+ ```ts
146
+ import { describe, expect, test } from "bun:test";
147
+ import { Game, sandboxed, scores } from "@kradle/challenges-sdk/test";
148
+ import source from "./challenge";
149
+
150
+ const challenge = await sandboxed(source); // the bundle under `kradle challenge test`, the source otherwise
151
+
152
+ test("a tag scores", () => {
153
+ const g = new Game(challenge).begin(1, { Alice: "seeker", Bob: "hider" });
154
+ g.player("Bob").hurt(1, "player", "Alice");
155
+ expect(scores(g)).toEqual({ Alice: 1, Bob: 0 });
156
+ expect(g.said()).toContain("Alice found Bob!");
157
+ expect(g.result()?.endState).toBe("all_found");
158
+ g.replay();
159
+ });
145
160
  ```
146
161
 
147
- `sandboxed()` reads `KRADLE_CHALLENGE_BUNDLE`, the bundle path the command sets,
148
- and refuses a bundle that holds a different challenge name. It is async (`ses`
149
- loads as an ES module), so await it at the top of the test file. A rule that calls
150
- `Date.now()` or `Math.random()` passes in the plain harness and fails under the
151
- command, as it fails in the arena; use `g.tick`, `g.elapsed`, `g.rand()` and
152
- `g.schedule()` instead. The loader itself is
153
- `@kradle/challenges-sdk/runtime/sandbox` (`lockdownOnce()`, `loadBundle()`,
154
- `loadBundleFile()`) for a test runner of your own. Call it in a dedicated
155
- process: the lockdown changes the intrinsics of the whole process. Under the
156
- command a `Game` on the imported source throws: the command wants the bundle,
157
- and it counts the Games built on it (`KRADLE_CHALLENGE_EVIDENCE`) as its proof
158
- that the rules ran inside the sandbox.
162
+ `Game.begin` supplies a position per participant (or takes `at`), sends the
163
+ joins and confirms the setup batches. `g.player(name)` scripts a player
164
+ (`moveTo`, `says`, `vote`, `hurt`, `dies`, `setInventory`, `places`, ...); the
165
+ game feeds the mirror (`terrain`, `setBlocks`, `spawnEntity`, `container`) and
166
+ answers the engine's side (`walkResult`, `batchResult`). `kradle challenge test
167
+ <slug>` runs the same file against the built bundle under the arena's sandbox.
159
168
 
160
169
  ## Build and run
161
170
 
@@ -0,0 +1,8 @@
1
+ export type { AnyChallenge } from "./runtime/builder.js";
2
+ export { ChallengeError, type ErrorCategory, categoryOf } from "./runtime/errors.js";
3
+ export { MAX_BATCH_EFFECTS, MAX_FILL_VOLUME, type State, assertJson, parseBlockState, splitBatches } from "./runtime/reducer.js";
4
+ export { type Duration, type Ticks, toTicks } from "./runtime/time.js";
5
+ export type { Action, BlockId, Box, Capability, ContainerItem, Effect, EffectId, EndReason, EngineChallenge, EntityTypeId, Event, GameMode, ItemId, ItemMeta, ItemStack, Json, ManifestVote, Marker, Participant, PlayerSample, RuntimeManifest, SdkEndState, SlotChange, Status, TitleTimes, Vec3, } from "./runtime/wire.js";
6
+ export { ALL_CAPABILITIES } from "./runtime/wire.js";
7
+ export type { ChunkMessage, Entity, EntityMove, Equipment, EquipmentChange, EquipmentSlot, Equipped, WorldState } from "./runtime/world.js";
8
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,KAAK,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjI,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACvE,YAAY,EACX,MAAM,EACN,OAAO,EACP,GAAG,EACH,UAAU,EACV,aAAa,EACb,MAAM,EACN,QAAQ,EACR,SAAS,EACT,eAAe,EACf,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,WAAW,EACX,YAAY,EACZ,eAAe,EACf,WAAW,EACX,UAAU,EACV,MAAM,EACN,UAAU,EACV,IAAI,GACJ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/engine.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALL_CAPABILITIES = exports.toTicks = exports.splitBatches = exports.parseBlockState = exports.assertJson = exports.MAX_FILL_VOLUME = exports.MAX_BATCH_EFFECTS = exports.categoryOf = exports.ChallengeError = void 0;
4
+ var errors_js_1 = require("./runtime/errors.js");
5
+ Object.defineProperty(exports, "ChallengeError", { enumerable: true, get: function () { return errors_js_1.ChallengeError; } });
6
+ Object.defineProperty(exports, "categoryOf", { enumerable: true, get: function () { return errors_js_1.categoryOf; } });
7
+ var reducer_js_1 = require("./runtime/reducer.js");
8
+ Object.defineProperty(exports, "MAX_BATCH_EFFECTS", { enumerable: true, get: function () { return reducer_js_1.MAX_BATCH_EFFECTS; } });
9
+ Object.defineProperty(exports, "MAX_FILL_VOLUME", { enumerable: true, get: function () { return reducer_js_1.MAX_FILL_VOLUME; } });
10
+ Object.defineProperty(exports, "assertJson", { enumerable: true, get: function () { return reducer_js_1.assertJson; } });
11
+ Object.defineProperty(exports, "parseBlockState", { enumerable: true, get: function () { return reducer_js_1.parseBlockState; } });
12
+ Object.defineProperty(exports, "splitBatches", { enumerable: true, get: function () { return reducer_js_1.splitBatches; } });
13
+ var time_js_1 = require("./runtime/time.js");
14
+ Object.defineProperty(exports, "toTicks", { enumerable: true, get: function () { return time_js_1.toTicks; } });
15
+ var wire_js_1 = require("./runtime/wire.js");
16
+ Object.defineProperty(exports, "ALL_CAPABILITIES", { enumerable: true, get: function () { return wire_js_1.ALL_CAPABILITIES; } });
17
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":";;;AAGA,iDAAqF;AAA5E,2GAAA,cAAc,OAAA;AAAsB,uGAAA,UAAU,OAAA;AACvD,mDAAiI;AAAxH,+GAAA,iBAAiB,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAc,wGAAA,UAAU,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,0GAAA,YAAY,OAAA;AAClG,6CAAuE;AAAnC,kGAAA,OAAO,OAAA;AA6B3C,6CAAqD;AAA5C,2GAAA,gBAAgB,OAAA"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,20 @@
1
- export * from "./runtime/sdk.js";
1
+ export { type AnyChallenge, type Challenge, defineChallenge } from "./runtime/builder.js";
2
+ export { ChallengeError, type ErrorCategory } from "./runtime/errors.js";
3
+ export { MAX_BATCH_EFFECTS, MAX_FILL_VOLUME, MAX_TRANSITIONS_PER_EVENT } from "./runtime/reducer.js";
4
+ export { winBy } from "./runtime/rules.js";
5
+ export { addDuration, asMinutes, asSeconds, type Duration, minutes, seconds, subtractDuration, TICKS_PER_SECOND, type Ticks, ticks, toTicks, } from "./runtime/time.js";
6
+ export type { BlockMatch, BlockPos, BlockSpec, BlockState, ChallengeApi, ChallengeConfig, Ctx, CtxOf, Decl, DeepReadonly, EndStateOf, Handlers, Init, ItemMatch, ItemRequest, LocationOf, NotifyCtx, NotifyCtxOf, ObservedPlayer, OptionFor, Outcome, ParamSpec, ParamsOf, PhaseSpec, PhaseStep, Player, PlayerIn, PlayerOf, Random, ReadCtx, ReadCtxOf, ReadPlayer, ReadPlayerOf, Result, RoleOf, Roster, SetupCtx, SetupCtxOf, StateDecl, TitleOptions, To, VoteIdOf, VoteRule, World, } from "./runtime/types.js";
7
+ export { PLAYER_VOTE, WORLD_DEFAULTS } from "./runtime/types.js";
8
+ export type { BlockId, Box, EffectId, EndReason, EntityTypeId, GameMode, ItemId, ItemMeta, ItemStack, Json, Marker, SdkEndState, Status, Vec3, } from "./runtime/wire.js";
9
+ export { blockIdOf, bounds, type Entity, type EntityFilter, type Equipment, inBox } from "./runtime/world.js";
10
+ import type { BlockPos } from "./runtime/types.js";
11
+ import type { Vec3 } from "./runtime/wire.js";
12
+ /** The integer block position of a continuous position: each axis floored. */
13
+ export declare const toBlockPos: (v: Vec3) => BlockPos;
14
+ /** The position one block under `at`. On a slab or in water this is not the supporting block. */
15
+ export declare const below: (at: Vec3) => BlockPos;
16
+ /** The position one block over `at`. */
17
+ export declare const above: (at: Vec3) => BlockPos;
18
+ /** `at` moved by `d` on each axis. */
19
+ export declare const offset: (at: Vec3, d: Partial<Vec3>) => Vec3;
2
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EACN,WAAW,EACX,SAAS,EACT,SAAS,EACT,KAAK,QAAQ,EACb,OAAO,EACP,OAAO,EACP,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,KAAK,EACV,KAAK,EACL,OAAO,GACP,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACX,UAAU,EACV,QAAQ,EACR,SAAS,EACT,UAAU,EACV,YAAY,EACZ,eAAe,EACf,GAAG,EACH,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,EACX,cAAc,EACd,SAAS,EACT,OAAO,EACP,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,SAAS,EACT,UAAU,EACV,YAAY,EACZ,MAAM,EACN,MAAM,EACN,MAAM,EACN,QAAQ,EACR,UAAU,EACV,SAAS,EACT,YAAY,EACZ,EAAE,EACF,QAAQ,EACR,QAAQ,EACR,KAAK,GACL,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EACX,OAAO,EACP,GAAG,EACH,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,MAAM,EACN,WAAW,EACX,MAAM,EACN,IAAI,GACJ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE9G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,8EAA8E;AAC9E,eAAO,MAAM,UAAU,GAAI,GAAG,IAAI,KAAG,QAA4E,CAAC;AAClH,iGAAiG;AACjG,eAAO,MAAM,KAAK,GAAI,IAAI,IAAI,KAAG,QAAmF,CAAC;AACrH,wCAAwC;AACxC,eAAO,MAAM,KAAK,GAAI,IAAI,IAAI,KAAG,QAAmF,CAAC;AACrH,sCAAsC;AACtC,eAAO,MAAM,MAAM,GAAI,IAAI,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAA8E,CAAC"}
package/dist/index.js CHANGED
@@ -1,19 +1,45 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
2
  Object.defineProperty(exports, "__esModule", { value: true });
17
- // The reducer SDK is the package: `@kradle/challenges-sdk`. The test kit is `./test`, the sandbox loader `./runtime/sandbox`.
18
- __exportStar(require("./runtime/sdk.js"), exports);
3
+ exports.offset = exports.above = exports.below = exports.toBlockPos = exports.inBox = exports.bounds = exports.blockIdOf = exports.WORLD_DEFAULTS = exports.PLAYER_VOTE = exports.toTicks = exports.ticks = exports.TICKS_PER_SECOND = exports.subtractDuration = exports.seconds = exports.minutes = exports.asSeconds = exports.asMinutes = exports.addDuration = exports.winBy = exports.MAX_TRANSITIONS_PER_EVENT = exports.MAX_FILL_VOLUME = exports.MAX_BATCH_EFFECTS = exports.ChallengeError = exports.defineChallenge = void 0;
4
+ // The author API: `@kradle/challenges-sdk`. The engine protocol is `@kradle/challenges-sdk/engine`,
5
+ // the test kit `@kradle/challenges-sdk/test`.
6
+ var builder_js_1 = require("./runtime/builder.js");
7
+ Object.defineProperty(exports, "defineChallenge", { enumerable: true, get: function () { return builder_js_1.defineChallenge; } });
8
+ var errors_js_1 = require("./runtime/errors.js");
9
+ Object.defineProperty(exports, "ChallengeError", { enumerable: true, get: function () { return errors_js_1.ChallengeError; } });
10
+ var reducer_js_1 = require("./runtime/reducer.js");
11
+ Object.defineProperty(exports, "MAX_BATCH_EFFECTS", { enumerable: true, get: function () { return reducer_js_1.MAX_BATCH_EFFECTS; } });
12
+ Object.defineProperty(exports, "MAX_FILL_VOLUME", { enumerable: true, get: function () { return reducer_js_1.MAX_FILL_VOLUME; } });
13
+ Object.defineProperty(exports, "MAX_TRANSITIONS_PER_EVENT", { enumerable: true, get: function () { return reducer_js_1.MAX_TRANSITIONS_PER_EVENT; } });
14
+ var rules_js_1 = require("./runtime/rules.js");
15
+ Object.defineProperty(exports, "winBy", { enumerable: true, get: function () { return rules_js_1.winBy; } });
16
+ var time_js_1 = require("./runtime/time.js");
17
+ Object.defineProperty(exports, "addDuration", { enumerable: true, get: function () { return time_js_1.addDuration; } });
18
+ Object.defineProperty(exports, "asMinutes", { enumerable: true, get: function () { return time_js_1.asMinutes; } });
19
+ Object.defineProperty(exports, "asSeconds", { enumerable: true, get: function () { return time_js_1.asSeconds; } });
20
+ Object.defineProperty(exports, "minutes", { enumerable: true, get: function () { return time_js_1.minutes; } });
21
+ Object.defineProperty(exports, "seconds", { enumerable: true, get: function () { return time_js_1.seconds; } });
22
+ Object.defineProperty(exports, "subtractDuration", { enumerable: true, get: function () { return time_js_1.subtractDuration; } });
23
+ Object.defineProperty(exports, "TICKS_PER_SECOND", { enumerable: true, get: function () { return time_js_1.TICKS_PER_SECOND; } });
24
+ Object.defineProperty(exports, "ticks", { enumerable: true, get: function () { return time_js_1.ticks; } });
25
+ Object.defineProperty(exports, "toTicks", { enumerable: true, get: function () { return time_js_1.toTicks; } });
26
+ var types_js_1 = require("./runtime/types.js");
27
+ Object.defineProperty(exports, "PLAYER_VOTE", { enumerable: true, get: function () { return types_js_1.PLAYER_VOTE; } });
28
+ Object.defineProperty(exports, "WORLD_DEFAULTS", { enumerable: true, get: function () { return types_js_1.WORLD_DEFAULTS; } });
29
+ var world_js_1 = require("./runtime/world.js");
30
+ Object.defineProperty(exports, "blockIdOf", { enumerable: true, get: function () { return world_js_1.blockIdOf; } });
31
+ Object.defineProperty(exports, "bounds", { enumerable: true, get: function () { return world_js_1.bounds; } });
32
+ Object.defineProperty(exports, "inBox", { enumerable: true, get: function () { return world_js_1.inBox; } });
33
+ /** The integer block position of a continuous position: each axis floored. */
34
+ const toBlockPos = (v) => ({ x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z) });
35
+ exports.toBlockPos = toBlockPos;
36
+ /** The position one block under `at`. On a slab or in water this is not the supporting block. */
37
+ const below = (at) => ({ x: Math.floor(at.x), y: Math.floor(at.y) - 1, z: Math.floor(at.z) });
38
+ exports.below = below;
39
+ /** The position one block over `at`. */
40
+ const above = (at) => ({ x: Math.floor(at.x), y: Math.floor(at.y) + 1, z: Math.floor(at.z) });
41
+ exports.above = above;
42
+ /** `at` moved by `d` on each axis. */
43
+ const offset = (at, d) => ({ x: at.x + (d.x ?? 0), y: at.y + (d.y ?? 0), z: at.z + (d.z ?? 0) });
44
+ exports.offset = offset;
19
45
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8HAA8H;AAC9H,mDAAiC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,oGAAoG;AACpG,8CAA8C;AAC9C,mDAA0F;AAA9C,6GAAA,eAAe,OAAA;AAC3D,iDAAyE;AAAhE,2GAAA,cAAc,OAAA;AACvB,mDAAqG;AAA5F,+GAAA,iBAAiB,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,uHAAA,yBAAyB,OAAA;AACtE,+CAA2C;AAAlC,iGAAA,KAAK,OAAA;AACd,6CAY2B;AAX1B,sGAAA,WAAW,OAAA;AACX,oGAAA,SAAS,OAAA;AACT,oGAAA,SAAS,OAAA;AAET,kGAAA,OAAO,OAAA;AACP,kGAAA,OAAO,OAAA;AACP,2GAAA,gBAAgB,OAAA;AAChB,2GAAA,gBAAgB,OAAA;AAEhB,gGAAA,KAAK,OAAA;AACL,kGAAA,OAAO,OAAA;AAgDR,+CAAiE;AAAxD,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AAiBpC,+CAA8G;AAArG,qGAAA,SAAS,OAAA;AAAE,kGAAA,MAAM,OAAA;AAAkD,iGAAA,KAAK,OAAA;AAIjF,8EAA8E;AACvE,MAAM,UAAU,GAAG,CAAC,CAAO,EAAY,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAArG,QAAA,UAAU,cAA2F;AAClH,iGAAiG;AAC1F,MAAM,KAAK,GAAG,CAAC,EAAQ,EAAY,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAAxG,QAAA,KAAK,SAAmG;AACrH,wCAAwC;AACjC,MAAM,KAAK,GAAG,CAAC,EAAQ,EAAY,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAAxG,QAAA,KAAK,SAAmG;AACrH,sCAAsC;AAC/B,MAAM,MAAM,GAAG,CAAC,EAAQ,EAAE,CAAgB,EAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;AAAtH,QAAA,MAAM,UAAgH"}
@@ -0,0 +1,32 @@
1
+ import { type State } from "./reducer.js";
2
+ import type { ChallengeApi, ChallengeConfig, Decl, Handlers, Outcome, PhaseSpec, PlayerMap, ResolvePlayers, VotesDecl } from "./types.js";
3
+ import type { Action, Event, RuntimeManifest } from "./wire.js";
4
+ /** A challenge as the arena and the harness use it: the API plus `init`, `reduce` and the manifest. */
5
+ export interface Challenge<C extends ChallengeConfig, G, PM extends PlayerMap<C>, Ph extends string, Z extends string, T extends string, V extends VotesDecl> extends ChallengeApi<C, G, PM, Ph, Z, T, V> {
6
+ /** The manifest the arena and the backend read. Reading it locks the definition. */
7
+ readonly manifest: RuntimeManifest;
8
+ /** True once the definition is locked (`init()` or `manifest` was used). */
9
+ readonly locked: boolean;
10
+ /** A fresh run state. Locks the definition on the first call. */
11
+ init(): State;
12
+ /** One reduction: the next state and the actions for the engine. Never mutates `state`. */
13
+ reduce(state: State, event: Event): {
14
+ /** The next state, frozen; the input is never changed. */
15
+ state: State;
16
+ /** What the engine and the arena apply, in order. */
17
+ actions: Action[];
18
+ };
19
+ }
20
+ /**
21
+ * The engine interface of any challenge, independent of its config and declared names.
22
+ * It contains the reducer operations. The builder methods keep their concrete author types.
23
+ */
24
+ export type AnyChallenge = Pick<Challenge<any, any, any, any, any, any, any>, "manifest" | "locked" | "init" | "reduce" | "zones" | "config">;
25
+ /**
26
+ * Defines a challenge. The root declaration carries the data and the state initializers; the
27
+ * steps carry the behavior. `init()` or the first `manifest` read locks the definition.
28
+ */
29
+ export declare function defineChallenge<const C extends ChallengeConfig, G extends object = Record<string, never>, P extends object = Record<string, never>, PS extends Partial<Record<RoleOfC<C>, object>> = Record<never, never>, const Ph extends string = never, const Z extends string = never, const T extends string = never, const V extends VotesDecl = Record<never, never>>(config: C, decl: Decl<C, G, P, PS, Ph, Z, T, V>): Challenge<C, G, ResolvePlayers<C, P, PS>, Ph, Z, T, V>;
30
+ type RoleOfC<C extends ChallengeConfig> = keyof C["roles"] & string;
31
+ export type { Handlers, Outcome, PhaseSpec };
32
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/runtime/builder.ts"],"names":[],"mappings":"AAYA,OAAO,EAAwF,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAEhI,OAAO,KAAK,EACX,YAAY,EACZ,eAAe,EACf,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,SAAS,EACT,SAAS,EACT,cAAc,EACd,SAAS,EAET,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,MAAM,EAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAIrE,uGAAuG;AACvG,MAAM,WAAW,SAAS,CACzB,CAAC,SAAS,eAAe,EACzB,CAAC,EACD,EAAE,SAAS,SAAS,CAAC,CAAC,CAAC,EACvB,EAAE,SAAS,MAAM,EACjB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,SAAS,CAClB,SAAQ,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5C,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,iEAAiE;IACjE,IAAI,IAAI,KAAK,CAAC;IACd,2FAA2F;IAC3F,MAAM,CACL,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,GACV;QACF,0DAA0D;QAC1D,KAAK,EAAE,KAAK,CAAC;QACb,qDAAqD;QACrD,OAAO,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;AA4H9I;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,KAAK,CAAC,CAAC,SAAS,eAAe,EAC/B,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACxC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACxC,EAAE,SAAS,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EACrE,KAAK,CAAC,EAAE,SAAS,MAAM,GAAG,KAAK,EAC/B,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,EAC9B,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,EAC9B,KAAK,CAAC,CAAC,SAAS,SAAS,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC/C,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAoMzG;AAED,KAAK,OAAO,CAAC,CAAC,SAAS,eAAe,IAAI,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC;AAGpE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC"}