@pkmn/engine 0.1.0-dev.30895c5a → 0.1.0-dev.3158fa12

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 (78) hide show
  1. package/README.md +9 -6
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +44 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +97 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -3
  12. package/build/pkg/addon.js +34 -65
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +13 -17
  19. package/build/pkg/gen1.js +6 -10
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +37 -89
  22. package/build/pkg/index.js +6 -63
  23. package/build/pkg/index.js.map +1 -1
  24. package/build/pkg/protocol.d.ts +20 -1
  25. package/build/pkg/protocol.js +105 -103
  26. package/build/pkg/protocol.js.map +1 -1
  27. package/build/tools/debug.js +35 -69
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +141 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +142 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +349 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +18 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +283 -77
  38. package/package.json +52 -134
  39. package/src/bin/install-pkmn-engine +7 -5
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/bindings/node.zig +121 -0
  42. package/src/lib/bindings/wasm.zig +59 -0
  43. package/src/lib/common/array.zig +77 -0
  44. package/src/lib/common/data.zig +8 -8
  45. package/src/lib/common/debug.zig +39 -8
  46. package/src/lib/common/js.zig +4 -2
  47. package/src/lib/common/optional.zig +13 -9
  48. package/src/lib/common/options.zig +4 -0
  49. package/src/lib/common/protocol.zig +44 -43
  50. package/src/lib/common/rational.zig +45 -50
  51. package/src/lib/common/rng.zig +5 -7
  52. package/src/lib/common/util.zig +50 -1
  53. package/src/lib/gen1/calc.zig +213 -62
  54. package/src/lib/gen1/chance.zig +102 -71
  55. package/src/lib/gen1/data/moves.zig +2 -4
  56. package/src/lib/gen1/data/species.zig +1 -3
  57. package/src/lib/gen1/data/types.zig +1 -2
  58. package/src/lib/gen1/data.zig +35 -38
  59. package/src/lib/gen1/helpers.zig +15 -27
  60. package/src/lib/gen1/mechanics.zig +109 -84
  61. package/src/lib/gen2/calc.zig +36 -18
  62. package/src/lib/gen2/chance.zig +86 -66
  63. package/src/lib/gen2/data/items.zig +1 -3
  64. package/src/lib/gen2/data/moves.zig +2 -4
  65. package/src/lib/gen2/data/species.zig +1 -3
  66. package/src/lib/gen2/data/types.zig +1 -2
  67. package/src/lib/gen2/data.zig +26 -29
  68. package/src/lib/gen2/generated.zig +18 -23
  69. package/src/lib/gen2/helpers.zig +9 -18
  70. package/src/lib/gen2/mechanics.zig +81 -81
  71. package/src/lib/node.zig +3 -116
  72. package/src/lib/pkmn.zig +12 -2
  73. package/src/lib/wasm.zig +13 -9
  74. package/src/tools/display/dom.ts +96 -0
  75. package/src/tools/display/ui.css +354 -0
  76. package/build/test/display.js +0 -405
  77. package/build/test/display.js.map +0 -1
  78. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,44 +1,43 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
1
+ const DEBUG = @import("../common/debug.zig").print;
4
2
 
3
+ const chance = @import("chance.zig");
5
4
  const common = @import("../common/data.zig");
6
- const DEBUG = @import("../common/debug.zig").print;
5
+ const data = @import("data.zig");
6
+ const helpers = @import("helpers.zig");
7
7
  const optional = @import("../common/optional.zig");
8
+ const pkmn = @import("../pkmn.zig");
8
9
  const protocol = @import("../common/protocol.zig");
9
10
  const rational = @import("../common/rational.zig");
11
+ const std = @import("std");
10
12
  const util = @import("../common/util.zig");
11
13
 
12
- const chance = @import("chance.zig");
13
- const data = @import("data.zig");
14
- const helpers = @import("helpers.zig");
15
-
14
+ const Action = chance.Action;
15
+ const Actions = chance.Actions;
16
16
  const assert = std.debug.assert;
