@pkmn/engine 0.1.0-dev.683fba11 → 0.1.0-dev.6b383c80

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 (61) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -16
  3. package/build/pkg/addon/browser.js +6 -3
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +23 -10
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.js +23 -14
  8. package/build/pkg/addon/wasm.js.map +1 -1
  9. package/build/pkg/addon.d.ts +5 -5
  10. package/build/pkg/addon.js.map +1 -1
  11. package/build/pkg/data.js.map +1 -1
  12. package/build/pkg/gen1.d.ts +2 -1
  13. package/build/pkg/gen1.js +22 -9
  14. package/build/pkg/gen1.js.map +1 -1
  15. package/build/pkg/index.d.ts +2 -0
  16. package/build/pkg/index.js +17 -7
  17. package/build/pkg/index.js.map +1 -1
  18. package/build/pkg/protocol.d.ts +11 -2
  19. package/build/pkg/protocol.js.map +1 -1
  20. package/build/tools/debug.js +54 -11
  21. package/build/tools/debug.js.map +1 -1
  22. package/build/tools/display/debug.jsx +19 -94
  23. package/build/tools/display/debug.jsx.map +1 -1
  24. package/build/tools/display/index.js +18 -7
  25. package/build/tools/display/index.js.map +1 -1
  26. package/build/tools/display/ui.jsx +20 -9
  27. package/build/tools/display/ui.jsx.map +1 -1
  28. package/build/tools/display/util.js +140 -1
  29. package/build/tools/display/util.js.map +1 -1
  30. package/build.zig +255 -313
  31. package/package.json +29 -131
  32. package/src/bin/install-pkmn-engine +24 -22
  33. package/src/lib/bindings/c.zig +201 -0
  34. package/src/lib/bindings/node.zig +117 -0
  35. package/src/lib/bindings/wasm.zig +51 -0
  36. package/src/lib/common/array.zig +9 -16
  37. package/src/lib/common/data.zig +8 -8
  38. package/src/lib/common/debug.zig +46 -61
  39. package/src/lib/common/js.zig +3 -9
  40. package/src/lib/common/optional.zig +20 -25
  41. package/src/lib/common/protocol.zig +224 -189
  42. package/src/lib/common/rational.zig +275 -284
  43. package/src/lib/common/rng.zig +4 -4
  44. package/src/lib/common/util.zig +34 -31
  45. package/src/lib/gen1/calc.zig +141 -119
  46. package/src/lib/gen1/chance.zig +162 -101
  47. package/src/lib/gen1/data/types.zig +2 -2
  48. package/src/lib/gen1/data.zig +27 -24
  49. package/src/lib/gen1/helpers.zig +36 -26
  50. package/src/lib/gen1/mechanics.zig +45 -49
  51. package/src/lib/gen2/calc.zig +3 -6
  52. package/src/lib/gen2/chance.zig +55 -43
  53. package/src/lib/gen2/data/types.zig +6 -3
  54. package/src/lib/gen2/data.zig +20 -16
  55. package/src/lib/gen2/helpers.zig +27 -19
  56. package/src/lib/gen2/mechanics.zig +17 -17
  57. package/src/lib/napi.h +2 -0
  58. package/src/lib/node.zig +3 -118
  59. package/src/lib/pkmn.zig +94 -19
  60. package/src/lib/wasm.zig +9 -44
  61. package/src/tools/display/dom.ts +43 -31
@@ -23,10 +23,6 @@ const PointerType = util.PointerType;
23
23
  const print = std.debug.print;
24
24
  const showdown = options.showdown;
25
25
 
