@pkmn/engine 0.1.0-dev.b3197aa3 → 0.1.0-dev.b396399c

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 (47) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +17 -53
  3. package/build/pkg/addon/wasm.js.map +1 -1
  4. package/build/pkg/addon.d.ts +4 -4
  5. package/build/pkg/addon.js.map +1 -1
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/protocol.d.ts +11 -2
  9. package/build/pkg/protocol.js.map +1 -1
  10. package/build/tools/debug.js +37 -3
  11. package/build/tools/debug.js.map +1 -1
  12. package/build/tools/display/debug.jsx +1 -87
  13. package/build/tools/display/debug.jsx.map +1 -1
  14. package/build/tools/display/ui.jsx +3 -2
  15. package/build/tools/display/ui.jsx.map +1 -1
  16. package/build/tools/display/util.js +125 -1
  17. package/build/tools/display/util.js.map +1 -1
  18. package/build.zig +141 -232
  19. package/package.json +22 -21
  20. package/src/bin/install-pkmn-engine +22 -7
  21. package/src/lib/bindings/c.zig +27 -82
  22. package/src/lib/bindings/node.zig +7 -7
  23. package/src/lib/bindings/wasm.zig +6 -15
  24. package/src/lib/common/array.zig +5 -12
  25. package/src/lib/common/debug.zig +33 -59
  26. package/src/lib/common/js.zig +3 -5
  27. package/src/lib/common/optional.zig +11 -21
  28. package/src/lib/common/protocol.zig +224 -189
  29. package/src/lib/common/rational.zig +18 -38
  30. package/src/lib/common/util.zig +14 -46
  31. package/src/lib/gen1/calc.zig +121 -108
  32. package/src/lib/gen1/chance.zig +112 -75
  33. package/src/lib/gen1/data/types.zig +1 -1
  34. package/src/lib/gen1/data.zig +5 -5
  35. package/src/lib/gen1/helpers.zig +8 -10
  36. package/src/lib/gen1/mechanics.zig +31 -31
  37. package/src/lib/gen2/calc.zig +3 -6
  38. package/src/lib/gen2/chance.zig +12 -18
  39. package/src/lib/gen2/data.zig +5 -5
  40. package/src/lib/gen2/helpers.zig +4 -6
  41. package/src/lib/gen2/mechanics.zig +16 -16
  42. package/src/lib/napi.h +2 -0
  43. package/src/lib/node.zig +1 -1
  44. package/src/lib/pkmn.zig +91 -17
  45. package/src/lib/wasm.zig +2 -6
  46. package/src/tools/display/dom.ts +40 -25
  47. package/src/lib/common/napi.zig +0 -4
@@ -16,7 +16,6 @@ const Actions = chance.Actions;
16
16
  const assert = std.debug.assert;
17
17
  const Chance = chance.Chance;
18
18
  const Choice = common.Choice;
19
- const Confusion = chance.Confusion;
20
19
  const Duration = chance.Duration;
21
20
  const Durations = chance.Durations;
22
21
  const enabled = pkmn.options.calc;
@@ -35,9 +34,6 @@ const Sleeps = chance.Sleeps;
35
34
  const tty = true; // DEBUG
36
35
  const summary = false; // DEBUG
37
36
 
38
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
39
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
40
-
41
37
  /// Information relevant to damage calculation that occured during a Generation I battle `update`.