17
-
17
+ const Chance = chance.Chance;
18
+ const Choice = common.Choice;
19
+ const Confusion = chance.Confusion;
20
+ const Duration = chance.Duration;
21
+ const Durations = chance.Durations;
22
+ const enabled = pkmn.options.calc;
18
23
  const expect = std.testing.expect;
19
24
  const expectEqual = std.testing.expectEqual;
20
25
  const expectEqualSlices = std.testing.expectEqualSlices;
21
-
22
- const enabled = pkmn.options.calc;
23
-
24
- const Player = common.Player;
25
- const Result = common.Result;
26
- const Choice = common.Choice;
27
-
26
+ const isPointerTo = util.isPointerTo;
27
+ const Observation = chance.Observation;
28
28
  const Optional = optional.Optional;
29
-
30
- const Rational = rational.Rational;
31
-
29
+ const Player = common.Player;
32
30
  const PointerType = util.PointerType;
33
-
34
- const Actions = chance.Actions;
35
- const Action = chance.Action;
36
- const Chance = chance.Chance;
37
- const Durations = chance.Durations;
31
+ const Rational = rational.Rational;
32
+ const Result = common.Result;
33
+ const Sleeps = chance.Sleeps;
38
34
 
39
35
  const tty = true; // DEBUG
40
36
  const summary = false; // DEBUG
41
37
 
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
+
42
41
  /// Information relevant to damage calculation that occured during a Generation I battle `update`.
43
42
  pub const Summaries = extern struct {
44
43
  /// Relevant information for Player 1.
@@ -52,7 +51,7 @@ pub const Summaries = extern struct {
52
51
 
53
52
  /// Returns the `Summary` for the given `player`.
54
53
  pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
55
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Summaries);
54
+ assert(isPointerTo(self, Summaries));
56
55
  return if (player == .P1) &self.p1 else &self.p2;
57
56
  }
58
57
  };
@@ -105,12 +104,20 @@ pub const Calc = struct {
105
104
 
106
105
  const val = @field(self.overrides.get(player), @tagName(field));
107
106
  return if (switch (@typeInfo(@TypeOf(val))) {
108
- .Enum => val != .None,
109
- .Int => val != 0,
107
+ Enum => val != .None,
108
+ Int => val != 0,
110
109
  else => unreachable,
111
110
  }) val else null;
112
111
  }
113
112
 