26
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
28
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
29
-
30
26
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
31
27
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
32
28
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -49,22 +45,36 @@ pub const Actions = extern struct {
49
45
 
50
46
  /// Returns true if `a` is equal to `b`.
51
47
  pub fn eql(a: Actions, b: Actions) bool {
52
- return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
48
+ return @as(u64, @bitCast(a.p1)) == @as(u64, @bitCast(b.p1)) and
49
+ @as(u64, @bitCast(a.p2)) == @as(u64, @bitCast(b.p2));
53
50
  }
54
51
 
55
52
  /// Returns true if `a` has the same "shape" as `b`, where `Actions` are defined to have the
56
53
  /// same shape if they have the same fields set (though those fields need not necessarily be
57
54
  /// set to the same value).
58
55
  pub fn matches(a: Actions, b: Actions) bool {
59
- inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
60
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
61
- const a_val = @field(@field(a, player.name), field.name);
62
- const b_val = @field(@field(b, player.name), field.name);
56
+ inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
57
+ @typeInfo(Actions).@"struct".fields
58
+ else
59
+ @typeInfo(Actions).@"struct".field_names) |player|
60
+ {
61
+ const player_name = if (@hasField(@TypeOf(player), "name"))
62
+ player.name
63
+ else
64
+ player;
65
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
66
+ @typeInfo(Action).@"struct".fields
67
+ else
68
+ @typeInfo(Action).@"struct".field_names) |field|
69
+ {
70
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
71
+ const a_val = @field(@field(a, player_name), field_name);
72
+ const b_val = @field(@field(b, player_name), field_name);
63
73
 
64
74
  switch (@typeInfo(@TypeOf(a_val))) {
65
- Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
75
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
66
76
  return false,
67
- Int => if ((a_val > 0) != (b_val > 0)) return false,
77
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
68
78
  else => unreachable,
69
79
  }
70
80
  }
@@ -91,7 +101,7 @@ pub const Actions = extern struct {
91
101
  return a;
92
102
  }
93
103
 
94
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
104
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
95
105
  try writer.writeAll("<P1 = ");
96
106
  try self.p1.fmt(writer, shape);
97
107
  try writer.writeAll(", P2 = ");
@@ -99,8 +109,7 @@ pub const Actions = extern struct {
99
109
  try writer.writeAll(">");
100
110
  }
101
111
 
102
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
103
- _ = .{ f, o };
112
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
104
113
  try fmt(a, w, false);
105
114
  }
106
115
  };
@@ -124,18 +133,41 @@ test Actions {
124
133
  }
125
134
 
126
135
  /// Observation made about a duration - whether the duration has started, been continued, or ended.
127
- pub const Observation = enum { started, continuing, ended };
128
- /// An extension of Observation to account for "hidden" confusion.
129
- pub const Confusion = enum {
130
- started,
131
- continuing,
132
- ended,
133
- /// An indicator that an existing confusion duration has been overwritten by a new confusion
134
- /// duration due to a Thrashing move ending. This is not immediately observable to an opponent,
135
- /// to compute probabilities from an opponent's point of view based solely on public information
136
- /// extra work must be done to correct for the information leak
137
- overwritten,
138
- };
136
+ pub fn Observation(comptime field: Duration.Field) type {
137
+ return switch (field) {
138
+ .attacking, .binding => enum(u2) {
139
+ started,
140
+ continuing,
141
+ ended,
142
+ },
143
+ .sleep, .disable => enum(u3) {
144
+ started,
145
+ continuing,
146
+ ended,
147
+ // TODO haze support
148
+ unused1,
149
+ unused2,
150
+ unused3,
151
+ unused4,
152
+ },
153
+ .confusion => enum(u4) {
154
+ started,
155
+ continuing,
156
+ ended,
157
+ /// An indicator that an existing confusion duration has been overwritten by a new
158
+ /// confusion duration due to a Thrashing move ending ("hidden" confusion). This is not
159
+ /// immediately observable to an opponent, to compute probabilities from an opponent's
160
+ /// point of view based solely on public information extra work must be done to correct
161
+ /// for the information leak
162
+ overwritten,
163
+ // TODO haze support
164
+ unused1,
165
+ unused2,
166
+ unused3,
167
+ unused4,
168
+ },
169
+ };
170
+ }
139
171
 
140
172
  /// Information about the RNG that was observed during a Generation I battle `update` for a
141
173
  /// single player.
