@pkmn/engine 0.1.0-dev.15f64810 → 0.1.0-dev.18478e7d

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 (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -17
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +47 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +110 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon/wasm.d.ts +2 -0
  10. package/build/pkg/addon/wasm.js +53 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +26 -4
  13. package/build/pkg/addon.js +34 -65
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +23 -1
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +29 -1
  26. package/build/pkg/protocol.js +105 -103
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +83 -73
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +66 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +360 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +157 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +346 -128
  39. package/package.json +59 -138
  40. package/src/bin/install-pkmn-engine +25 -7
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +201 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +51 -0
  45. package/src/lib/common/array.zig +70 -0
  46. package/src/lib/common/data.zig +8 -8
  47. package/src/lib/common/debug.zig +45 -29
  48. package/src/lib/common/js.zig +3 -7
  49. package/src/lib/common/optional.zig +26 -22
  50. package/src/lib/common/options.zig +4 -0
  51. package/src/lib/common/protocol.zig +196 -160
  52. package/src/lib/common/rational.zig +338 -254
  53. package/src/lib/common/rng.zig +5 -7
  54. package/src/lib/common/util.zig +53 -1
  55. package/src/lib/gen1/calc.zig +651 -205
  56. package/src/lib/gen1/chance.zig +443 -257
  57. package/src/lib/gen1/data/moves.zig +2 -4
  58. package/src/lib/gen1/data/species.zig +1 -3
  59. package/src/lib/gen1/data/types.zig +3 -4
  60. package/src/lib/gen1/data.zig +40 -43
  61. package/src/lib/gen1/helpers.zig +39 -574
  62. package/src/lib/gen1/mechanics.zig +328 -229
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +117 -518
  65. package/src/lib/gen2/data/items.zig +1 -3
  66. package/src/lib/gen2/data/moves.zig +2 -4
  67. package/src/lib/gen2/data/species.zig +1 -3
  68. package/src/lib/gen2/data/types.zig +7 -5
  69. package/src/lib/gen2/data.zig +33 -35
  70. package/src/lib/gen2/generated.zig +18 -23
  71. package/src/lib/gen2/helpers.zig +32 -33
  72. package/src/lib/gen2/mechanics.zig +105 -132
  73. package/src/lib/napi.h +2 -0
  74. package/src/lib/node.zig +4 -121
  75. package/src/lib/pkmn.zig +103 -22
  76. package/src/lib/wasm.zig +9 -9
  77. package/src/tools/display/dom.ts +108 -0
  78. package/src/tools/display/ui.css +354 -0
  79. package/build/test/display.js +0 -400
  80. package/build/test/display.js.map +0 -1
  81. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,26 +1,27 @@
1
- const std = @import("std");
2
-
3
- const assert = std.debug.assert;
4
- const print = std.debug.print;
5
-
6
- const expect = std.testing.expect;
7
- const expectEqual = std.testing.expectEqual;
8
-
9
1
  const DEBUG = @import("../common/debug.zig").print;
2
+
3
+ const array = @import("../common/array.zig");
4
+ const common = @import("../common/data.zig");
5
+ const data = @import("data.zig");
6
+ const optional = @import("../common/optional.zig");
10
7
  const options = @import("../common/options.zig");
11
8
  const rational = @import("../common/rational.zig");
9
+ const std = @import("std");
12
10
  const util = @import("../common/util.zig");
13
11
 
14
- const Player = @import("../common/data.zig").Player;
15
- const Optional = @import("../common/optional.zig").Optional;
16
-
17
- const data = @import("data.zig");
18
-
12
+ const Array = array.Array;
13
+ const assert = std.debug.assert;
19
14
  const enabled = options.chance;
20
- const showdown = options.showdown;
21
-
15
+ const expect = std.testing.expect;
16
+ const expectEqual = std.testing.expectEqual;
17
+ const isPointerTo = util.isPointerTo;
22
18
  const Move = data.Move;
23
19
  const MoveSlot = data.MoveSlot;
20
+ const Optional = optional.Optional;
21
+ const Player = common.Player;
22
+ const PointerType = util.PointerType;
23
+ const print = std.debug.print;
24
+ const showdown = options.showdown;
24
25
 
25
26
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
26
27
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
@@ -37,17 +38,11 @@ pub const Actions = extern struct {
37
38
  }
38
39
 
39
40
  /// Returns the `Action` for the given `player`.
40
- pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
41
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
41
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
42
+ assert(isPointerTo(self, Actions));
42
43
  return if (player == .P1) &self.p1 else &self.p2;
43
44
  }
44
45
 
45
- /// Perform a reset by clearing fields which should not persist across updates.
46
- pub fn reset(self: *Actions) void {
47
- self.p1.reset();
48
- self.p2.reset();
49
- }
50
-
51
46
  /// Returns true if `a` is equal to `b`.