42
38
  pub const Summaries = extern struct {
43
39
  /// Relevant information for Player 1.
@@ -99,13 +95,13 @@ pub const Calc = struct {
99
95
  self: Calc,
100
96
  player: Player,
101
97
  comptime field: Action.Field,
102
- ) ?util.FieldType(Action, field) {
98
+ ) ?@FieldType(Action, @tagName(field)) {
103
99
  if (!enabled) return null;
104
100
 
105
101
  const val = @field(self.overrides.get(player), @tagName(field));
106
102
  return if (switch (@typeInfo(@TypeOf(val))) {
107
- Enum => val != .None,
108
- Int => val != 0,
103
+ .@"enum" => val != .None,
104
+ .int => val != 0,
109
105
  else => unreachable,
110
106
  }) val else null;
111
107
  }
@@ -146,7 +142,7 @@ const Null = struct {
146
142
  self: Null,
147
143
  player: Player,
148
144
  comptime field: Action.Field,
149
- ) ?util.FieldType(Action, field) {
145
+ ) ?@FieldType(Action, @tagName(field)) {
150
146
  _ = .{ self, player };
151
147
  return null;
152
148
  }
@@ -182,24 +178,25 @@ pub const Options = struct {
182
178
  metronome: bool = false,
183
179
  };
184
180
 
185
- pub const MAX_FRONTIER = 83;
181
+ pub const MAX_FRONTIER = 86;
186
182
 
187
183
  pub fn transitions(
188
184
  battle: anytype,
189
185
  c1: Choice,
190
186
  c2: Choice,
191
187
  allocator: std.mem.Allocator,
192
- writer: anytype,
188
+ out: *std.Io.Writer,
189
+ err: *std.Io.Writer,
193
190
  options: Options,
194
191
  ) !?Stats {
195
192
  var stats: Stats = .{};
196
193
 
197
194
  const cap = options.cap;
198
195
 
199
- var seen = std.AutoHashMap(Actions, void).init(allocator);
196
+ var seen: std.AutoHashMap(Actions, void) = .init(allocator);
200
197
  defer seen.deinit();
201
- var frontier = std.ArrayList(Actions).init(allocator);
202
- defer frontier.deinit();
198
+ var frontier: std.array_list.Aligned(Actions, null) = .empty;
199
+ defer frontier.deinit(allocator);
203
200
 
204
201
  const d = options.durations;
205
202
 
@@ -217,7 +214,7 @@ pub fn transitions(
217
214
  const p2 = b.side(.P2);
218
215
 
219
216
  var p: Rational(u256) = .{ .p = 0, .q = 1 };
220
- try frontier.append(opts.chance.actions);
217
+ try frontier.append(allocator, opts.chance.actions);
221
218
 
222
219
  // zig fmt: off
223
220
  for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
@@ -233,7 +230,7 @@ pub fn transitions(
233
230
 
234
231
  var r: Rational(u256) = .{ .p = 0, .q = 1 };
235
232
 
236
- try debug(writer, f, .{
233
+ try debug(out, f, .{
237
234
  .shape = true,
238
235
  .color = i,
239
236
  .bold = true,
@@ -280,8 +277,8 @@ pub fn transitions(
280
277
 
281
278
  var p2_dmg = Rolls.damage(f.p2, p2_hit);
282
279
 
283
- const p1_min: u9 = p1_dmg.min;
284
- const p2_min: u9 = p2_dmg.min;
280
+ const p1_min: u9 = @intCast(p1_dmg.min);
281
+ const p2_min: u9 = @intCast(p2_dmg.min);
285
282
 
286
283
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
287
284
  a.p2.damage = @intCast(p2_dmg.min);
@@ -298,7 +295,7 @@ pub fn transitions(
298
295
 
299
296
  const summaries = &opts.calc.summaries;
300
297
  const p1_max: u9 = if (p2_dmg.min != p2_min)
301
- p1_dmg.min
298
+ @intCast(p1_dmg.min)
302
299
  else
303
300
  Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
304
301
  const p2_max: u9 =
@@ -307,7 +304,7 @@ pub fn transitions(
307
304
  if (opts.chance.actions.matches(f)) {
308
305
  if (!opts.chance.actions.relax().eql(a)) {
309
306
  if (!summary) {
310
- try debug(writer, opts.chance.actions, .{
307
+ try debug(out, opts.chance.actions, .{
311
308
  .p1_max = p1_max,
312
309
  .p2_max = p2_max,
313
310
  .color = i,
@@ -321,7 +318,7 @@ pub fn transitions(
321
318
  }
322
319
 
323
320
  if (!summary) {
324
- try debug(writer, opts.chance.actions, .{
321
+ try debug(out, opts.chance.actions, .{
325
322
  .p1_max = p1_max,
326
323
  .p2_max = p2_max,
327
324
  .color = i,
@@ -334,7 +331,7 @@ pub fn transitions(
334
331
  acts.p1.damage = @intCast(p1d);
335
332
  acts.p2.damage = @intCast(p2d);
336
333
  if ((try seen.getOrPut(acts)).found_existing) {
337
- err("already seen {}", .{acts}, options.seed);
334
+ die(err, "already seen {f}", .{acts}, options.seed);
338
335
  return error.TestUnexpectedResult;
339
336
  }
340
337
  }
@@ -351,21 +348,21 @@ pub fn transitions(
351
348
  stats.saved += 1;
352
349
 
353
350
  if (p.q < p.p) {
354
- err("improper fraction {}", .{p}, options.seed);
351
+ die(err, "improper fraction {f}", .{p}, options.seed);
355
352
  return error.TestUnexpectedResult;
356
353
  }
357
354
  } else {
358
355
  if (!opts.chance.actions.matchesAny(frontier.items, i)) {
359
- try frontier.append(opts.chance.actions);
356
+ try frontier.append(allocator, opts.chance.actions);
360
357
 
361
- try debug(writer, opts.chance.actions, .{
358
+ try debug(out, opts.chance.actions, .{
362
359
  .p1_max = p1_max,
363
360
  .p2_max = p2_max,
364
361
  .dim = true,
365
362
  .newline = false,
366
363
  });
367
- try writer.writeAll(" → ");
368
- try debug(writer, opts.chance.actions, .{
364
+ try out.writeAll(" → ");
365
+ try debug(out, opts.chance.actions, .{
369
366
  .shape = true,
370
367
  .color = frontier.items.len - 1,
371
368
  .dim = true,
@@ -373,7 +370,7 @@ pub fn transitions(
373
370
  .indent = false,
374
371
  });
375
372
  } else if (!summary) {
376
- try debug(writer, opts.chance.actions, .{
373
+ try debug(out, opts.chance.actions, .{
377
374
  .p1_max = p1_max,
378
375
  .p2_max = p2_max,
379
376
  .dim = true,
@@ -388,15 +385,12 @@ pub fn transitions(
388
385
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}
389
386
 
390
387
  assert(stats.saved > saved);
391
- // TODO: ziglang/zig#22667
392
- // const old = @hasDecl(std.fmt, "formatFloatDecimal");
393
- // if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
394
- // try writer.print(
395
- // " {} ({d:.2}%)\n = ──────────\n {} ({d:.2}%)\n\n",
396
- // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
397
- // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
398
- // );
399
- // }
388
+ // TODO: LLVM - float_from_int from 'u256' without intrinsics
389
+ // try out.print(
390
+ // " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
391
+ // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
392
+ // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
393
+ // );
400
394
 
401
395
  }
402
396
 
@@ -411,7 +405,7 @@ pub fn transitions(
411
405
 
412
406
  p.reduce();
413
407
  if (p.p != 1 or p.q != 1) {
414
- err("expected 1, found {}", .{p}, options.seed);
408
+ die(err, "expected 1, found {f}", .{p}, options.seed);
415
409
  return error.TestExpectedEqual;
416
410
  }
417
411
 
@@ -424,7 +418,8 @@ pub fn update(
424
418
  c2: Choice,
425
419
  options: anytype,
426
420
  allocator: std.mem.Allocator,
427
- writer: anytype,
421
+ out: *std.Io.Writer,
422
+ err: *std.Io.Writer,
428
423
  transition: bool,
429
424
  ) !Result {
430
425
  const durations = options.chance.durations;
@@ -442,8 +437,8 @@ pub fn update(
442
437
  if (@TypeOf(battle.rng) == data.PRNG) {
443
438
  // Ensure we can encode the diffs in less than MAX_DIFFS bytes.
444
439
  var buf: [helpers.MAX_DIFFS]u8 = undefined;
445
- var stream: protocol.ByteStream = .{ .buffer = &buf };
446
- const n = try helpers.diff(&original, battle, stream.writer());
440
+ var pw: protocol.Writer = .{ .buffer = &buf };
441
+ const n = try helpers.diff(&original, battle, &pw);
447
442
 
448
443
  // Applying the diff to the battle should take us back to the original copy
449
444
  // of the battle (ignoring the RNG).
@@ -457,7 +452,7 @@ pub fn update(
457
452
  // Ensure we can generate all transitions from the same original state
458
453
  // (we must change the battle's RNG from a FixedRNG to a PRNG because
459
454
  // the transitions function relies on RNG for discovery of states)
460
- if (try transitions(unfix(original), c1, c2, allocator, writer, .{
455
+ if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
461
456
  .durations = durations,
462
457
  .cap = true,
463
458
  })) |stats| try expect(stats.frontier <= MAX_FRONTIER);
@@ -504,10 +499,9 @@ pub fn update(
504
499
  fn expectEqualActions(expected: Actions, actual: Actions) !void {
505
500
  return expectEqual(expected, actual) catch |e| switch (e) {
506
501
  error.TestExpectedEqual => {
507
- std.debug.print("expected {}, found {}\n", .{ expected, actual });
502
+ std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
508
503
  return e;
509
504
  },
510
- else => return e,
511
505
  };
512
506
  }
513
507
 
@@ -526,10 +520,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
526
520
  };
527
521
  }
528
522
 
529
- fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
530
- const w = std.io.getStdErr().writer();
523
+ fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
531
524
  w.print(fmt, v) catch return;
532
- if (seed) |s| return w.print("{}\n", .{s}) catch return;
525
+ if (seed) |s| return w.print("{d}\n", .{s}) catch return;
533
526
  return w.writeByte('\n') catch return;
534
527
  }
535
528
 
@@ -545,7 +538,7 @@ const Style = struct {
545
538
  indent: bool = true,
546
539
  };
547
540
 
548
- fn debug(writer: anytype, actions: Actions, style: Style) !void {
541
+ fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
549
542
  if (style.indent) try writer.writeAll(" ");
550
543
  if (tty) {
551
544
  const mod: usize = if (style.dim) 2 else 1;
@@ -570,13 +563,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
570
563
  if (style.newline) try writer.writeByte('\n');
571
564
  }
572
565
 
573
- fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
566
+ fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
574
567
  var input: [1024]u8 = undefined;
575
568
  var output: [1024]u8 = undefined;
576
569
 
577
- var stream = std.io.fixedBufferStream(&input);
578
- try actions.fmt(&stream.writer(), false);
579
- var len = stream.getWritten().len;
570
+ var w = std.Io.Writer.fixed(&input);
571
+ try actions.fmt(&w, false);
572
+ var len = w.buffered().len;
580
573
  @memcpy(output[0..len], input[0..len]);
581
574
 
582
575
  var find: [16]u8 = undefined;
@@ -609,80 +602,92 @@ pub const Rolls = struct {
609
602
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
610
603
  }
611
604
 
612
- const OBS_NONE = [_]Optional(Observation){.None};
613
- const OBS_STARTED = [_]Optional(Observation){.started};
614
- const OBS_CONTINUING = [_]Optional(Observation){.continuing};
615
- const OBS_ENDED = [_]Optional(Observation){.ended};
616
- const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
617
- const OBS = [_]Optional(Observation){ .continuing, .ended };
605
+ const SLP_NONE = [_]Optional(Observation(.sleep)){.None};
606
+ const SLP_STARTED = [_]Optional(Observation(.sleep)){.started};
607
+ const SLP_ENDED = [_]Optional(Observation(.sleep)){.ended};
608
+ const SLP_ALL = [_]Optional(Observation(.sleep)){ .started, .continuing, .ended };
609
+ const SLP = [_]Optional(Observation(.sleep)){ .continuing, .ended };
618
610
 
619
611
  /// Returns a slice with a range of values for sleep given the `action` state and observed
620
612
  /// `durations`.
621
- pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
622
- if (action.sleep == .None) return &OBS_NONE;
613
+ pub fn sleep(action: Action, duration: Duration) []const Optional(Observation(.sleep)) {
614
+ if (action.sleep == .None) return &SLP_NONE;
623
615
  return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
624
- &OBS_ALL
616
+ &SLP_ALL
625
617
  else switch (action.sleep) {
626
- .started => &OBS_STARTED,
627
- else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
618
+ .started => &SLP_STARTED,
619
+ else => if (Sleeps.get(duration.sleeps, 0) >= 7) &SLP_ENDED else &SLP,
628
620
  };
629
621
  }
630
622
 
623
+ const DIS_NONE = [_]Optional(Observation(.disable)){.None};
624
+ const DIS_STARTED = [_]Optional(Observation(.disable)){.started};
625
+ const DIS_CONTINUING = [_]Optional(Observation(.disable)){.continuing};
626
+ const DIS_ENDED = [_]Optional(Observation(.disable)){.ended};
627
+ const DIS_ALL = [_]Optional(Observation(.disable)){ .started, .continuing, .ended };
628
+ const DIS = [_]Optional(Observation(.disable)){ .continuing, .ended };
629
+
631
630
  /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
632
631
  /// and the state of the `parent` (observation of the player's Pokémon sleep status).
633
632
  pub fn disable(
634
633
  action: Action,
635
634
  duration: Duration,
636
- parent: Optional(Observation),
637
- ) []const Optional(Observation) {
638
- if (action.disable == .None) return &OBS_NONE;
635
+ parent: Optional(Observation(.sleep)),
636
+ ) []const Optional(Observation(.disable)) {
637
+ if (action.disable == .None) return &DIS_NONE;
639
638
  return if (duration.disable == 0 and action.speed_tie != .None)
640
- &OBS_ALL
639
+ &DIS_ALL
641
640
  else switch (action.disable) {
642
- .started => &OBS_STARTED,
641
+ .started => &DIS_STARTED,
643
642
  else => if (duration.disable >= 8)
644
- &OBS_ENDED
643
+ &DIS_ENDED
645
644
  else if (parent != .None and parent != .started)
646
- &OBS_CONTINUING
645
+ &DIS_CONTINUING
647
646
  else
648
- &OBS,
647
+ &DIS,
649
648
  };
650
649
  }
651
650
 
652
- /// FIXME
651
+ const ATK_NONE = [_]Optional(Observation(.attacking)){.None};
652
+ const ATK_STARTED = [_]Optional(Observation(.attacking)){.started};
653
+ const ATK_CONTINUING = [_]Optional(Observation(.attacking)){.continuing};
654
+ const ATK_ENDED = [_]Optional(Observation(.attacking)){.ended};
655
+ const ATK = [_]Optional(Observation(.attacking)){ .continuing, .ended };
656
+
657
+ /// FIXME document
653
658
  pub fn attacking(
654
659
  action: Action,
655
660
  duration: Duration,
656
- parent: Optional(Observation),
657
- ) []const Optional(Observation) {
661
+ parent: Optional(Observation(.sleep)),
662
+ ) []const Optional(Observation(.attacking)) {
658
663
  return switch (action.attacking) {
659
- .None => &OBS_NONE,
660
- .started => &OBS_STARTED,
664
+ .None => &ATK_NONE,
665
+ .started => &ATK_STARTED,
661
666
  else => if (duration.attacking >= 3)
662
- &OBS_ENDED
667
+ &ATK_ENDED
663
668
  else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
664
- &OBS_CONTINUING
669
+ &ATK_CONTINUING
665
670
  else
666
- &OBS,
671
+ &ATK,
667
672
  };
668
673
  }
669
674
 
670
- const CFZ_NONE = [_]Optional(Confusion){.None};
671
- const CFZ_STARTED = [_]Optional(Confusion){.started};
672
- const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
673
- const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
674
- const CFZ_ENDED = [_]Optional(Confusion){.ended};
675
- const CFZ = [_]Optional(Confusion){ .continuing, .ended };
676
- const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
675
+ const CFZ_NONE = [_]Optional(Observation(.confusion)){.None};
676
+ const CFZ_STARTED = [_]Optional(Observation(.confusion)){.started};
677
+ const CFZ_OVERWRITTEN = [_]Optional(Observation(.confusion)){ .started, .overwritten };
678
+ const CFZ_CONTINUING = [_]Optional(Observation(.confusion)){.continuing};
679
+ const CFZ_ENDED = [_]Optional(Observation(.confusion)){.ended};
680
+ const CFZ = [_]Optional(Observation(.confusion)){ .continuing, .ended };
681
+ const CFZ_TIE = [_]Optional(Observation(.confusion)){ .started, .continuing, .ended };
677
682
 
678
- /// FIXME
683
+ /// FIXME document
679
684
  pub fn confusion(
680
685
  action: Action,
681
686
  duration: Duration,
682
687
  tie: Optional(Player),
683
- sibling: Optional(Observation),
684
- parent: Optional(Observation),
685
- ) []const Optional(Confusion) {
688
+ sibling: Optional(Observation(.attacking)),
689
+ parent: Optional(Observation(.sleep)),
690
+ ) []const Optional(Observation(.confusion)) {
686
691
  return switch (action.confusion) {
687
692
  .None => &CFZ_NONE,
688
693
  .started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
@@ -700,18 +705,23 @@ pub const Rolls = struct {
700
705
  };
701
706
  }
702
707
 
708
+ const BND_NONE = [_]Optional(Observation(.binding)){.None};
709
+ const BND_STARTED = [_]Optional(Observation(.binding)){.started};
710
+ const BND_ENDED = [_]Optional(Observation(.binding)){.ended};
711
+ const BND = [_]Optional(Observation(.binding)){ .continuing, .ended };
712
+
703
713
  /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
704
714
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
705
715
  pub fn binding(
706
716
  action: Action,
707
717
  duration: Duration,
708
718
  parent: Optional(bool),
709
- ) []const Optional(Observation) {
710
- if (parent == .true) return &OBS_NONE;
719
+ ) []const Optional(Observation(.binding)) {
720
+ if (parent == .true) return &BND_NONE;
711
721
  return switch (action.binding) {
712
- .None => &OBS_NONE,
713
- .started => &OBS_STARTED,
714
- else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
722
+ .None => &BND_NONE,
723
+ .started => &BND_STARTED,
724
+ else => if (duration.binding >= 4) &BND_ENDED else &BND,
715
725
  };
716
726
  }
717
727
 
@@ -740,7 +750,7 @@ pub const Rolls = struct {
740
750
  }
741
751
 
742
752
  /// The min and max bounds on iteration over damage rolls.
743
- pub const Range = struct { min: u9, max: u9 };
753
+ pub const Range = struct { min: usize, max: usize };
744
754
 
745
755
  /// Returns the range bounding damage rolls given the `action` state and the state of
746
756
  /// the `parent` (whether the player's Pokémon's move hit).
@@ -765,7 +775,10 @@ pub const Rolls = struct {
765
775
 
766
776
  /// Returns a slice with the correct range of values for confused given the `action` state and
767
777
  /// the state of the `parent` (observation of the player's Pokémon confusion status).
768
- pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
778
+ pub fn confused(
779
+ action: Action,
780
+ parent: Optional(Observation(.confusion)),
781
+ ) []const Optional(bool) {
769
782
  const done = parent == .None or parent == .ended;
770
783
  return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
771
784
  }
@@ -777,24 +790,24 @@ pub const Rolls = struct {
777
790
  return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
778
791
  }
779
792
 
780
- const SLOT_NONE = [_]u4{0};
781
- const SLOT = [_]u4{ 1, 2, 3, 4 };
793
+ const SLOT_NONE = [_]u3{0};
794
+ const SLOT = [_]u3{ 1, 2, 3, 4 };
782
795
 
783
796
  /// Returns a slice with a range of values for move slots given the `action` state
784
797
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
785
798
  ///
786
799
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
787
- pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
800
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
788
801
  if (parent == .false) return &SLOT_NONE;
789
802
  return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
790
803
  }
791
804
 
792
- const MULTI_NONE = [_]u4{0};
793
- const MULTI = [_]u4{ 2, 3, 4, 5 };
805
+ const MULTI_NONE = [_]u3{0};
806
+ const MULTI = [_]u3{ 2, 3, 4, 5 };
794
807
 
795
808
  /// Returns a slice with the correct range of values for multi hit given the `action` state
796
809
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
797
- pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
810
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
798
811
  if (parent == .false) return &MULTI_NONE;
799
812
  return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
800
813
  }
@@ -926,16 +939,16 @@ test "Rolls.paralyzed" {
926
939
 
927
940
  test "Rolls.moveSlot" {
928
941
  const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
929
- try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
930
- try expectEqualSlices(u4, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
931
- try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p2, .false));
942
+ try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p1, .None));
943
+ try expectEqualSlices(u3, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
944
+ try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p2, .false));
932
945
  }
933
946
 
934
947
  test "Rolls.multiHit" {
935
948
  const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
936
- try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p1, .None));
937
- try expectEqualSlices(u4, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
938
- try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p2, .false));
949
+ try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p1, .None));
950
+ try expectEqualSlices(u3, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
951
+ try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p2, .false));
939
952
  }
940
953
 
941
954
  test "Rolls.metronome" {