@kradle/challenges-sdk 0.11.0 → 0.13.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.
- package/CHANGELOG.md +71 -1
- package/LLM_README.md +286 -193
- package/MIGRATION-0.13.md +109 -0
- package/README.md +98 -90
- package/dist/engine.d.ts +8 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +17 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -16
- package/dist/index.js.map +1 -1
- package/dist/runtime/builder.d.ts +33 -0
- package/dist/runtime/builder.d.ts.map +1 -0
- package/dist/runtime/builder.js +357 -0
- package/dist/runtime/builder.js.map +1 -0
- package/dist/runtime/errors.d.ts +61 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +71 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/reducer.d.ts +205 -0
- package/dist/runtime/reducer.d.ts.map +1 -0
- package/dist/runtime/reducer.js +2025 -0
- package/dist/runtime/reducer.js.map +1 -0
- package/dist/runtime/rules.d.ts +29 -0
- package/dist/runtime/rules.d.ts.map +1 -0
- package/dist/runtime/rules.js +23 -0
- package/dist/runtime/rules.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +2 -1
- package/dist/runtime/sandbox.d.ts.map +1 -1
- package/dist/runtime/sandbox.js +7 -4
- package/dist/runtime/sandbox.js.map +1 -1
- package/dist/runtime/time.d.ts +33 -0
- package/dist/runtime/time.d.ts.map +1 -0
- package/dist/runtime/time.js +53 -0
- package/dist/runtime/time.js.map +1 -0
- package/dist/runtime/types.d.ts +963 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +25 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/wire.d.ts +858 -0
- package/dist/runtime/wire.d.ts.map +1 -0
- package/dist/runtime/wire.js +28 -0
- package/dist/runtime/wire.js.map +1 -0
- package/dist/runtime/world.d.ts +58 -3
- package/dist/runtime/world.d.ts.map +1 -1
- package/dist/runtime/world.js +12 -0
- package/dist/runtime/world.js.map +1 -1
- package/dist/structures.d.ts +119 -0
- package/dist/structures.d.ts.map +1 -0
- package/dist/structures.js +279 -0
- package/dist/structures.js.map +1 -0
- package/dist/test/harness.d.ts +280 -0
- package/dist/test/harness.d.ts.map +1 -0
- package/dist/{runtime → test}/harness.js +193 -68
- package/dist/test/harness.js.map +1 -0
- package/dist/test/helpers.d.ts +8 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/helpers.js +12 -0
- package/dist/test/helpers.js.map +1 -0
- package/dist/test/index.d.ts +3 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/{runtime → test}/index.js +3 -1
- package/dist/test/index.js.map +1 -0
- package/package.json +31 -16
- package/dist/runtime/examples/capture-the-flag.d.ts +0 -33
- package/dist/runtime/examples/capture-the-flag.d.ts.map +0 -1
- package/dist/runtime/examples/capture-the-flag.js +0 -79
- package/dist/runtime/examples/capture-the-flag.js.map +0 -1
- package/dist/runtime/examples/climb.d.ts +0 -34
- package/dist/runtime/examples/climb.d.ts.map +0 -1
- package/dist/runtime/examples/climb.js +0 -54
- package/dist/runtime/examples/climb.js.map +0 -1
- package/dist/runtime/examples/four-bridges.d.ts +0 -107
- package/dist/runtime/examples/four-bridges.d.ts.map +0 -1
- package/dist/runtime/examples/four-bridges.js +0 -294
- package/dist/runtime/examples/four-bridges.js.map +0 -1
- package/dist/runtime/examples/herding.d.ts +0 -59
- package/dist/runtime/examples/herding.d.ts.map +0 -1
- package/dist/runtime/examples/herding.js +0 -181
- package/dist/runtime/examples/herding.js.map +0 -1
- package/dist/runtime/examples/skywars.d.ts +0 -116
- package/dist/runtime/examples/skywars.d.ts.map +0 -1
- package/dist/runtime/examples/skywars.js +0 -253
- package/dist/runtime/examples/skywars.js.map +0 -1
- package/dist/runtime/harness.d.ts +0 -176
- package/dist/runtime/harness.d.ts.map +0 -1
- package/dist/runtime/harness.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/sdk.d.ts +0 -1038
- package/dist/runtime/sdk.d.ts.map +0 -1
- package/dist/runtime/sdk.js +0 -1271
- 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
|
@@ -14,14 +14,14 @@ the CLI's README first.
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
kradle init
|
|
17
|
-
kradle challenge create my-challenge # challenges/my-challenge/{challenge.ts,config.ts
|
|
17
|
+
kradle challenge create my-challenge # challenges/my-challenge/{challenge.ts,config.ts,challenge.test.ts}
|
|
18
18
|
kradle challenge test my-challenge # the tests, inside the arena's sandbox
|
|
19
19
|
kradle challenge build my-challenge # the bundle, uploaded and activated
|
|
20
20
|
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
|
-
(
|
|
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,121 +42,129 @@ Then run `kradle challenge test <slug>` in the consumer project.
|
|
|
42
42
|
|
|
43
43
|
## Writing a challenge
|
|
44
44
|
|
|
45
|
-
One call
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
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;
|
|
68
|
-
seeker.
|
|
69
|
-
|
|
70
|
-
g.
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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.
|
|
97
107
|
|
|
98
108
|
## Reading the world
|
|
99
109
|
|
|
100
|
-
The
|
|
101
|
-
|
|
102
|
-
`entityId`, `uuid`, `equipment`, `invisible`), every entity
|
|
103
|
-
(`g.entities({ box, type, tag })`), every loaded chunk (`g.blockAt` for the id,
|
|
104
|
-
`g.blockStateAt` for the full state, `g.blockCount`, `g.findBlocks`), the
|
|
105
|
-
world time (`g.worldTime`), and each participant's inventory (`p.inventory`,
|
|
106
|
-
`p.slots`, `g.count(p, item)`). Nothing is declared to the engine for that,
|
|
107
|
-
except the chests a challenge reads: `containers: { delivery: { x, y, z } }` in
|
|
108
|
-
the spec makes the engine report their contents; `g.contents("delivery")` gives
|
|
109
|
-
the item counts and `on.container(g, { at, block, items, changes, by })` fires
|
|
110
|
-
on every change. A report for an undeclared position is ignored.
|
|
111
|
-
Zones are named boxes the SDK evaluates from the positions every tick;
|
|
112
|
-
`g.inZone(p, box)` takes any box too. The `mirror` option narrows what the
|
|
113
|
-
engine streams when a world is large. `src/runtime/examples/herding.ts` scores
|
|
114
|
-
sheep inside a pen from the entity mirror and watches a fence gap through the
|
|
115
|
-
block mirror.
|
|
116
|
-
|
|
117
|
-
`typeof x.ctx` on a challenge is the same type; use it in tests. In the file that
|
|
118
|
-
defines the challenge it works only for helpers called from inside a rule
|
|
119
|
-
(`endState: (g) => classify(g)`): a helper passed as the property value
|
|
120
|
-
(`endState: classify`) would make the challenge's type circular.
|
|
121
|
-
`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:
|
|
122
112
|
|
|
123
|
-
|
|
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)`
|
|
124
118
|
|
|
125
|
-
|
|
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`.
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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;
|
|
133
138
|
```
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
timers, no `process`, no network. The harness alone does not apply those limits.
|
|
137
|
-
`kradle challenge test <slug>` builds the challenge and runs its tests against the
|
|
138
|
-
built bundle inside that sandbox. Wrap the import with `sandboxed()`: the test
|
|
139
|
-
then uses the bundle under the command, and the plain import elsewhere.
|
|
140
|
+
## Testing
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
import climbSource from "../challenges/climb/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.
|
|
144
144
|
|
|
145
|
-
|
|
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
|
+
});
|
|
146
160
|
```
|
|
147
161
|
|
|
148
|
-
`
|
|
149
|
-
and
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`@kradle/challenges-sdk/runtime/sandbox` (`lockdownOnce()`, `loadBundle()`,
|
|
155
|
-
`loadBundleFile()`) for a test runner of your own. Call it in a dedicated
|
|
156
|
-
process: the lockdown changes the intrinsics of the whole process. Under the
|
|
157
|
-
command a `Game` on the imported source throws: the command wants the bundle,
|
|
158
|
-
and it counts the Games built on it (`KRADLE_CHALLENGE_EVIDENCE`) as its proof
|
|
159
|
-
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.
|
|
160
168
|
|
|
161
169
|
## Build and run
|
|
162
170
|
|
package/dist/engine.d.ts
ADDED
|
@@ -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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
18
|
-
|
|
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":"
|
|
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,33 @@
|
|
|
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 shape every built challenge bundle exports as `default`: any challenge, whatever its config and names.
|
|
22
|
+
* `any` on purpose: a concrete challenge (literal phase names, typed options) is not assignable to one typed
|
|
23
|
+
* with `string`, because the callbacks are contravariant in those names.
|
|
24
|
+
*/
|
|
25
|
+
export type AnyChallenge = Challenge<any, any, any, any, any, any, any>;
|
|
26
|
+
/**
|
|
27
|
+
* Defines a challenge. The root declaration carries the data and the state initializers; the
|
|
28
|
+
* steps carry the behavior. `init()` or the first `manifest` read locks the definition.
|
|
29
|
+
*/
|
|
30
|
+
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>;
|
|
31
|
+
type RoleOfC<C extends ChallengeConfig> = keyof C["roles"] & string;
|
|
32
|
+
export type { Handlers, Outcome, PhaseSpec };
|
|
33
|
+
//# 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;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AA4HxE;;;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"}
|