52
47
  pub fn eql(a: Actions, b: Actions) bool {
53
48
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
@@ -57,20 +52,28 @@ pub const Actions = extern struct {
57
52
  /// same shape if they have the same fields set (though those fields need not necessarily be
58
53
  /// set to the same value).
59
54
  pub fn matches(a: Actions, b: Actions) bool {
60
- inline for (@typeInfo(Actions).Struct.fields) |player| {
61
- inline for (@typeInfo(Action).Struct.fields) |field| {
62
- const a_val = @field(@field(a, player.name), field.name);
63
- const b_val = @field(@field(b, player.name), field.name);
55
+ inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
56
+ @typeInfo(Actions).@"struct".fields
57
+ else
58
+ @typeInfo(Actions).@"struct".field_names) |player|
59
+ {
60
+ const player_name = if (@hasField(@TypeOf(player), "name"))
61
+ player.name
62
+ else
63
+ player;
64
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
65
+ @typeInfo(Action).@"struct".fields
66
+ else
67
+ @typeInfo(Action).@"struct".field_names) |field|
68
+ {
69
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
70
+ const a_val = @field(@field(a, player_name), field_name);
71
+ const b_val = @field(@field(b, player_name), field_name);
64
72
 
65
73
  switch (@typeInfo(@TypeOf(a_val))) {
66
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
67
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
68
- return false;
69
- }
70
- },
71
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
74
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
75
  return false,
73
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
76
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
74
77
  else => unreachable,
75
78
  }
76
79
  }
@@ -78,7 +81,26 @@ pub const Actions = extern struct {
78
81
  return true;
79
82
  }
80
83
 
81
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
84
+ /// TODO
85
+ pub fn matchesAny(a: Actions, bs: []Actions, i: usize) bool {
86
+ for (bs, 0..) |b, j| {
87
+ // TODO: is skipping this redundant check worth it?
88
+ if (i == j) continue;
89
+ if (b.matches(a)) return true;
90
+ }
91
+ return false;
92
+ }
93
+
94
+ /// TODO
95
+ pub fn relax(actions: Actions) Actions {
96
+ if (!options.overwrite) return actions;
97
+ var a = actions;
98
+ if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
99
+ if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
100
+ return a;
101
+ }
102
+
103
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
82
104
  try writer.writeAll("<P1 = ");
83
105
  try self.p1.fmt(writer, shape);
84
106
  try writer.writeAll(", P2 = ");
@@ -86,20 +108,15 @@ pub const Actions = extern struct {
86
108
  try writer.writeAll(">");
87
109
  }
88
110
 
89
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
90
- _ = .{ f, o };
111
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
91
112
  try fmt(a, w, false);
92
113
  }
93
114
  };
94
115
 