@@ -162,26 +194,24 @@ pub const Action = packed struct(u64) {
162
194
  duration: u4 = 0,
163
195
 
164
196
  /// TODO
165
- sleep: Optional(Observation) = .None,
197
+ sleep: Optional(Observation(.sleep)) = .None,
166
198
  /// TODO
167
- confusion: Optional(Confusion) = .None,
199
+ confusion: Optional(Observation(.confusion)) = .None,
168
200
  /// TODO
169
- disable: Optional(Observation) = .None,
201
+ disable: Optional(Observation(.disable)) = .None,
170
202
  /// TODO
171
- attacking: Optional(Observation) = .None,
203
+ attacking: Optional(Observation(.attacking)) = .None,
172
204
  /// TODO
173
- binding: Optional(Observation) = .None,
174
-
175
- _: u1 = 0,
205
+ binding: Optional(Observation(.binding)) = .None,
176
206
 
177
207
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
178
208
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
179
- move_slot: u4 = 0,
209
+ move_slot: u3 = 0,
210
+ /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
211
+ multi_hit: u3 = 0,
180
212
 
181
213
  /// TODO
182
214
  pp: u4 = 0,
183
- /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
184
- multi_hit: u4 = 0,
185
215
 
186
216
  /// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
187
217
  psywave: u8 = 0,
@@ -191,44 +221,45 @@ pub const Action = packed struct(u64) {
191
221
 
192
222
  pub const Field = std.meta.FieldEnum(Action);
193
223
 
194
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
195
- _ = .{ f, o };
224
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
196
225
  try fmt(a, w, false);
197
226
  }
198
227
 
199
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
228
+ const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
229
+
230
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
200
231
  try writer.writeByte('(');
201
232
  var printed = false;
202
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
203
- const val = @field(self, field.name);
233
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
234
+ @typeInfo(Action).@"struct".fields
235
+ else
236
+ @typeInfo(Action).@"struct".field_names) |field|
237
+ {
238
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
239
+ const val = @field(self, field_name);
204
240
  switch (@typeInfo(@TypeOf(val))) {
205
- Enum => if (val != .None) {
241
+ .@"enum" => if (val != .None) {
206
242
  if (printed) try writer.writeAll(", ");
207
243
  if (shape) {
208
- try writer.print("{s}:?", .{field.name});
244
+ try writer.print("{s}:?", .{field_name});
209
245
  } else if (@TypeOf(val) == Optional(bool)) {
210
246
  try writer.print("{s}{s}", .{
211
247
  if (val == .false) "!" else "",
212
- field.name,
213
- });
214
- } else if (@TypeOf(val) == Optional(Observation) or
215
- @TypeOf(val) == Optional(Confusion))
216
- {
217
- try writer.print("{s}{s}", .{
218
- ([_][]const u8{ "+", "", "-", "#" })[@intFromEnum(val) - 1],
219
- field.name,
248
+ field_name,
220
249
  });
250
+ } else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
251
+ try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field_name });
221
252
  } else {
222
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
253
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
223
254
  }
224
255
  printed = true;
225
256
  },