113
+ pub fn confusion(self: *Calc, player: Player) void {
114
+ if (!enabled) return;
115
+
116
+ if (self.overrides.get(player).confusion == .started) {
117
+ self.overrides.get(player).confusion = .None;
118
+ }
119
+ }
120
+
114
121
  pub fn base(self: *Calc, player: Player, val: u16) void {
115
122
  if (!enabled) return;
116
123
 
@@ -144,6 +151,10 @@ const Null = struct {
144
151
  return null;
145
152
  }
146
153
 
154
+ pub fn confusion(self: Null, player: Player) void {
155
+ _ = .{ self, player };
156
+ }
157
+
147
158
  pub fn base(self: Null, player: Player, val: u16) void {
148
159
  _ = .{ self, player, val };
149
160
  }
@@ -171,7 +182,7 @@ pub const Options = struct {
171
182
  metronome: bool = false,
172
183
  };
173
184
 
174
- pub const MAX_FRONTIER = 19;
185
+ pub const MAX_FRONTIER = 81;
175
186
 
176
187
  pub fn transitions(
177
188
  battle: anytype,
@@ -190,9 +201,11 @@ pub fn transitions(
190
201
  var frontier = std.ArrayList(Actions).init(allocator);
191
202
  defer frontier.deinit();
192
203
 
204
+ const d = options.durations;
205
+
193
206
  var opts = pkmn.battle.options(
194
207
  protocol.NULL,
195
- Chance(Rational(u128)){ .probability = .{}, .durations = options.durations },
208
+ Chance(Rational(u128)){ .probability = .{}, .durations = d },
196
209
  Calc{},
197
210
  );
198
211
 
@@ -216,6 +229,7 @@ pub fn transitions(
216
229
  assert(frontier.items.len == 1);
217
230
  while (i < frontier.items.len) : (i += 1) {
218
231
  const f = frontier.items[i];
232
+ const saved = stats.saved;
219
233
 
220
234
  try debug(writer, f, .{
221
235
  .shape = true,
@@ -231,10 +245,20 @@ pub fn transitions(
231
245
  };
232
246
 
233
247
  for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
234
- for (Rolls.confused(f.p1)) |p1_cfzd| { a.p1.confused = p1_cfzd;
235
- for (Rolls.confused(f.p2)) |p2_cfzd| { a.p2.confused = p2_cfzd;
248
+ for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
249
+ for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
250
+ for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
251
+ for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
252
+ for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
253
+ for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
254
+ for (Rolls.confusion(f.p1, d.p1, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
255
+ for (Rolls.confusion(f.p2, d.p2, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
256
+ for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
257
+ for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
236
258
  for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
237
259
  for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
260
+ for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
261
+ for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
238
262
  for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
239
263
  for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
240
264
  for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
@@ -262,7 +286,7 @@ pub fn transitions(
262
286
 
263
287
  opts.calc.overrides = a;
264
288
  opts.calc.summaries = .{};
265
- opts.chance = .{ .probability = .{}, .durations = options.durations };
289
+ opts.chance = .{ .probability = .{}, .durations = d };
266
290
  const q = &opts.chance.probability;
267
291
 
268
292
  b = battle;
@@ -279,7 +303,7 @@ pub fn transitions(
279
303
  try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
280
304
 
281
305
  if (opts.chance.actions.matches(f)) {
282
- if (!opts.chance.actions.eql(a)) {
306
+ if (!opts.chance.actions.relax().eql(a)) {
283
307
  if (!summary) {
284
308
  try debug(writer, opts.chance.actions, .{
285
309
  .p1_max = p1_max,
@@ -313,10 +337,13 @@ pub fn transitions(
313
337
  }
314
338
  }
315
339
  }
316
-
317
340
  if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
318
341
  if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
342
+
343
+ q.reduce();
319
344
  try p.add(q);
345
+ p.reduce();
346
+
320
347
  stats.saved += 1;
321
348
 
322
349
  if (p.q < p.p) {
@@ -324,7 +351,7 @@ pub fn transitions(
324
351
  return error.TestUnexpectedResult;
325
352
  }
326
353
  } else {
327
- if (!matches(opts.chance.actions, i, frontier.items)) {
354
+ if (!opts.chance.actions.matchesAny(frontier.items, i)) {
328
355
  try frontier.append(opts.chance.actions);
329
356
 
330
357
  try debug(writer, opts.chance.actions, .{
@@ -354,9 +381,11 @@ pub fn transitions(
354
381
  p2_dmg.min = p2_max;
355
382
  }
356
383
 
357
- }}}}}}}}}}}}}}}}}}
384
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}
358
385
 
359
- if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
386
+ assert(stats.saved > saved);
387
+ const old = @hasDecl(std.fmt, "formatFloatDecimal");
388
+ if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
360
389
  p.reduce();
361
390
  try writer.print(
362
391
  " = {s} ({d:.2}%)\n\n",
@@ -492,15 +521,6 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
492
521
  };
493
522
  }
494
523
 
495
- fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
496
- for (frontier, 0..) |f, j| {
497
- // TODO: is skipping this redundant check worth it?
498
- if (i == j) continue;
499
- if (f.matches(actions)) return true;
500
- }
501
- return false;
502
- }
503
-
504
524
  fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
505
525
  const w = std.io.getStdErr().writer();
506
526
  w.print(fmt, v) catch return;
@@ -573,8 +593,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
573
593
  try writer.writeAll(output[0..len]);
574
594
  }
575
595
 
576
- const NONE = @intFromEnum(Optional(bool).None);
577
-
578
596
  /// Helper functions that efficiently return valid ranges for various RNG events based on the
579
597
  /// state of an `Action` and other events to be used to construct a "transitions" function.
580
598
  pub const Rolls = struct {
@@ -586,6 +604,108 @@ pub const Rolls = struct {
586
604
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
587
605
  }
588
606
 
607
+ const OBS_NONE = [_]Optional(Observation){.None};
608
+ const OBS_STARTED = [_]Optional(Observation){.started};
609
+ const OBS_CONTINUING = [_]Optional(Observation){.continuing};
610
+ const OBS_ENDED = [_]Optional(Observation){.ended};
611
+ const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
612
+ const OBS = [_]Optional(Observation){ .continuing, .ended };
613
+
614
+ /// Returns a slice with a range of values for sleep given the `action` state and observed
615
+ /// `durations`.
616
+ pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
617
+ if (action.sleep == .None) return &OBS_NONE;
618
+ return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
619
+ &OBS_ALL
620
+ else switch (action.sleep) {
621
+ .started => &OBS_STARTED,
622
+ else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
623
+ };
624
+ }
625
+
626
+ /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
627
+ /// and the state of the `parent` (observation of the player's Pokémon sleep status).
628
+ pub fn disable(
629
+ action: Action,
630
+ duration: Duration,
631
+ parent: Optional(Observation),
632
+ ) []const Optional(Observation) {
633
+ if (action.disable == .None) return &OBS_NONE;
634
+ return if (duration.disable == 0 and action.speed_tie != .None)
635
+ &OBS_ALL
636
+ else switch (action.disable) {
637
+ .started => &OBS_STARTED,
638
+ else => if (duration.disable >= 8)
639
+ &OBS_ENDED
640
+ else if (parent != .None and parent != .started)
641
+ &OBS_CONTINUING
642
+ else
643
+ &OBS,
644
+ };
645
+ }
646
+
647
+ /// FIXME
648
+ pub fn attacking(
649
+ action: Action,
650
+ duration: Duration,
651
+ parent: Optional(Observation),
652
+ ) []const Optional(Observation) {
653
+ return switch (action.attacking) {
654
+ .None => &OBS_NONE,
655
+ .started => &OBS_STARTED,
656
+ else => if (duration.attacking >= 3)
657
+ &OBS_ENDED
658
+ else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
659
+ &OBS_CONTINUING
660
+ else
661
+ &OBS,
662
+ };
663
+ }
664
+
665
+ const CFZ_NONE = [_]Optional(Confusion){.None};
666
+ const CFZ_STARTED = [_]Optional(Confusion){.started};
667
+ const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
668
+ const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
669
+ const CFZ_ENDED = [_]Optional(Confusion){.ended};
670
+ const CFZ = [_]Optional(Confusion){ .continuing, .ended };
671
+
672
+ /// FIXME
673
+ pub fn confusion(
674
+ action: Action,
675
+ duration: Duration,
676
+ sibling: Optional(Observation),
677
+ parent: Optional(Observation),
678
+ ) []const Optional(Confusion) {
679
+ return switch (action.confusion) {
680
+ .None => &CFZ_NONE,
681
+ .started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
682
+ &CFZ_OVERWRITTEN
683
+ else
684
+ &CFZ_STARTED,
685
+ else => if (duration.confusion >= 5)
686
+ &CFZ_ENDED
687
+ else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
688
+ &CFZ_CONTINUING
689
+ else
690
+ &CFZ,
691
+ };
692
+ }
693
+
694
+ /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
695
+ /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
696
+ pub fn binding(
697
+ action: Action,
698
+ duration: Duration,
699
+ parent: Optional(bool),
700
+ ) []const Optional(Observation) {
701
+ if (parent == .true) return &OBS_NONE;
702
+ return switch (action.binding) {
703
+ .None => &OBS_NONE,
704
+ .started => &OBS_STARTED,
705
+ else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
706
+ };
707
+ }
708
+
589
709
  const BOOL_NONE = [_]Optional(bool){.None};
590
710
  const BOOLS = [_]Optional(bool){ .false, .true };
591
711
 
@@ -599,7 +719,7 @@ pub const Rolls = struct {
599
719
  /// Returns a slice with the correct range of values for critical hits given the `action` state
600
720
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
601
721
  pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
602
- if (parent == .false) return &BOOL_NONE;
722
+ if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
603
723
  return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
604
724
  }
605
725
 
@@ -634,9 +754,11 @@ pub const Rolls = struct {
634
754
  return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
635
755
  }
636
756
 
637
- /// Returns a slice with the correct range of values for confused.
638
- pub fn confused(action: Action) []const Optional(bool) {
639
- return if (action.confused == .None) &BOOL_NONE else &BOOLS;
757
+ /// Returns a slice with the correct range of values for confused given the `action` state and
758
+ /// the state of the `parent` (observation of the player's Pokémon confusion status).
759
+ pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
760
+ const done = parent == .None or parent == .ended;
761
+ return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
640
762
  }
641
763
 
642
764
  /// Returns a slice with the correct range of values for paralysis given the `action` state
@@ -703,6 +825,26 @@ test "Rolls.speedTie" {
703
825
  try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
704
826
  }
705
827
 
828
+ test "Rolls.sleep" {
829
+ return error.SkipZigTest; // TODO
830
+ }
831
+
832
+ test "Rolls.disable" {
833
+ return error.SkipZigTest; // TODO
834
+ }
835
+
836
+ test "Rolls.confusion" {
837
+ return error.SkipZigTest; // TODO
838
+ }
839
+
840
+ test "Rolls.atacking" {
841
+ return error.SkipZigTest; // TODO
842
+ }
843
+
844
+ test "Rolls.binding" {
845
+ return error.SkipZigTest; // TODO
846
+ }
847
+
706
848
  test "Rolls.damage" {
707
849
  const actions: Actions = .{ .p2 = .{ .damage = 221 } };
708
850
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
@@ -712,12 +854,12 @@ test "Rolls.damage" {
712
854
 
713
855
  test "Rolls.coalesce" {
714
856
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
715
- try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
716
- try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
717
- try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
857
+ try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
858
+ try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
859
+ try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
718
860
  summaries.p1.damage.final = 74;
719
- try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
720
- try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
861
+ try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
862
+ try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
721
863
  }
722
864
 
723
865
  test "Rolls.hit" {
@@ -746,14 +888,23 @@ test "Rolls.criticalHit" {
746
888
  &.{ .false, .true },
747
889
  Rolls.criticalHit(actions.p1, .None),
748
890
  );
749
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p1, .false));
891
+ try expectEqualSlices(
892
+ Optional(bool),
893
+ if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
894
+ Rolls.criticalHit(actions.p1, .false),
895
+ );
750
896
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
751
897
  }
752
898
 
753
899
  test "Rolls.confused" {
754
900
  const actions: Actions = .{ .p2 = .{ .confused = .true } };
755
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
756
- try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2));
901
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
902
+ try expectEqualSlices(
903
+ Optional(bool),
904
+ &.{ .false, .true },
905
+ Rolls.confused(actions.p2, .continuing),
906
+ );
907
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
757
908
  }
758
909
 
759
910
  test "Rolls.paralyzed" {
@@ -794,9 +945,9 @@ test "Rolls.psywave" {
794
945
 
795
946
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
796
947
  var rolls = Rolls.psywave(actions.p2, &side, .None);
797
- try expectEqual(150, rolls[rolls.len - 1]);
948
+ try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
798
949
  side.stored().level = 81;
799
950
  rolls = Rolls.psywave(actions.p2, &side, .None);
800
- try expectEqual(121, rolls[rolls.len - 1]);
951
+ try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
801
952
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
802
953
  }