95
- // TODO: ziglang/zig#19730
96
116
  test Actions {
97
117
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
98
118
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
99
119
  const c: Actions = .{ .p1 = .{ .hit = .true } };
100
- // const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
101
- // const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
- // const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
103
120
 
104
121
  try expect(a.eql(a));
105
122
  try expect(!a.eql(b));
@@ -112,8 +129,43 @@ test Actions {
112
129
  try expect(b.matches(a));
113
130
  try expect(!a.matches(c));
114
131
  try expect(!c.matches(a));
115
- // try expect(d.matches(e));
116
- // try expect(!d.matches(f));
132
+ }
133
+
134
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
135
+ pub fn Observation(comptime field: Duration.Field) type {
136
+ return switch (field) {
137
+ .attacking, .binding => enum(u2) {
138
+ started,
139
+ continuing,
140
+ ended,
141
+ },
142
+ .sleep, .disable => enum(u3) {
143
+ started,
144
+ continuing,
145
+ ended,
146
+ // TODO haze support
147
+ unused1,
148
+ unused2,
149
+ unused3,
150
+ unused4,
151
+ },
152
+ .confusion => enum(u4) {
153
+ started,
154
+ continuing,
155
+ ended,
156
+ /// An indicator that an existing confusion duration has been overwritten by a new
157
+ /// confusion duration due to a Thrashing move ending ("hidden" confusion). This is not
158
+ /// immediately observable to an opponent, to compute probabilities from an opponent's
159
+ /// point of view based solely on public information extra work must be done to correct
160
+ /// for the information leak
161
+ overwritten,
162
+ // TODO haze support
163
+ unused1,
164
+ unused2,
165
+ unused3,
166
+ unused4,
167
+ },
168
+ };
117
169
  }
118
170
 
119
171
  /// Information about the RNG that was observed during a Generation I battle `update` for a
@@ -140,14 +192,25 @@ pub const Action = packed struct(u64) {
140
192
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
141
193
  duration: u4 = 0,
142
194
 
143
- /// Observed values of various durations. Does not influence future RNG calls. TODO
144
- durations: Duration = .{},
195
+ /// TODO
196
+ sleep: Optional(Observation(.sleep)) = .None,
197
+ /// TODO
198
+ confusion: Optional(Observation(.confusion)) = .None,
199
+ /// TODO
200
+ disable: Optional(Observation(.disable)) = .None,
201
+ /// TODO
202
+ attacking: Optional(Observation(.attacking)) = .None,
203
+ /// TODO
204
+ binding: Optional(Observation(.binding)) = .None,
145
205
 
146
206
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
147
207
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
148
- move_slot: u4 = 0,
208
+ move_slot: u3 = 0,
149
209
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
150
- multi_hit: u4 = 0,
210
+ multi_hit: u3 = 0,
211
+
212
+ /// TODO
213
+ pp: u4 = 0,
151
214
 
152
215
  /// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
153
216
  psywave: u8 = 0,
@@ -155,58 +218,47 @@ pub const Action = packed struct(u64) {
155
218
  /// If not None, the Move to return for Rolls.metronome.
156
219
  metronome: Move = .None,
157
220
 
158
- pub const DURATIONS: u64 = 0x000000FFFF000000;
159
-
160
221
  pub const Field = std.meta.FieldEnum(Action);
161
222
 
162
- /// Perform a reset by clearing fields which should not persist across updates.
163
- pub fn reset(self: *Action) void {
164
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
165
- }
166
-
167
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
168
- _ = .{ f, o };
223
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
169
224
  try fmt(a, w, false);
170
225
  }
171
226
 
172
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
227
+ const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
228
+
229
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
173
230
  try writer.writeByte('(');
174
231
  var printed = false;
175
- inline for (@typeInfo(Action).Struct.fields) |field| {
176
- const val = @field(self, field.name);
232
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
233
+ @typeInfo(Action).@"struct".fields
234
+ else
235
+ @typeInfo(Action).@"struct".field_names) |field|
236
+ {
237
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
238
+ const val = @field(self, field_name);
177
239
  switch (@typeInfo(@TypeOf(val))) {
178
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
179
- const v = @field(val, f.name);
180
- if (v != 0) {
181
- if (printed) try writer.writeAll(", ");
182
- if (shape) {
183
- try writer.print("{s}:?", .{f.name});
184
- } else {
185
- try writer.print("{s}:{d}", .{ f.name, v });
186
- }
187
- printed = true;
188
- }
189
- },
190
- .Enum => if (val != .None) {
240
+ .@"enum" => if (val != .None) {
191
241
  if (printed) try writer.writeAll(", ");
192
242
  if (shape) {
193
- try writer.print("{s}:?", .{field.name});
243
+ try writer.print("{s}:?", .{field_name});
194
244
  } else if (@TypeOf(val) == Optional(bool)) {
195
245
  try writer.print("{s}{s}", .{
196
246
  if (val == .false) "!" else "",
197
- field.name,
247
+ field_name,
198
248
  });
249
+ } else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
250
+ try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field_name });
199
251
  } else {
200
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
252
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
201
253
  }
202
254
  printed = true;
203
255
  },
204
- .Int => if (val != 0) {
256
+ .int => if (val != 0) {
205
257
  if (printed) try writer.writeAll(", ");
206
258
  if (shape) {
207
- try writer.print("{s}:?", .{field.name});
259
+ try writer.print("{s}:?", .{field_name});
208
260
  } else {
209
- try writer.print("{s}:{d}", .{ field.name, val });
261
+ try writer.print("{s}:{d}", .{ field_name, val });
210
262
  }
211
263
  printed = true;
212
264
  },
@@ -217,43 +269,78 @@ pub const Action = packed struct(u64) {
217
269
  }
218
270
  };
219
271
 
220
- test Action {
221
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
222
- a.reset();
223
-
224
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
225
- }
226
-
227
272
  /// TODO
228
273
  pub const Durations = extern struct {
229
- /// Information about the durations for Player 1.
274
+ /// TODO
230
275
  p1: Duration = .{},
231
- /// Information about the durations for Player 2.
276
+ /// TODO
232
277
  p2: Duration = .{},
233
278
 
234
279
  comptime {
235
- assert(@sizeOf(Durations) == 4);
280
+ assert(@sizeOf(Durations) == 8);
281
+ }
282
+
283
+ /// TODO
284
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
285
+ assert(isPointerTo(self, Durations));
286
+ return if (player == .P1) &self.p1 else &self.p2;
287
+ }
288
+
289
+ pub fn format(self: Durations, writer: *std.Io.Writer) !void {
290
+ try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
236
291
  }
237
292
  };
238
293
 
