@horizon36596/zenith-season-biobuzz 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Horizon (FTC 36596)
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/NOTICE ADDED
@@ -0,0 +1,18 @@
1
+ Zenith
2
+ Copyright (c) 2026 Horizon (FTC 36596)
3
+
4
+ Zenith is licensed under the MIT License; see LICENSE.
5
+
6
+ Third-party assets
7
+
8
+ - BIOBUZZ field images (apps/web/public/fields/biobuzz/*.webp) by Team Juice 16236, from the r/FTC
9
+ post "BIOBUZZ custom field images (MeepMeep compatible)". Used with the credit the author asks for.
10
+
11
+ Bundled fonts
12
+
13
+ The following fonts are bundled in apps/web/public/fonts/. Each is licensed under the SIL Open Font
14
+ License, Version 1.1. The full licence text ships next to each font file.
15
+
16
+ - Jost, licensed under the SIL Open Font License 1.1 (jost-OFL.txt).
17
+ - IBM Plex Sans, licensed under the SIL Open Font License 1.1 (ibm-plex-sans-OFL.txt).
18
+ - JetBrains Mono, licensed under the SIL Open Font License 1.1 (jetbrains-mono-OFL.txt).
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @horizon36596/zenith-season-biobuzz
2
+
3
+ The FTC BIOBUZZ season for [Zenith](https://libraries.horizon36596.org/zenith/), the FTC autonomous
4
+ planner by Horizon (FTC 36596): the field file, and the season-rules plugin that reads it.
5
+
6
+ ```
7
+ npm install @horizon36596/zenith-season-biobuzz
8
+ ```
9
+
10
+ The field file ships as `field/biobuzz.field.json`
11
+ (`@horizon36596/zenith-season-biobuzz/field/biobuzz.field.json`); `zenith init` copies it into a
12
+ project. Every rule is derived from that file, so a team that corrects a measurement in its own copy
13
+ gets rules that match.
14
+
15
+ Most projects reach this package through
16
+ [`@horizon36596/zenith-seasons`](https://www.npmjs.com/package/@horizon36596/zenith-seasons).
17
+ Documentation: https://libraries.horizon36596.org/zenith/. Licence: MIT.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @horizon36596/zenith-season-biobuzz: the BIOBUZZ field file and the `SeasonRules` plugin that reads it
3
+ * (site/docs/seasons.md).
4
+ *
5
+ * The field file is the season: every rule below is derived from `field/biobuzz.field.json`, so a
6
+ * team that corrects a measurement in their own copy gets the corrected rules with it.
7
+ */
8
+ export declare const seasonId = "biobuzz";
9
+ export * from "./state.js";
10
+ export * from "./rules.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,YAAY,CAAC;AAElC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @horizon36596/zenith-season-biobuzz: the BIOBUZZ field file and the `SeasonRules` plugin that reads it
3
+ * (site/docs/seasons.md).
4
+ *
5
+ * The field file is the season: every rule below is derived from `field/biobuzz.field.json`, so a
6
+ * team that corrects a measurement in their own copy gets the corrected rules with it.
7
+ */
8
+ export const seasonId = "biobuzz";
9
+ export * from "./state.js";
10
+ export * from "./rules.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAC;AAElC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type SeasonRules } from "@horizon36596/zenith-core";
2
+ import type { Field } from "@horizon36596/zenith-schema";
3
+ /**
4
+ * The BIOBUZZ season plugin: the `SeasonRules` of
5
+ * site/docs/seasons.md, implemented from `field.json` alone.
6
+ *
7
+ * It is deliberately simple. It does not model where a spilled game piece lands; after a tip the
8
+ * spilled count becomes loose with an unknown position, which is what makes a following
9
+ * `collectNearestPollen` an unknown time rather than a made-up one.
10
+ */
11
+ /** How close a footprint corner has to be to a wall to count as touching it, in inches. */
12
+ export declare const TOUCHING_WALL_TOLERANCE_IN = 0.5;
13
+ /** How far a footprint may sit over the centre line before it is no longer in its own half. */
14
+ export declare const OWN_HALF_TOLERANCE_IN = 0.05;
15
+ export declare const biobuzzRules: SeasonRules;
16
+ /** The rules for a field file: this plugin when the file names it, and nothing otherwise. */
17
+ export declare function loadSeason(field: Field): SeasonRules;
18
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EAIjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,KAAK,EAAS,MAAM,6BAA6B,CAAC;AAUhE;;;;;;;GAOG;AAEH,2FAA2F;AAC3F,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,+FAA+F;AAC/F,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAoD1C,eAAO,MAAM,YAAY,EAAE,WAwW1B,CAAC;AAiBF,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,CAIpD"}
package/dist/rules.js ADDED
@@ -0,0 +1,402 @@
1
+ import { fieldBounds, footprintAt, noSeasonRules, polygonOverlapsBox, } from "@horizon36596/zenith-core";
2
+ import { heldTotal, initialBiobuzzState, oppositeSide, upCellFace, } from "./state.js";
3
+ /**
4
+ * The BIOBUZZ season plugin: the `SeasonRules` of
5
+ * site/docs/seasons.md, implemented from `field.json` alone.
6
+ *
7
+ * It is deliberately simple. It does not model where a spilled game piece lands; after a tip the
8
+ * spilled count becomes loose with an unknown position, which is what makes a following
9
+ * `collectNearestPollen` an unknown time rather than a made-up one.
10
+ */
11
+ /** How close a footprint corner has to be to a wall to count as touching it, in inches. */
12
+ export const TOUCHING_WALL_TOLERANCE_IN = 0.5;
13
+ /** How far a footprint may sit over the centre line before it is no longer in its own half. */
14
+ export const OWN_HALF_TOLERANCE_IN = 0.05;
15
+ const as = (state) => state;
16
+ const plural = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
17
+ const addHold = (holds, kind, delta) => {
18
+ const next = { ...holds };
19
+ const value = (next[kind] ?? 0) + delta;
20
+ if (value <= 0)
21
+ delete next[kind];
22
+ else
23
+ next[kind] = value;
24
+ return next;
25
+ };
26
+ /** The number in the up cell that tips the hive next, from `rules.tipTable` (`02`). */
27
+ function tipThreshold(hive, tipTable) {
28
+ if (tipTable.length === 0)
29
+ return null;
30
+ const index = Math.min(hive.tips, tipTable.length - 1);
31
+ return tipTable[index] ?? null;
32
+ }
33
+ /** Swings the hive over: the up cell spills, the down cell comes up, and the up side flips. */
34
+ function tipHive(state, hive) {
35
+ const spilled = hive.upCellCount;
36
+ const tipped = {
37
+ ...hive,
38
+ upSide: oppositeSide(hive.upSide),
39
+ upCellCount: hive.downCellCount,
40
+ downCellCount: 0,
41
+ tips: hive.tips + 1,
42
+ };
43
+ return {
44
+ ...state,
45
+ hives: { ...state.hives, [hive.id]: tipped },
46
+ looseCount: state.looseCount + spilled,
47
+ looseUnknown: state.looseUnknown || spilled > 0,
48
+ };
49
+ }
50
+ /** The hive a target id belongs to, or null when the target is not a hive cell. */
51
+ function hiveOfTarget(state, target) {
52
+ for (const hive of Object.values(state.hives)) {
53
+ if (hive.targetId === target || hive.id === target)
54
+ return hive;
55
+ }
56
+ return null;
57
+ }
58
+ export const biobuzzRules = {
59
+ id: "season-biobuzz",
60
+ initialState: (field) => initialBiobuzzState(field),
61
+ start: (state, holds) => {
62
+ const current = as(state);
63
+ return { ...current, robotHolds: { ...holds } };
64
+ },
65
+ holds: (state) => ({ ...as(state).robotHolds }),
66
+ /**
67
+ * A volley into the hive's up cell: the pieces leave the robot, land in the cell, and once the
68
+ * cell holds what the tip table says, the hive swings and whatever was up spills onto the floor.
69
+ */
70
+ onLaunch: (state, target, count, pose) => {
71
+ void pose;
72
+ const current = as(state);
73
+ const hive = hiveOfTarget(current, target);
74
+ const launched = Math.max(0, Math.min(count, heldTotal(current)));
75
+ let robotHolds = current.robotHolds;
76
+ let remaining = launched;
77
+ for (const kind of Object.keys(robotHolds)) {
78
+ if (remaining <= 0)
79
+ break;
80
+ const take = Math.min(remaining, robotHolds[kind] ?? 0);
81
+ robotHolds = addHold(robotHolds, kind, -take);
82
+ remaining -= take;
83
+ }
84
+ if (hive === null)
85
+ return { ...current, robotHolds };
86
+ const filled = { ...hive, upCellCount: hive.upCellCount + launched };
87
+ let next = {
88
+ ...current,
89
+ robotHolds,
90
+ hives: { ...current.hives, [hive.id]: filled },
91
+ };
92
+ for (let guard = 0; guard < 8; guard += 1) {
93
+ const standing = next.hives[hive.id];
94
+ if (standing === undefined)
95
+ break;
96
+ const threshold = tipThreshold(standing, next.tipTable);
97
+ if (threshold === null || standing.upCellCount < threshold)
98
+ break;
99
+ next = tipHive(next, standing);
100
+ }
101
+ return next;
102
+ },
103
+ /**
104
+ * The flowers hand pollen out of the opening at their base, and only the kinds that opening
105
+ * lists; a hive's nectar is locked in and no `collectFrom` annotation can take it out. A garden
106
+ * element is a single piece lying on the floor.
107
+ */
108
+ onCollect: (state, containerId, count) => {
109
+ const current = as(state);
110
+ const room = Math.max(0, current.capacity - heldTotal(current));
111
+ const wanted = Math.max(0, Math.min(count, room));
112
+ if (wanted === 0)
113
+ return state;
114
+ const flower = current.flowers[containerId];
115
+ if (flower !== undefined) {
116
+ let holds = flower.holds;
117
+ let robotHolds = current.robotHolds;
118
+ let left = wanted;
119
+ for (const kind of flower.takes) {
120
+ if (left <= 0)
121
+ break;
122
+ const take = Math.min(left, holds[kind] ?? 0);
123
+ if (take === 0)
124
+ continue;
125
+ holds = addHold(holds, kind, -take);
126
+ robotHolds = addHold(robotHolds, kind, take);
127
+ left -= take;
128
+ }
129
+ return {
130
+ ...current,
131
+ robotHolds,
132
+ flowers: { ...current.flowers, [containerId]: { ...flower, holds } },
133
+ };
134
+ }
135
+ if (current.hives[containerId] !== undefined) {
136
+ // Nectar is locked inside the hive: there is no legal way to take it back out in auto.
137
+ return state;
138
+ }
139
+ const gardenIds = Object.keys(current.garden).filter((id) => (id === containerId || id.startsWith(containerId)) && current.garden[id] === true);
140
+ if (gardenIds.length > 0) {
141
+ const taken = gardenIds.slice(0, wanted);
142
+ const garden = { ...current.garden };
143
+ for (const id of taken)
144
+ garden[id] = false;
145
+ return {
146
+ ...current,
147
+ garden,
148
+ robotHolds: addHold(current.robotHolds, current.elementKind, taken.length),
149
+ };
150
+ }
151
+ if (containerId === "loose" && current.looseCount > 0) {
152
+ const taken = Math.min(wanted, current.looseCount);
153
+ return {
154
+ ...current,
155
+ looseCount: current.looseCount - taken,
156
+ robotHolds: addHold(current.robotHolds, current.elementKind, taken),
157
+ };
158
+ }
159
+ return state;
160
+ },
161
+ /**
162
+ * The author asserting the hive swung, which `assumeTipped` and `expect.tip` both do. It is
163
+ * trusted over the tip table, because a camera or a driver saw it and the table only predicts it.
164
+ */
165
+ onTip: (state, which, alliance) => {
166
+ const current = as(state);
167
+ const wanted = which === "own" ? alliance : alliance === "RED" ? "BLUE" : "RED";
168
+ const hive = Object.values(current.hives).find((candidate) => candidate.alliance === wanted);
169
+ if (hive === undefined)
170
+ return state;
171
+ return tipHive(current, hive);
172
+ },
173
+ /** The alliance's own hive cell that is currently up: the only thing worth shooting at. */
174
+ currentTarget: (state, alliance) => {
175
+ for (const hive of Object.values(as(state).hives)) {
176
+ if (hive.alliance === alliance)
177
+ return hive.targetId ?? hive.id;
178
+ }
179
+ return null;
180
+ },
181
+ /**
182
+ * A launch is only legal from outboard of the up cell's outer face, by the margin the field file
183
+ * states, along the hive's swing axis and on the side the cell faces.
184
+ */
185
+ legalApproach: (state, target, pose, robot) => {
186
+ void robot;
187
+ const hive = hiveOfTarget(as(state), target);
188
+ if (hive === null)
189
+ return true;
190
+ const { faceIn, outboardSign } = upCellFace(hive);
191
+ const along = hive.axis === "x" ? pose.xIn : pose.yIn;
192
+ const limit = faceIn + outboardSign * hive.approachMarginIn;
193
+ return outboardSign > 0 ? along >= limit : along <= limit;
194
+ },
195
+ /**
196
+ * Where the up cell sits, for the turret-range check: the middle of the cell, which is half the
197
+ * outer-face distance out from the pivot on the side that is up.
198
+ */
199
+ targetPoint: (state, target) => {
200
+ const hive = hiveOfTarget(as(state), target);
201
+ if (hive === null)
202
+ return null;
203
+ const { outboardSign } = upCellFace(hive);
204
+ const middle = outboardSign * (hive.outerFaceIn / 2);
205
+ return hive.axis === "x"
206
+ ? { xIn: hive.pivotIn.xIn + middle, yIn: hive.pivotIn.yIn }
207
+ : { xIn: hive.pivotIn.xIn, yIn: hive.pivotIn.yIn + middle };
208
+ },
209
+ /**
210
+ * The nearest pose the approach rule would accept: straight out along the hive's axis to the
211
+ * edge of the legal band, keeping the other coordinate, facing the cell.
212
+ */
213
+ nearestLegalApproach: (state, target, pose, robot) => {
214
+ const current = as(state);
215
+ const hive = hiveOfTarget(current, target);
216
+ if (hive === null)
217
+ return null;
218
+ if (biobuzzRules.legalApproach(state, target, pose, robot))
219
+ return pose;
220
+ const { faceIn, outboardSign } = upCellFace(hive);
221
+ const limit = faceIn + outboardSign * hive.approachMarginIn;
222
+ const moved = hive.axis === "x" ? { xIn: limit, yIn: pose.yIn } : { xIn: pose.xIn, yIn: limit };
223
+ const aim = biobuzzRules.targetPoint?.(state, target) ?? null;
224
+ const headingRad = aim === null ? pose.headingRad : Math.atan2(aim.yIn - moved.yIn, aim.xIn - moved.xIn);
225
+ return { ...moved, headingRad };
226
+ },
227
+ /**
228
+ * `startRules` from `field.json`, checked against the footprint at the start pose.
229
+ *
230
+ * The pose is in the frame of `alliance`, the file's own, which is where the robot starts it. The
231
+ * field file writes its rules for `field.frame.canonicalAlliance`, so for the other alliance its
232
+ * own half is the one its own hive is in and a named zone is read as its counterpart
233
+ * (`loadingZoneRed` is BLUE's `loadingZoneBlue`).
234
+ */
235
+ startLegal: (field, pose, robot, alliance = field.frame.canonicalAlliance) => {
236
+ const rules = field.startRules;
237
+ if (rules === undefined)
238
+ return [];
239
+ const ownId = (id) => alliance === field.frame.canonicalAlliance ? id : (swapAllianceWord(id) ?? id);
240
+ const findings = [];
241
+ const bounds = fieldBounds(field);
242
+ const corners = footprintAt(pose, robot, "startIn").bodyIn;
243
+ const say = (message) => {
244
+ findings.push({
245
+ severity: "error",
246
+ stepId: "start",
247
+ code: "START_ILLEGAL",
248
+ message,
249
+ geometry: { polygonIn: corners },
250
+ });
251
+ };
252
+ if (rules.touchingWall === true) {
253
+ const nearest = Math.min(...corners.map((corner) => Math.min(corner.xIn - bounds.minXIn, bounds.maxXIn - corner.xIn, corner.yIn - bounds.minYIn, bounds.maxYIn - corner.yIn)));
254
+ if (nearest > TOUCHING_WALL_TOLERANCE_IN) {
255
+ say(`The robot must start touching a wall, and its nearest corner is ${nearest.toFixed(2)} in away from one.`);
256
+ }
257
+ }
258
+ if (rules.ownHalf === true) {
259
+ // The pose is in the frame of the file's own alliance, and that alliance's hive says which
260
+ // side of the centre line is its own half.
261
+ const ownHive = (field.elements ?? []).find((element) => element.container === "hive" && element.id.toLowerCase().includes(alliance.toLowerCase()));
262
+ const pivotX = ownHive === undefined ? -1 : (numberAt(ownHive, "pivotIn", "xIn") ?? -1);
263
+ // Own half is where `x * ownSign` is positive, so a corner on the wrong side of the centre
264
+ // line is one whose `-x * ownSign` is above the tolerance.
265
+ const ownSign = pivotX < 0 ? -1 : 1;
266
+ const worst = Math.max(...corners.map((corner) => -corner.xIn * ownSign));
267
+ if (worst > OWN_HALF_TOLERANCE_IN) {
268
+ say(`The robot must start in its own half, and its footprint crosses the centre line by ${worst.toFixed(2)} in.`);
269
+ }
270
+ }
271
+ for (const zoneId of (rules.notInZones ?? []).map(ownId)) {
272
+ const zone = (field.zones ?? []).find((candidate) => candidate.id === zoneId);
273
+ if (zone === undefined)
274
+ continue;
275
+ if (polygonOverlapsBox(corners, zone).overlaps) {
276
+ say(`The robot must not start in ${zoneId}${zone.rule === undefined ? "" : ` (${zone.rule})`}.`);
277
+ }
278
+ }
279
+ for (const containerKind of rules.notTouchingContainers ?? []) {
280
+ for (const element of field.elements ?? []) {
281
+ if (element.container !== containerKind)
282
+ continue;
283
+ // A container's footprint on the floor is the obstacle the field file draws for it.
284
+ for (const obstacle of field.obstacles ?? []) {
285
+ if (!obstacle.id.startsWith(element.id))
286
+ continue;
287
+ if (polygonOverlapsBox(corners, obstacle).overlaps) {
288
+ say(`The robot must not start touching a ${containerKind}, and it overlaps ${obstacle.id}.`);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ return findings;
294
+ },
295
+ describe: (before, after) => {
296
+ const a = as(before);
297
+ const b = as(after);
298
+ const parts = [];
299
+ for (const hive of Object.values(b.hives)) {
300
+ const was = a.hives[hive.id];
301
+ if (was === undefined)
302
+ continue;
303
+ if (hive.tips !== was.tips) {
304
+ parts.push(`${hive.id} tipped and now faces ${hive.upSide}`);
305
+ }
306
+ else if (hive.upCellCount !== was.upCellCount) {
307
+ parts.push(`${hive.id} up cell holds ${plural(hive.upCellCount, "piece")}`);
308
+ }
309
+ }
310
+ for (const flower of Object.values(b.flowers)) {
311
+ const was = a.flowers[flower.id];
312
+ if (was === undefined)
313
+ continue;
314
+ const left = Object.values(flower.holds).reduce((sum, value) => sum + value, 0);
315
+ const had = Object.values(was.holds).reduce((sum, value) => sum + value, 0);
316
+ if (left !== had)
317
+ parts.push(`${flower.id} has ${plural(left, "piece")} left`);
318
+ }
319
+ if (b.looseCount !== a.looseCount) {
320
+ parts.push(`${plural(b.looseCount - a.looseCount, "piece")} spilled to an unknown place`);
321
+ }
322
+ return parts.length === 0 ? null : parts.join("; ");
323
+ },
324
+ /**
325
+ * The other alliance's name for a field id.
326
+ *
327
+ * Every alliance-specific id in `biobuzz.field.json` is spelled with `Red` or `Blue` in it
328
+ * (`hiveRed`, `hiveBlueUpCell`, `gardenRed2`, `loadingZoneBlue`), so the counterpart is the same
329
+ * id with the colour swapped. An id with neither — a flower, which both alliances share — is its
330
+ * own counterpart. Nothing here invents an id: a swapped name that the field file does not carry
331
+ * is reported as having no counterpart, so `mirrorAuto` refuses rather than writing a dangling
332
+ * reference.
333
+ */
334
+ mirrorId: (id) => {
335
+ const swapped = swapAllianceWord(id);
336
+ if (swapped === null)
337
+ return id;
338
+ return swapped;
339
+ },
340
+ /** The end-of-routine rows the ledger panel and the pull-request body both print. */
341
+ summary: (state) => {
342
+ const current = as(state);
343
+ const rows = [
344
+ {
345
+ stepId: "end",
346
+ label: "robot",
347
+ detail: heldTotal(current) === 0
348
+ ? "empty"
349
+ : Object.entries(current.robotHolds)
350
+ .map(([kind, count]) => plural(count, kind))
351
+ .join(", "),
352
+ holds: { ...current.robotHolds },
353
+ },
354
+ ];
355
+ for (const hive of Object.values(current.hives)) {
356
+ rows.push({
357
+ stepId: "end",
358
+ label: hive.id,
359
+ detail: `up ${hive.upSide}, ${plural(hive.tips, "tip")}, ${plural(hive.upCellCount, "piece")} in the up cell`,
360
+ });
361
+ }
362
+ for (const flower of Object.values(current.flowers)) {
363
+ const left = Object.values(flower.holds).reduce((sum, value) => sum + value, 0);
364
+ rows.push({ stepId: "end", label: flower.id, detail: plural(left, "piece") });
365
+ }
366
+ const gardenLeft = Object.values(current.garden).filter(Boolean).length;
367
+ rows.push({ stepId: "end", label: "garden", detail: plural(gardenLeft, "piece") });
368
+ if (current.looseCount > 0) {
369
+ rows.push({
370
+ stepId: "end",
371
+ label: "loose",
372
+ detail: `${plural(current.looseCount, "piece")}, position unknown`,
373
+ });
374
+ }
375
+ return rows;
376
+ },
377
+ };
378
+ /** `hiveRedUpCell` -> `hiveBlueUpCell`, and null when the id names no alliance at all. */
379
+ function swapAllianceWord(id) {
380
+ if (id.includes("Red"))
381
+ return id.split("Red").join("Blue");
382
+ if (id.includes("Blue"))
383
+ return id.split("Blue").join("Red");
384
+ return null;
385
+ }
386
+ function numberAt(source, key, inner) {
387
+ if (typeof source !== "object" || source === null)
388
+ return null;
389
+ const nested = source[key];
390
+ if (typeof nested !== "object" || nested === null)
391
+ return null;
392
+ const value = nested[inner];
393
+ return typeof value === "number" ? value : null;
394
+ }
395
+ /** The rules for a field file: this plugin when the file names it, and nothing otherwise. */
396
+ export function loadSeason(field) {
397
+ const plugin = field.rules?.plugin;
398
+ if (plugin === biobuzzRules.id || field.season === "biobuzz")
399
+ return biobuzzRules;
400
+ return noSeasonRules;
401
+ }
402
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import type { Field } from "@horizon36596/zenith-schema";
2
+ /**
3
+ * The BIOBUZZ ledger state. Everything in it is read out of `field.json` at kickoff, because
4
+ * site/docs/file-format.md says the file alone is the season: nothing here is a constant typed
5
+ * from the manual, and a team that corrects a measurement in their own copy of the field file gets
6
+ * the corrected rules for free.
7
+ *
8
+ * A `type` rather than an `interface` on purpose: the core threads this through `SeasonState`,
9
+ * which is `Readonly<Record<string, unknown>>`, and only a type alias is assignable to that.
10
+ */
11
+ export type HiveState = {
12
+ id: string;
13
+ /** RED or BLUE, taken from the hive's own id, which is how `field.json` names sides. */
14
+ alliance: "RED" | "BLUE";
15
+ pivotIn: {
16
+ xIn: number;
17
+ yIn: number;
18
+ };
19
+ /** Which way the cell that is currently up faces. */
20
+ upSide: string;
21
+ /** How many game pieces are sitting in the cell that is up. */
22
+ upCellCount: number;
23
+ /** How many are in the cell that is down; they ride back up at the next tip. */
24
+ downCellCount: number;
25
+ /** How many times this hive has tipped so far. */
26
+ tips: number;
27
+ /** The axis the cells swing along, and how far the outer faces sit from the pivot. */
28
+ axis: "x" | "y";
29
+ outerFaceIn: number;
30
+ /** How far outboard of that face the robot has to be to have a legal approach. */
31
+ approachMarginIn: number;
32
+ /** The target id that names this hive's up cell. */
33
+ targetId: string | null;
34
+ };
35
+ export type FlowerState = {
36
+ id: string;
37
+ xIn: number;
38
+ yIn: number;
39
+ /** What is left inside, by element kind. */
40
+ holds: Record<string, number>;
41
+ /** The kinds the bottom retrieval opening lets out. Nectar is not one of them. */
42
+ takes: string[];
43
+ };
44
+ export type BiobuzzState = {
45
+ /** What the robot is carrying, by element kind. */
46
+ robotHolds: Record<string, number>;
47
+ hives: Record<string, HiveState>;
48
+ flowers: Record<string, FlowerState>;
49
+ /** The garden lines, still on the floor: element id to true while it is there. */
50
+ garden: Record<string, boolean>;
51
+ /**
52
+ * Pieces that spilled out of a tipping hive. `03` section 6 is explicit that the ledger does not
53
+ * model where they land, so the count is carried and the position is not.
54
+ */
55
+ looseCount: number;
56
+ /** True once a tip has spilled: their positions are unknown and a collect near them is untimed. */
57
+ looseUnknown: boolean;
58
+ /** The number in the up cell that makes the hive tip, for the first tip, the second, and so on. */
59
+ tipTable: number[];
60
+ /** How many pieces the robot may hold at once, from `rules.capacity`. */
61
+ capacity: number;
62
+ /** The element kind the robot carries and launches. */
63
+ elementKind: string;
64
+ };
65
+ /** The BIOBUZZ state at kickoff, entirely from `field.json` (`03` section 6, trust source one). */
66
+ export declare function initialBiobuzzState(field: Field): BiobuzzState;
67
+ /** How many pieces the robot is carrying, across every kind. */
68
+ export declare const heldTotal: (state: BiobuzzState) => number;
69
+ /** Which way the hive's up cell faces after it swings over. */
70
+ export declare const oppositeSide: (side: string) => string;
71
+ /**
72
+ * Where the up cell's outer face sits along the swing axis, in field coordinates, and which way is
73
+ * outboard of it. A cell facing SOUTH or WEST has its face on the negative side of the pivot.
74
+ */
75
+ export declare function upCellFace(hive: HiveState): {
76
+ faceIn: number;
77
+ outboardSign: 1 | -1;
78
+ };
79
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA0B,MAAM,6BAA6B,CAAC;AAEjF;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,wFAAwF;IACxF,QAAQ,EAAE,KAAK,GAAG,MAAM,CAAC;IACzB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,kFAAkF;IAClF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,mGAAmG;IACnG,YAAY,EAAE,OAAO,CAAC;IACtB,mGAAmG;IACnG,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AA0DF,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,YAAY,CA8D9D;AAED,gEAAgE;AAChE,eAAO,MAAM,SAAS,GAAI,OAAO,YAAY,KAAG,MAAiC,CAAC;AAElF,+DAA+D;AAC/D,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAQ3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;CAAE,CAKpF"}