226
- Int => if (val != 0) {
257
+ .int => if (val != 0) {
227
258
  if (printed) try writer.writeAll(", ");
228
259
  if (shape) {
229
- try writer.print("{s}:?", .{field.name});
260
+ try writer.print("{s}:?", .{field_name});
230
261
  } else {
231
- try writer.print("{s}:{d}", .{ field.name, val });
262
+ try writer.print("{s}:{d}", .{ field_name, val });
232
263
  }
233
264
  printed = true;
234
265
  },
@@ -256,14 +287,8 @@ pub const Durations = extern struct {
256
287
  return if (player == .P1) &self.p1 else &self.p2;
257
288
  }
258
289
 
259
- pub fn format(
260
- self: Durations,
261
- comptime fmt: []const u8,
262
- opts: std.fmt.FormatOptions,
263
- writer: anytype,
264
- ) !void {
265
- _ = .{ fmt, opts };
266
- try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
290
+ pub fn format(self: Durations, writer: *std.Io.Writer) !void {
291
+ try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
267
292
  }
268
293
  };
269
294
 
@@ -279,29 +304,33 @@ pub const Duration = packed struct(u32) {
279
304
 
280
305
  _: u1 = 0,
281
306
 
307
+ pub const Field = enum { attacking, binding, sleep, disable, confusion };
308
+
282
309
  pub fn format(
283
310
  self: Duration,
284
- comptime fmt: []const u8,
285
- opts: std.fmt.FormatOptions,
286
- writer: anytype,
311
+ writer: *std.Io.Writer,
287
312
  ) !void {
288
- _ = .{ fmt, opts };
289
313
  try writer.writeByte('(');
290
314
  var printed = false;
291
- inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
292
- const val = @field(self, field.name);
315
+ inline for (if (@hasField(@TypeOf(@typeInfo(Duration).@"struct"), "fields"))
316
+ @typeInfo(Duration).@"struct".fields
317
+ else
318
+ @typeInfo(Duration).@"struct".field_names) |field|
319
+ {
320
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
321
+ const val = @field(self, field_name);
293
322
  switch (@typeInfo(@TypeOf(val))) {
294
- Int => if (val != 0) {
323
+ .int => if (val != 0) {
295
324
  if (printed) try writer.writeAll(", ");
296
- if (comptime std.mem.eql(u8, field.name, "sleeps")) {
297
- try writer.print("{s}:[", .{field.name});
325
+ if (comptime std.mem.eql(u8, field_name, "sleeps")) {
326
+ try writer.print("{s}:[", .{field_name});
298
327
  for (0..6) |i| {
299
328
  if (i != 0) try writer.writeByte(',');
300
329
  try writer.print("{d}", .{Sleeps.get(val, i)});
301
330
  }
302
331
  try writer.writeAll("]");
303
332
  } else {
304
- try writer.print("{s}:{d}", .{ field.name, val });
333
+ try writer.print("{s}:{d}", .{ field_name, val });
305
334
  }
306
335
  printed = true;
307
336
  },
@@ -552,7 +581,7 @@ pub fn Chance(comptime Rational: type) type {
552
581
  self: *Self,
553
582
  comptime field: Action.Field,
554
583
  player: Player,
555
- obs: Optional(Confusion),
584
+ obs: Optional(Observation(.confusion)),
556
585
  ) void {
557
586
  if (!enabled) return;
558
587
 
@@ -594,7 +623,7 @@ pub fn Chance(comptime Rational: type) type {
594
623
  }
595
624
  }
596
625
 
597
- pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
626
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
598
627
  if (!enabled) return;
599
628
 
600
629
  var a = self.actions.get(player);
@@ -618,7 +647,11 @@ pub fn Chance(comptime Rational: type) type {
618
647
  }
619
648
  }
620
649
 
621
- pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
650
+ pub fn confusion(
651
+ self: *Self,
652
+ player: Player,
653
+ obs: Optional(Observation(.confusion)),
654
+ ) Error!void {
622
655
  if (!enabled) return;
623
656
 
624
657
  var a = self.actions.get(player);
@@ -640,7 +673,11 @@ pub fn Chance(comptime Rational: type) type {
640
673
  }
641
674
  }
642
675
 
643
- pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
676
+ pub fn disable(
677
+ self: *Self,
678
+ player: Player,
679
+ obs: Optional(Observation(.disable)),
680
+ ) Error!void {
644
681
  if (!enabled) return;
645
682
 
646
683
  var a = self.actions.get(player);
@@ -662,7 +699,11 @@ pub fn Chance(comptime Rational: type) type {
662
699
  }
663
700
  }
664
701
 
665
- pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
702
+ pub fn attacking(
703
+ self: *Self,
704
+ player: Player,
705
+ obs: Optional(Observation(.attacking)),
706
+ ) Error!void {
666
707
  if (!enabled) return;
667
708
 
668
709
  var a = self.actions.get(player);
@@ -684,7 +725,11 @@ pub fn Chance(comptime Rational: type) type {
684
725
  }
685
726
  }
686
727
 
687
- pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
728
+ pub fn binding(
729
+ self: *Self,
730
+ player: Player,
731
+ obs: Optional(Observation(.binding)),
732
+ ) Error!void {
688
733
  if (!enabled) return;
689
734
 
690
735
  var a = self.actions.get(player);
@@ -833,16 +878,16 @@ test "Chance.damage" {
833
878
 
834
879
  try chance.damage(.P1, 217);
835
880
  if (showdown) {
836
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
881
+ try expectValue(217, chance.actions.p1.damage);
837
882
  try expectProbability(&chance.probability, 1, 39);
838
883
  } else {
839
884
  try expectProbability(&chance.probability, 1, 1);
840
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
885
+ try expectValue(0, chance.actions.p1.damage);
841
886
 
842
887
  try chance.commit(.P1, .hit);
843
888
 
844
889
  try expectProbability(&chance.probability, 1, 39);
845
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
890
+ try expectValue(217, chance.actions.p1.damage);
846
891
  }
847
892
  }
848
893
 
@@ -894,13 +939,13 @@ test "Chance.moveSlot" {
894
939
 
895
940
  try chance.moveSlot(.P2, 2, &ms, 2);
896
941
  try expectProbability(&chance.probability, 1, 2);
897
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
942
+ try expectValue(2, chance.actions.p2.move_slot);
898
943
 
899
944
  chance.reset();
900
945
 
901
946
  try chance.moveSlot(.P1, 1, &ms, 0);
902
947
  try expectProbability(&chance.probability, 1, 3);
903
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
948
+ try expectValue(1, chance.actions.p1.move_slot);
904
949
  }
905
950
 
906
951
  test "Chance.multiHit" {
@@ -908,25 +953,25 @@ test "Chance.multiHit" {
908
953
 
909
954
  try chance.multiHit(.P1, 3);
910
955
  try expectProbability(&chance.probability, 3, 8);
911
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
956
+ try expectValue(3, chance.actions.p1.multi_hit);
912
957
 
913
958
  chance.reset();
914
959
 
915
960
  try chance.multiHit(.P2, 5);
916
961
  try expectProbability(&chance.probability, 1, 8);
917
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
962
+ try expectValue(5, chance.actions.p2.multi_hit);
918
963
  }
919
964
 
920
965
  test "Chance.duration" {
921
966
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
922
967
 
923
968
  chance.duration(.P1, 2);
924
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
969
+ try expectValue(2, chance.actions.p1.duration);
925
970
 
926
971
  chance.reset();
927
972
 
928
973
  chance.duration(.P2, 4);
929
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
974
+ try expectValue(4, chance.actions.p2.duration);
930
975
  }
931
976
 
932
977
  test "Chance.sleep" {
@@ -936,7 +981,7 @@ test "Chance.sleep" {
936
981
  chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
937
982
  try chance.sleep(.P1, .ended);
938
983
  try expectProbability(&chance.probability, 1, d);
939
- try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
984
+ try expectValue(0, Sleeps.get(chance.durations.p1.sleeps, 0));
940
985
 
941
986
  chance.reset();
942
987
 
@@ -959,7 +1004,7 @@ test "Chance.confusion" {
959
1004
  chance.durations.p2.confusion = @intCast(i);
960
1005
  try chance.confusion(.P2, .ended);
961
1006
  try expectProbability(&chance.probability, 1, d);
962
- try expectValue(@as(u3, 0), chance.durations.p2.confusion);
1007
+ try expectValue(0, chance.durations.p2.confusion);
963
1008
 
964
1009
  chance.reset();
965
1010
  }
@@ -982,7 +1027,7 @@ test "Chance.disable" {
982
1027
  chance.durations.p1.disable = @intCast(i);
983
1028
  try chance.disable(.P1, .ended);
984
1029
  try expectProbability(&chance.probability, 1, d);
985
- try expectValue(@as(u4, 0), chance.durations.p1.disable);
1030
+ try expectValue(0, chance.durations.p1.disable);
986
1031
 
987
1032
  chance.reset();
988
1033
 
@@ -1005,7 +1050,7 @@ test "Chance.attacking" {
1005
1050
  chance.durations.p2.attacking = @intCast(i);
1006
1051
  try chance.attacking(.P2, .ended);
1007
1052
  try expectProbability(&chance.probability, 1, d);
1008
- try expectValue(@as(u3, 0), chance.durations.p2.attacking);
1053
+ try expectValue(0, chance.durations.p2.attacking);
1009
1054
 
1010
1055
  chance.reset();
1011
1056
  }
@@ -1031,7 +1076,7 @@ test "Chance.binding" {
1031
1076
  chance.durations.p1.binding = @intCast(i);
1032
1077
  try chance.binding(.P1, .ended);
1033
1078
  try expectProbability(&chance.probability, p, q);
1034
- try expectValue(@as(u3, 0), chance.durations.p1.binding);
1079
+ try expectValue(0, chance.durations.p1.binding);
1035
1080
 
1036
1081
  chance.reset();
1037
1082
 
@@ -1051,7 +1096,7 @@ test "Chance.psywave" {
1051
1096
 
1052
1097
  try chance.psywave(.P2, 100, 150);
1053
1098
  try expectProbability(&chance.probability, 1, 150);
1054
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
1099
+ try expectValue(101, chance.actions.p2.psywave);
1055
1100
  }
1056
1101
 
1057
1102
  test "Chance.metronome" {
@@ -1067,7 +1112,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
1067
1112
 
1068
1113
  r.reduce();
1069
1114
  if (r.p != p or r.q != q) {
1070
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
1115
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
1071
1116
  return error.TestExpectedEqual;
1072
1117
  }
1073
1118
  }
@@ -1145,28 +1190,44 @@ const Null = struct {
1145
1190
  self: Null,
1146
1191
  comptime field: Action.Field,
1147
1192
  player: Player,
1148
- obs: Optional(Confusion),
1193
+ obs: Optional(Observation(.confusion)),
1149
1194
  ) void {
1150
1195
  _ = .{ self, field, player, obs };
1151
1196
  }
1152
1197
 
1153
- pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1198
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
1154
1199
  _ = .{ self, player, obs };
1155
1200
  }
1156
1201
 
1157
- pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1202
+ pub fn confusion(
1203
+ self: Null,
1204
+ player: Player,
1205
+ obs: Optional(Observation(.confusion)),
1206
+ ) Error!void {
1158
1207
  _ = .{ self, player, obs };
1159
1208
  }
1160
1209
 
1161
- pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1210
+ pub fn disable(
1211
+ self: Null,
1212
+ player: Player,
1213
+ obs: Optional(Observation(.disable)),
1214
+ ) Error!void {
1162
1215
  _ = .{ self, player, obs };
1163
1216
  }
1164
1217
 
1165
- pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1218
+ pub fn attacking(
1219
+ self: Null,
1220
+ player: Player,
1221
+ obs: Optional(Observation(.attacking)),
1222
+ ) Error!void {
1166
1223
  _ = .{ self, player, obs };
1167
1224
  }
1168
1225
 
1169
- pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1226
+ pub fn binding(
1227
+ self: Null,
1228
+ player: Player,
1229
+ obs: Optional(Observation(.binding)),
1230
+ ) Error!void {
1170
1231
  _ = .{ self, player, obs };
1171
1232
  }
1172
1233
 
@@ -98,7 +98,7 @@ pub const Type = enum(u4) {
98
98
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
99
99
  }
100
100
 
101
- /// The precedence order of Type `t2` vs. Type `t1`.
101
+ /// The precedence order of Type `t1` vs. Type `t2`.
102
102
  pub fn precedence(t1: Type, t2: Type) u8 {
103
103
  for (PRECEDENCE, 0..) |matchup, i| {
104
104
  if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
@@ -108,7 +108,7 @@ pub const Type = enum(u4) {
108
108
  };
109
109
 
110
110
  /// Representation of a Pokémon's typing.
111
- pub const Types = packed struct {
111
+ pub const Types = packed struct(u8) {
112
112
  /// A Pokémon's primary type.
113
113
  type1: Type = .Normal,
114
114
  /// A Pokémon's secondary type (may be identical to its primary type).