239
- /// Observed values for various durations that need to be tracked in order to properly
240
- /// deduplicate transitions with a primary key. TODO
241
- pub const Duration = packed struct(u16) {
242
- /// The number of turns a Pokémon has been observed to be sleeping.
243
- sleep: u3 = 0,
244
- /// The number of turns a Pokémon has been observed to be confused.
294
+ pub const Sleeps = Array(6, u3);
295
+
296
+ /// TODO
297
+ pub const Duration = packed struct(u32) {
298
+ sleeps: Sleeps.T = 0,
245
299
  confusion: u3 = 0,
246
- /// The number of turns a Pokémon has been observed to be disabled.
247
300
  disable: u4 = 0,
248
- /// The number of turns a Pokémon has been observed to be attacking.
249
301
  attacking: u3 = 0,
250
- /// The number of turns a Pokémon has been observed to be binding their opponent.
251
302
  binding: u3 = 0,
252
303
 
253
- pub const Field = std.meta.FieldEnum(Duration);
304
+ _: u1 = 0,
305
+
306
+ pub const Field = enum { attacking, binding, sleep, disable, confusion };
307
+
308
+ pub fn format(
309
+ self: Duration,
310
+ writer: *std.Io.Writer,
311
+ ) !void {
312
+ try writer.writeByte('(');
313
+ var printed = false;
314
+ inline for (if (@hasField(@TypeOf(@typeInfo(Duration).@"struct"), "fields"))
315
+ @typeInfo(Duration).@"struct".fields
316
+ else
317
+ @typeInfo(Duration).@"struct".field_names) |field|
318
+ {
319
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
320
+ const val = @field(self, field_name);
321
+ switch (@typeInfo(@TypeOf(val))) {
322
+ .int => if (val != 0) {
323
+ if (printed) try writer.writeAll(", ");
324
+ if (comptime std.mem.eql(u8, field_name, "sleeps")) {
325
+ try writer.print("{s}:[", .{field_name});
326
+ for (0..6) |i| {
327
+ if (i != 0) try writer.writeByte(',');
328
+ try writer.print("{d}", .{Sleeps.get(val, i)});
329
+ }
330
+ try writer.writeAll("]");
331
+ } else {
332
+ try writer.print("{s}:{d}", .{ field_name, val });
333
+ }
334
+ printed = true;
335
+ },
336
+ else => unreachable,
337
+ }
338
+ }
339
+ try writer.writeByte(')');
340
+ }
254
341
  };
255
342
 
256
- pub const Commit = enum { hit, miss, binding };
343
+ pub const Commit = enum { hit, miss, binding, err, glitch };
257
344
 
258
345
  const uN = if (options.miss) u8 else u9;
259
346
 
@@ -268,14 +355,8 @@ pub fn Chance(comptime Rational: type) type {
268
355
  /// The actions taken by a hypothetical "chance player" that convey information about which
269
356
  /// RNG events were observed during a battle `update`.
270
357
  actions: Actions = .{},
271
-
272
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
273
- // other durations which are all tied to volatiles, sleep's counter persists through
274
- // switching and so we must store it here. The indices of these arrays correspond to the
275
- // `order` field of a Side. This information could be stored in actions but size is a
276
- // concern so this is tracked separately as actions only purports to track RNG events
277
- // observed during a single `update` (not across updates).
278
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
358
+ /// TODO
359
+ durations: Durations = .{},
279
360
 
280
361
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
281
362
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -299,7 +380,7 @@ pub fn Chance(comptime Rational: type) type {
299
380
  damage_roll: u8 = 0,
300
381
  hit: bool = false,
301
382
  hit_probablity: uN = 0,
302
- binding: u8 = 0,
383
+ glitch: bool = false,
303
384
  } = .{},
304
385
 
305
386
  /// Possible error returned by operations tracking chance probability.
@@ -310,7 +391,7 @@ pub fn Chance(comptime Rational: type) type {
310
391
  if (!enabled) return;
311
392
 
312
393
  self.probability.reset();
313
- self.actions.reset();
394
+ self.actions = .{};
314
395
  self.pending = .{};
315
396
  }
316
397
 
@@ -319,20 +400,18 @@ pub fn Chance(comptime Rational: type) type {
319
400
 
320
401
  assert(!showdown);
321
402
 
403
+ if (kind == .glitch and !self.pending.glitch) return;
404
+
322
405
  var action = self.actions.get(player);
323
406
 
324
- // We need to handle binding specially because we still need to commit it when attacking
325
- // into a Pokémon that is immune
326
- if (kind != .miss and self.pending.binding != 0) {
327
- assert(action.duration == 0);
328
- action.duration = @intCast(self.pending.binding);
329
- assert(action.durations.binding == 0);
330
- action.durations.binding = 1;
407
+ if (kind == .err and self.pending.crit_probablity != 0) {
408
+ try self.probability.update(self.pending.crit_probablity, 256);
409
+ action.critical_hit = if (self.pending.crit) .true else .false;
410
+ return;
331
411
  }
332
- if (kind == .binding) return;
333
412
 
334
413
  // Always commit the hit result if we make it here (commit won't be called at all if the
335
- // target is immune/behind a sub/etc)
414
+ // target is immune*/behind a sub/etc)
336
415
  if (self.pending.hit_probablity != 0) {
337
416
  try self.probability.update(self.pending.hit_probablity, 256);
338
417
  action.hit = if (self.pending.hit) .true else .false;
@@ -340,57 +419,59 @@ pub fn Chance(comptime Rational: type) type {
340
419
 
341
420
  // If the move actually lands we can commit any past critical hit / damage rolls. Note
342
421
  // we can't rely on the presence or absence of a damage roll to determine whether a
343
- // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
422
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
344
423
  // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
345
- // passed to the critical hit helper)
346
- if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
424
+ // passed to the critical hit helper).
425
+ //
426
+ // There is a special edge case with the division-by-zero freeze where we need to commit
427
+ // the status of critical hit roll the occured before the move missed to avoid missing
428
+ // out on the possibility that the alternative branch encountered an error
429
+ if (kind != .hit and !self.pending.glitch) {
430
+ return assert(!self.pending.crit or self.pending.crit_probablity > 0);
431
+ }
347
432
 
348
433
  if (self.pending.crit_probablity != 0) {
349
434
  try self.probability.update(self.pending.crit_probablity, 256);
350
435
  action.critical_hit = if (self.pending.crit) .true else .false;
351
- }
436
+ } else assert(!self.pending.glitch);
352
437
 
353
- if (self.pending.damage_roll > 0) {
438
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
354
439
  try self.probability.update(1, 39);
355
440
  action.damage = self.pending.damage_roll;
356
441
  }
357
442
  }
358
443
 
359
- pub fn clearPending(self: *Self) void {
444
+ pub fn glitch(self: *Self) void {
360
445
  if (!enabled) return;
361
446
 
362
- self.pending = .{};
447
+ assert(!showdown);
448
+ self.pending.glitch = true;
363
449
  }
364
450
 
365
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
451
+ pub fn clearPending(self: *Self) void {
366
452
  if (!enabled) return;
367
453
 
368
- var action = self.actions.get(player);
369
- if (haze) |status| {
370
- if (status) action.durations.sleep = 0;
371
- action.durations.confusion = 0;
372
- action.durations.disable = 0;
373
- } else {
374
- action.durations.attacking = 0;
375
- action.durations.binding = 0;
376
- }
454
+ assert(!showdown);
455
+ self.pending = .{};
377
456
  }
378
457
 
379
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
458
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
380
459
  if (!enabled) return;
381
460
 
382
- assert(in >= 1 and in <= 6);
383
- assert(out >= 1 and out <= 6);
461
+ assert(slot >= 1 and slot <= 6);
384
462
 
385
- var action = self.actions.get(player);
463
+ var d = self.durations.get(player);
386
464
 
387
- const slp = action.durations.sleep;
388
- action.durations = .{};
465
+ const out = Sleeps.get(d.sleeps, 0);
466
+ d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
467
+ d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
389
468
 
390
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
391
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
469
+ d.confusion = 0;
470
+ d.disable = 0;
471
+ d.attacking = 0;
472
+ d.binding = 0;
392
473
 
393
- self.actions.get(player.foe()).durations.binding = 0;
474
+ self.durations.get(player.foe()).binding = 0;
394
475
  }
395
476
 
396
477
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -489,108 +570,188 @@ pub fn Chance(comptime Rational: type) type {
489
570
  self.actions.get(player).multi_hit = n;
490
571
  }
491
572
 
492
- pub fn duration(
573
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
574
+ if (!enabled) return;
575
+
576
+ self.actions.get(player).duration = if (options.key) 2 else turns;
577
+ }
578
+
579
+ pub fn observe(
493
580
  self: *Self,
494
- comptime field: Duration.Field,
581
+ comptime field: Action.Field,
495
582
  player: Player,
496
- target: Player,
497
- turns: u4,
583
+ obs: Optional(Observation(.confusion)),
498
584
  ) void {
499
585
  if (!enabled) return;
500
586
 
501
- if (!showdown and field == .binding) {
502
- self.pending.binding = if (options.key) 1 else turns;
503
- } else {
504
- var durations = &self.actions.get(target).durations;
505
- assert(@field(durations, @tagName(field)) == 0 or
506
- (field == .confusion and player == target));
507
- @field(durations, @tagName(field)) = 1;
508
- self.actions.get(player).duration = if (options.key) 1 else turns;
587
+ var a = self.actions.get(player);
588
+ const val = @field(a, @tagName(field));
589
+ switch (obs) {
590
+ .overwritten => {
591
+ assert(field == .confusion);
592
+ assert(val == .started or val == .continuing);
593
+ assert(a.duration > 0);
594
+ a.confusion = obs;
595
+ self.durations.get(player).confusion = 1;
596
+ },
597
+ .None => {
598
+ @field(a, @tagName(field)) = .None;
599
+ if (field == .sleep) {
600
+ var d = self.durations.get(player);
601
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
602
+ } else {
603
+ @field(self.durations.get(player), @tagName(field)) = 0;
604
+ }
605
+ },
606
+ .started => {
607
+ assert(val == .None or val == .ended);
608
+ assert(switch (field) {
609
+ .confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
610
+ .sleep, .disable => self.actions.get(player.foe()).duration > 0,
611
+ else => a.duration > 0,
612
+ });
613
+ @field(a, @tagName(field)) = .started;
614
+ if (field == .sleep) {
615
+ var d = self.durations.get(player);
616
+ d.sleeps = Sleeps.set(d.sleeps, 0, 1);
617
+ } else {
618
+ @field(self.durations.get(player), @tagName(field)) = 1;
619
+ }
620
+ },
621
+ else => unreachable,
509
622
  }
510
623
  }
511
624
 
512
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
625
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
513
626
  if (!enabled) return;
514
627
 
515
- var durations = &self.actions.get(player).durations;
516
- const n = durations.sleep;
517
- if (turns == 0) {
628
+ var a = self.actions.get(player);
629
+ assert(a.sleep == .None or a.sleep == .started);
630
+ a.sleep = obs;
631
+
632
+ var d = self.durations.get(player);
633
+ const n = Sleeps.get(d.sleeps, 0);
634
+
635
+ if (obs == .None) {
636
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
637
+ } else if (obs == .ended) {
518
638
  assert(n >= 1 and n <= 7);
519
639
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
520
- durations.sleep = 0;
640
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
521
641
  } else {
642
+ assert(obs == .continuing);
522
643
  assert(n >= 1 and n < 7);
523
644
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
524
- durations.sleep += 1;
645
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
525
646
  }
526
647
  }
527
648
 
528
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
649
+ pub fn confusion(
650
+ self: *Self,
651
+ player: Player,
652
+ obs: Optional(Observation(.confusion)),
653
+ ) Error!void {
529
654
  if (!enabled) return;
530
655
 
531
- var durations = &self.actions.get(player).durations;
532
- const n = durations.confusion;
533
- if (turns == 0) {
656
+ var a = self.actions.get(player);
657
+ assert(a.confusion == .None or a.confusion == .started);
658
+ a.confusion = obs;
659
+
660
+ var d = self.durations.get(player);
661
+ const n = d.confusion;
662
+
663
+ if (obs == .ended) {
534
664
  assert(n >= 2 and n <= 5);
535
665
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
536
- durations.confusion = 0;
666
+ d.confusion = 0;
537
667
  } else {
668
+ assert(obs == .continuing);
538
669
  assert(n >= 1 and n < 5);
539
670
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
540
- durations.confusion += 1;
671
+ d.confusion += 1;
541
672
  }
542
673
  }
543
674
 
544
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
675
+ pub fn disable(
676
+ self: *Self,
677
+ player: Player,
678
+ obs: Optional(Observation(.disable)),
679
+ ) Error!void {
545
680
  if (!enabled) return;
546
681
 
547
- var durations = &self.actions.get(player).durations;
548
- const n = durations.disable;
549
- if (turns == 0) {
682
+ var a = self.actions.get(player);
683
+ assert(a.disable == .None or a.disable == .started);
684
+ a.disable = obs;
685
+
686
+ var d = self.durations.get(player);
687
+ const n = d.disable;
688
+
689
+ if (obs == .ended) {
550
690
  assert(n >= 1 and n <= 8);
551
691
  if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
552
- durations.disable = 0;
692
+ d.disable = 0;
553
693
  } else {
694
+ assert(obs == .continuing);
554
695
  assert(n >= 1 and n < 8);
555
696
  try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
556
- durations.disable += 1;
697
+ d.disable += 1;
557
698
  }
558
699
  }
559
700
 
560
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
701
+ pub fn attacking(
702
+ self: *Self,
703
+ player: Player,
704
+ obs: Optional(Observation(.attacking)),
705
+ ) Error!void {
561
706
  if (!enabled) return;
562
707
 
563
- var durations = &self.actions.get(player).durations;
564
- const n = durations.attacking;
565
- if (turns == 0) {
708
+ var a = self.actions.get(player);
709
+ assert(a.attacking == .None or a.attacking == .started);
710
+ a.attacking = obs;
711
+
712
+ var d = self.durations.get(player);
713
+ const n = d.attacking;
714
+ if (obs == .ended) {
715
+ // "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
566
716
  assert(n >= 2 and n <= 3);
567
717
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
568
- durations.attacking = 0;
718
+ d.attacking = 0;
569
719
  } else {
720
+ assert(obs == .continuing);
570
721
  assert(n >= 1 and n < 3);
571
722
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
572
- durations.attacking += 1;
723
+ d.attacking += 1;
573
724
  }
574
725
  }
575
726
 
576
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
727
+ pub fn binding(
728
+ self: *Self,
729
+ player: Player,
730
+ obs: Optional(Observation(.binding)),
731
+ ) Error!void {
577
732
  if (!enabled) return;
578
733
 
579
- var durations = &self.actions.get(player).durations;
580
- const n = durations.binding;
734
+ var a = self.actions.get(player);
735
+ assert(a.binding == .None or a.binding == .started);
736
+ a.binding = obs;
737
+
738
+ var d = self.durations.get(player);
739
+ const n = d.binding;
581
740
 
582
741
  assert(n > 0);
583
742
  const p: u4 = if (n < 3) 3 else 1;
584
743
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
585
744
 
586
- if (turns == 0) {
745
+ if (obs == .ended) {
746
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
587
747
  assert(n >= 1 and n <= 4);
588
748
  if (n != 4) try self.probability.update(p, q);
589
- durations.binding = 0;
749
+ d.binding = 0;
590
750
  } else {
751
+ assert(obs == .continuing);
591
752
  assert(n >= 1 and n < 4);
592
753
  try self.probability.update(q - p, q);
593
- durations.binding += 1;
754
+ d.binding += 1;
594
755
  }
595
756
  }
596
757
 
@@ -607,6 +768,15 @@ pub fn Chance(comptime Rational: type) type {
607
768
  try self.probability.update(1, Move.METRONOME.len);
608
769
  self.actions.get(player).metronome = move;
609
770
  }
771
+
772
+ pub fn pp(self: *Self, player: Player, n: u4) bool {
773
+ if (!enabled) return true;
774
+
775
+ var a = self.actions.get(player);
776
+ if (n > 1 and a.pp == n) return false;
777
+ if (a.metronome != .None) a.pp += n;
778
+ return true;
779
+ }
610
780
  };
611
781
  }
612
782
 
@@ -707,16 +877,16 @@ test "Chance.damage" {
707
877
 
708
878
  try chance.damage(.P1, 217);
709
879
  if (showdown) {
710
- try expectValue(217, chance.actions.p1.damage);
880
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
711
881
  try expectProbability(&chance.probability, 1, 39);
712
882
  } else {
713
883
  try expectProbability(&chance.probability, 1, 1);
714
- try expectValue(0, chance.actions.p1.damage);
884
+ try expectValue(@as(u8, 0), chance.actions.p1.damage);
715
885
 
716
886
  try chance.commit(.P1, .hit);
717
887
 
718
888
  try expectProbability(&chance.probability, 1, 39);
719
- try expectValue(217, chance.actions.p1.damage);
889
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
720
890
  }
721
891
  }
722
892
 
@@ -768,13 +938,13 @@ test "Chance.moveSlot" {
768
938
 
769
939
  try chance.moveSlot(.P2, 2, &ms, 2);
770
940
  try expectProbability(&chance.probability, 1, 2);
771
- try expectValue(2, chance.actions.p2.move_slot);
941
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
772
942
 
773
943
  chance.reset();
774
944
 
775
945
  try chance.moveSlot(.P1, 1, &ms, 0);
776
946
  try expectProbability(&chance.probability, 1, 3);
777
- try expectValue(1, chance.actions.p1.move_slot);
947
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
778
948
  }
779
949
 
780
950
  test "Chance.multiHit" {
@@ -782,51 +952,43 @@ test "Chance.multiHit" {
782
952
 
783
953
  try chance.multiHit(.P1, 3);
784
954
  try expectProbability(&chance.probability, 3, 8);
785
- try expectValue(3, chance.actions.p1.multi_hit);
955
+ try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
786
956
 
787
957
  chance.reset();
788
958
 
789
959
  try chance.multiHit(.P2, 5);
790
960
  try expectProbability(&chance.probability, 1, 8);
791
- try expectValue(5, chance.actions.p2.multi_hit);
961
+ try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
792
962
  }
793
963
 
794
964
  test "Chance.duration" {
795
965
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
796
966
 
797
- chance.duration(.sleep, .P1, .P2, 2);
798
- try expectValue(2, chance.actions.p1.duration);
799
- try expectValue(1, chance.actions.p2.durations.sleep);
967
+ chance.duration(.P1, 2);
968
+ try expectValue(@as(u4, 2), chance.actions.p1.duration);
800
969
 
801
970
  chance.reset();
802
971
 
803
- chance.duration(.binding, .P2, .P2, 4);
804
- if (!showdown) {
805
- try expectValue(0, chance.actions.p2.duration);
806
- try expectValue(0, chance.actions.p2.durations.binding);
807
-
808
- try chance.commit(.P2, .hit);
809
- }
810
- try expectValue(4, chance.actions.p2.duration);
811
- try expectValue(1, chance.actions.p2.durations.binding);
972
+ chance.duration(.P2, 4);
973
+ try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
974
  }
813
975
 
814
976
  test "Chance.sleep" {
815
977
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
816
978
 
817
979
  for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
818
- chance.actions.p1.durations.sleep = @intCast(i);
819
- try chance.sleep(.P1, 0);
980
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
981
+ try chance.sleep(.P1, .ended);
820
982
  try expectProbability(&chance.probability, 1, d);
821
- try expectValue(0, chance.actions.p1.durations.sleep);
983
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
822
984
 
823
985
  chance.reset();
824
986
 
825
987
  if (i < 7) {
826
- chance.actions.p1.durations.sleep = @intCast(i);
827
- try chance.sleep(.P1, 1);
988
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
989
+ try chance.sleep(.P1, .continuing);
828
990
  try expectProbability(&chance.probability, d - 1, d);
829
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
991
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
830
992
 
831
993
  chance.reset();
832
994
  }
@@ -838,19 +1000,19 @@ test "Chance.confusion" {
838
1000
 
839
1001
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
840
1002
  if (i > 1) {
841
- chance.actions.p2.durations.confusion = @intCast(i);
842
- try chance.confusion(.P2, 0);
1003
+ chance.durations.p2.confusion = @intCast(i);
1004
+ try chance.confusion(.P2, .ended);
843
1005
  try expectProbability(&chance.probability, 1, d);
844
- try expectValue(0, chance.actions.p2.durations.confusion);
1006
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
845
1007
 
846
1008
  chance.reset();
847
1009
  }
848
1010
 
849
1011
  if (i < 5) {
850
- chance.actions.p2.durations.confusion = @intCast(i);
851
- try chance.confusion(.P2, 1);
1012
+ chance.durations.p2.confusion = @intCast(i);
1013
+ try chance.confusion(.P2, .continuing);
852
1014
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
853
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
1015
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
854
1016
 
855
1017
  chance.reset();
856
1018
  }
@@ -861,18 +1023,18 @@ test "Chance.disable" {
861
1023
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
862
1024
 
863
1025
  for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
864
- chance.actions.p1.durations.disable = @intCast(i);
865
- try chance.disable(.P1, 0);
1026
+ chance.durations.p1.disable = @intCast(i);
1027
+ try chance.disable(.P1, .ended);
866
1028
  try expectProbability(&chance.probability, 1, d);
867
- try expectValue(0, chance.actions.p1.durations.disable);
1029
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
868
1030
 
869
1031
  chance.reset();
870
1032
 
871
1033
  if (i < 8) {
872
- chance.actions.p1.durations.disable = @intCast(i);
873
- try chance.disable(.P1, 1);
1034
+ chance.durations.p1.disable = @intCast(i);
1035
+ try chance.disable(.P1, .continuing);
874
1036
  try expectProbability(&chance.probability, d - 1, d);
875
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
1037
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
876
1038
 
877
1039
  chance.reset();
878
1040
  }
@@ -884,19 +1046,19 @@ test "Chance.attacking" {
884
1046
 
885
1047
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
886
1048
  if (i > 1) {
887
- chance.actions.p2.durations.attacking = @intCast(i);
888
- try chance.attacking(.P2, 0);
1049
+ chance.durations.p2.attacking = @intCast(i);
1050
+ try chance.attacking(.P2, .ended);
889
1051
  try expectProbability(&chance.probability, 1, d);
890
- try expectValue(0, chance.actions.p2.durations.attacking);
1052
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
891
1053
 
892
1054
  chance.reset();
893
1055
  }
894
1056
 
895
1057
  if (i < 3) {
896
- chance.actions.p2.durations.attacking = @intCast(i);
897
- try chance.attacking(.P2, 1);
1058
+ chance.durations.p2.attacking = @intCast(i);
1059
+ try chance.attacking(.P2, .continuing);
898
1060
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
899
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
1061
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
900
1062
 
901
1063
  chance.reset();
902
1064
  }
@@ -910,18 +1072,18 @@ test "Chance.binding" {
910
1072
  const qs = [_]u8{ 8, 5, 2, 1 };
911
1073
 
912
1074
  for (ps, qs, 1..5) |p, q, i| {
913
- chance.actions.p1.durations.binding = @intCast(i);
914
- try chance.binding(.P1, 0);
1075
+ chance.durations.p1.binding = @intCast(i);
1076
+ try chance.binding(.P1, .ended);
915
1077
  try expectProbability(&chance.probability, p, q);
916
- try expectValue(0, chance.actions.p1.durations.binding);
1078
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
917
1079
 
918
1080
  chance.reset();
919
1081
 
920
1082
  if (i < 4) {
921
- chance.actions.p1.durations.binding = @intCast(i);
922
- try chance.binding(.P1, 1);
1083
+ chance.durations.p1.binding = @intCast(i);
1084
+ try chance.binding(.P1, .continuing);
923
1085
  try expectProbability(&chance.probability, q - p, q);
924
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1086
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
925
1087
 
926
1088
  chance.reset();
927
1089
  }
@@ -933,7 +1095,7 @@ test "Chance.psywave" {
933
1095
 
934
1096
  try chance.psywave(.P2, 100, 150);
935
1097
  try expectProbability(&chance.probability, 1, 150);
936
- try expectValue(101, chance.actions.p2.psywave);
1098
+ try expectValue(@as(u8, 101), chance.actions.p2.psywave);
937
1099
  }
938
1100
 
939
1101
  test "Chance.metronome" {
@@ -949,7 +1111,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
949
1111
 
950
1112
  r.reduce();
951
1113
  if (r.p != p or r.q != q) {
952
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
1114
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
953
1115
  return error.TestExpectedEqual;
954
1116
  }
955
1117
  }
@@ -971,16 +1133,16 @@ const Null = struct {
971
1133
  _ = .{ self, player, kind };
972
1134
  }
973
1135
 
974
- pub fn clearPending(self: Null) void {
1136
+ pub fn glitch(self: Null) void {
975
1137
  _ = .{self};
976
1138
  }
977
1139
 
978
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
979
- _ = .{ self, player, haze };
1140
+ pub fn clearPending(self: Null) void {
1141
+ _ = .{self};
980
1142
  }
981
1143
 
982
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
983
- _ = .{ self, player, in, out };
1144
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1145
+ _ = .{ self, player, slot };
984
1146
  }
985
1147
 
986
1148
  pub fn speedTie(self: Null, p1: bool) Error!void {
@@ -1019,34 +1181,53 @@ const Null = struct {
1019
1181
  _ = .{ self, player, n };
1020
1182
  }
1021
1183
 
1022
- pub fn duration(
1184
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1185
+ _ = .{ self, player, turns };
1186
+ }
1187
+
1188
+ pub fn observe(
1023
1189
  self: Null,
1024
- comptime field: Duration.Field,
1190
+ comptime field: Action.Field,
1025
1191
  player: Player,
1026
- target: Player,
1027
- turns: u4,
1192
+ obs: Optional(Observation(.confusion)),
1028
1193
  ) void {
1029
- _ = .{ self, field, player, target, turns };
1194
+ _ = .{ self, field, player, obs };
1030
1195
  }
1031
1196
 
1032
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1033
- _ = .{ self, player, turns };
1197
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
1198
+ _ = .{ self, player, obs };
1034
1199
  }
1035
1200
 
1036
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1037
- _ = .{ self, player, turns };
1201
+ pub fn confusion(
1202
+ self: Null,
1203
+ player: Player,
1204
+ obs: Optional(Observation(.confusion)),
1205
+ ) Error!void {
1206
+ _ = .{ self, player, obs };
1038
1207
  }
1039
1208
 
1040
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1041
- _ = .{ self, player, turns };
1209
+ pub fn disable(
1210
+ self: Null,
1211
+ player: Player,
1212
+ obs: Optional(Observation(.disable)),
1213
+ ) Error!void {
1214
+ _ = .{ self, player, obs };
1042
1215
  }
1043
1216
 
1044
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1045
- _ = .{ self, player, turns };
1217
+ pub fn attacking(
1218
+ self: Null,
1219
+ player: Player,
1220
+ obs: Optional(Observation(.attacking)),
1221
+ ) Error!void {
1222
+ _ = .{ self, player, obs };
1046
1223
  }
1047
1224
 
1048
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1049
- _ = .{ self, player, turns };
1225
+ pub fn binding(
1226
+ self: Null,
1227
+ player: Player,
1228
+ obs: Optional(Observation(.binding)),
1229
+ ) Error!void {
1230
+ _ = .{ self, player, obs };
1050
1231
  }
1051
1232
 
1052
1233
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1056,4 +1237,9 @@ const Null = struct {
1056
1237
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1057
1238
  _ = .{ self, player, move };
1058
1239
  }
1240
+
1241
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1242
+ _ = .{ self, player, n };
1243
+ return true;
1244
+ }
1059
1245
  };