@pkmn/engine 0.1.0-dev.62399ed5 → 0.1.0-dev.62eefbc0

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 +24 -14
  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 +266 -103
  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 +200 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +50 -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 +33 -28
  48. package/src/lib/common/js.zig +3 -7
  49. package/src/lib/common/optional.zig +11 -17
  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 +303 -310
  53. package/src/lib/common/rng.zig +5 -7
  54. package/src/lib/common/util.zig +33 -1
  55. package/src/lib/gen1/calc.zig +569 -190
  56. package/src/lib/gen1/chance.zig +412 -249
  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 +17 -556
  62. package/src/lib/gen1/mechanics.zig +317 -221
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +91 -510
  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 +9 -20
  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,15 @@ 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| {
55
+ inline for (@typeInfo(Actions).@"struct".fields) |player| {
56
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
62
57
  const a_val = @field(@field(a, player.name), field.name);
63
58
  const b_val = @field(@field(b, player.name), field.name);
64
59
 
65
60
  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))
61
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
62
  return false,
73
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
63
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
74
64
  else => unreachable,
75
65
  }
76
66
  }
@@ -78,7 +68,26 @@ pub const Actions = extern struct {
78
68
  return true;
79
69
  }
80
70
 
81
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
71
+ /// TODO
72
+ pub fn matchesAny(a: Actions, bs: []Actions, i: usize) bool {
73
+ for (bs, 0..) |b, j| {
74
+ // TODO: is skipping this redundant check worth it?
75
+ if (i == j) continue;
76
+ if (b.matches(a)) return true;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /// TODO
82
+ pub fn relax(actions: Actions) Actions {
83
+ if (!options.overwrite) return actions;
84
+ var a = actions;
85
+ if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
86
+ if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
87
+ return a;
88
+ }
89
+
90
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
82
91
  try writer.writeAll("<P1 = ");
83
92
  try self.p1.fmt(writer, shape);
84
93
  try writer.writeAll(", P2 = ");
@@ -86,20 +95,15 @@ pub const Actions = extern struct {
86
95
  try writer.writeAll(">");
87
96
  }
88
97
 
89
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
90
- _ = .{ f, o };
98
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
91
99
  try fmt(a, w, false);
92
100
  }
93
101
  };
94
102
 
95
- // TODO: ziglang/zig#19730
96
103
  test Actions {
97
104
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
98
105
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
99
106
  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
107
 
104
108
  try expect(a.eql(a));
105
109
  try expect(!a.eql(b));
@@ -112,8 +116,43 @@ test Actions {
112
116
  try expect(b.matches(a));
113
117
  try expect(!a.matches(c));
114
118
  try expect(!c.matches(a));
115
- // try expect(d.matches(e));
116
- // try expect(!d.matches(f));
119
+ }
120
+
121
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
122
+ pub fn Observation(comptime field: Duration.Field) type {
123
+ return switch (field) {
124
+ .attacking, .binding => enum(u2) {
125
+ started,
126
+ continuing,
127
+ ended,
128
+ },
129
+ .sleep, .disable => enum(u3) {
130
+ started,
131
+ continuing,
132
+ ended,
133
+ // TODO haze support
134
+ unused1,
135
+ unused2,
136
+ unused3,
137
+ unused4,
138
+ },
139
+ .confusion => enum(u4) {
140
+ started,
141
+ continuing,
142
+ ended,
143
+ /// An indicator that an existing confusion duration has been overwritten by a new
144
+ /// confusion duration due to a Thrashing move ending ("hidden" confusion). This is not
145
+ /// immediately observable to an opponent, to compute probabilities from an opponent's
146
+ /// point of view based solely on public information extra work must be done to correct
147
+ /// for the information leak
148
+ overwritten,
149
+ // TODO haze support
150
+ unused1,
151
+ unused2,
152
+ unused3,
153
+ unused4,
154
+ },
155
+ };
117
156
  }
118
157
 
119
158
  /// Information about the RNG that was observed during a Generation I battle `update` for a
@@ -140,14 +179,25 @@ pub const Action = packed struct(u64) {
140
179
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
141
180
  duration: u4 = 0,
142
181
 
143
- /// Observed values of various durations. Does not influence future RNG calls. TODO
144
- durations: Duration = .{},
182
+ /// TODO
183
+ sleep: Optional(Observation(.sleep)) = .None,
184
+ /// TODO
185
+ confusion: Optional(Observation(.confusion)) = .None,
186
+ /// TODO
187
+ disable: Optional(Observation(.disable)) = .None,
188
+ /// TODO
189
+ attacking: Optional(Observation(.attacking)) = .None,
190
+ /// TODO
191
+ binding: Optional(Observation(.binding)) = .None,
145
192
 
146
193
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
147
194
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
148
- move_slot: u4 = 0,
195
+ move_slot: u3 = 0,
149
196
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
150
- multi_hit: u4 = 0,
197
+ multi_hit: u3 = 0,
198
+
199
+ /// TODO
200
+ pp: u4 = 0,
151
201
 
152
202
  /// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
153
203
  psywave: u8 = 0,
@@ -155,39 +205,21 @@ pub const Action = packed struct(u64) {
155
205
  /// If not None, the Move to return for Rolls.metronome.
156
206
  metronome: Move = .None,
157
207
 
158
- pub const DURATIONS: u64 = 0x000000FFFF000000;
159
-
160
208
  pub const Field = std.meta.FieldEnum(Action);
161
209
 
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 };
210
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
169
211
  try fmt(a, w, false);
170
212
  }
171
213
 
172
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
214
+ const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
215
+
216
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
173
217
  try writer.writeByte('(');
174
218
  var printed = false;
175
- inline for (@typeInfo(Action).Struct.fields) |field| {
219
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
176
220
  const val = @field(self, field.name);
177
221
  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) {
222
+ .@"enum" => if (val != .None) {
191
223
  if (printed) try writer.writeAll(", ");
192
224
  if (shape) {
193
225
  try writer.print("{s}:?", .{field.name});
@@ -196,12 +228,14 @@ pub const Action = packed struct(u64) {
196
228
  if (val == .false) "!" else "",
197
229
  field.name,
198
230
  });
231
+ } else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
232
+ try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field.name });
199
233
  } else {
200
234
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
201
235
  }
202
236
  printed = true;
203
237
  },
204
- .Int => if (val != 0) {
238
+ .int => if (val != 0) {
205
239
  if (printed) try writer.writeAll(", ");
206
240
  if (shape) {
207
241
  try writer.print("{s}:?", .{field.name});
@@ -217,43 +251,73 @@ pub const Action = packed struct(u64) {
217
251
  }
218
252
  };
219
253
 
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
254
  /// TODO
228
255
  pub const Durations = extern struct {
229
- /// Information about the durations for Player 1.
256
+ /// TODO
230
257
  p1: Duration = .{},
231
- /// Information about the durations for Player 2.
258
+ /// TODO
232
259
  p2: Duration = .{},
233
260
 
234
261
  comptime {
235
- assert(@sizeOf(Durations) == 4);
262
+ assert(@sizeOf(Durations) == 8);
263
+ }
264
+
265
+ /// TODO
266
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
267
+ assert(isPointerTo(self, Durations));
268
+ return if (player == .P1) &self.p1 else &self.p2;
269
+ }
270
+
271
+ pub fn format(self: Durations, writer: *std.Io.Writer) !void {
272
+ try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
236
273
  }
237
274
  };
238
275
 
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.
276
+ pub const Sleeps = Array(6, u3);
277
+
278
+ /// TODO
279
+ pub const Duration = packed struct(u32) {
280
+ sleeps: Sleeps.T = 0,
245
281
  confusion: u3 = 0,
246
- /// The number of turns a Pokémon has been observed to be disabled.
247
282
  disable: u4 = 0,
248
- /// The number of turns a Pokémon has been observed to be attacking.
249
283
  attacking: u3 = 0,
250
- /// The number of turns a Pokémon has been observed to be binding their opponent.
251
284
  binding: u3 = 0,
252
285
 
253
- pub const Field = std.meta.FieldEnum(Duration);
286
+ _: u1 = 0,
287
+
288
+ pub const Field = enum { attacking, binding, sleep, disable, confusion };
289
+
290
+ pub fn format(
291
+ self: Duration,
292
+ writer: *std.Io.Writer,
293
+ ) !void {
294
+ try writer.writeByte('(');
295
+ var printed = false;
296
+ inline for (@typeInfo(Duration).@"struct".fields) |field| {
297
+ const val = @field(self, field.name);
298
+ switch (@typeInfo(@TypeOf(val))) {
299
+ .int => if (val != 0) {
300
+ if (printed) try writer.writeAll(", ");
301
+ if (comptime std.mem.eql(u8, field.name, "sleeps")) {
302
+ try writer.print("{s}:[", .{field.name});
303
+ for (0..6) |i| {
304
+ if (i != 0) try writer.writeByte(',');
305
+ try writer.print("{d}", .{Sleeps.get(val, i)});
306
+ }
307
+ try writer.writeAll("]");
308
+ } else {
309
+ try writer.print("{s}:{d}", .{ field.name, val });
310
+ }
311
+ printed = true;
312
+ },
313
+ else => unreachable,
314
+ }
315
+ }
316
+ try writer.writeByte(')');
317
+ }
254
318
  };
255
319
 
256
- pub const Commit = enum { hit, miss, binding };
320
+ pub const Commit = enum { hit, miss, binding, err, glitch };
257
321
 
258
322
  const uN = if (options.miss) u8 else u9;
259
323
 
@@ -268,14 +332,8 @@ pub fn Chance(comptime Rational: type) type {
268
332
  /// The actions taken by a hypothetical "chance player" that convey information about which
269
333
  /// RNG events were observed during a battle `update`.
270
334
  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 },
335
+ /// TODO
336
+ durations: Durations = .{},
279
337
 
280
338
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
281
339
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -299,7 +357,7 @@ pub fn Chance(comptime Rational: type) type {
299
357
  damage_roll: u8 = 0,
300
358
  hit: bool = false,
301
359
  hit_probablity: uN = 0,
302
- binding: u8 = 0,
360
+ glitch: bool = false,
303
361
  } = .{},
304
362
 
305
363
  /// Possible error returned by operations tracking chance probability.
@@ -310,7 +368,7 @@ pub fn Chance(comptime Rational: type) type {
310
368
  if (!enabled) return;
311
369
 
312
370
  self.probability.reset();
313
- self.actions.reset();
371
+ self.actions = .{};
314
372
  self.pending = .{};
315
373
  }
316
374
 
@@ -319,20 +377,18 @@ pub fn Chance(comptime Rational: type) type {
319
377
 
320
378
  assert(!showdown);
321
379
 
380
+ if (kind == .glitch and !self.pending.glitch) return;
381
+
322
382
  var action = self.actions.get(player);
323
383
 
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;
384
+ if (kind == .err and self.pending.crit_probablity != 0) {
385
+ try self.probability.update(self.pending.crit_probablity, 256);
386
+ action.critical_hit = if (self.pending.crit) .true else .false;
387
+ return;
331
388
  }
332
- if (kind == .binding) return;
333
389
 
334
390
  // 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)
391
+ // target is immune*/behind a sub/etc)
336
392
  if (self.pending.hit_probablity != 0) {
337
393
  try self.probability.update(self.pending.hit_probablity, 256);
338
394
  action.hit = if (self.pending.hit) .true else .false;
@@ -340,57 +396,59 @@ pub fn Chance(comptime Rational: type) type {
340
396
 
341
397
  // If the move actually lands we can commit any past critical hit / damage rolls. Note
342
398
  // 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
399
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
344
400
  // 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);
401
+ // passed to the critical hit helper).
402
+ //
403
+ // There is a special edge case with the division-by-zero freeze where we need to commit
404
+ // the status of critical hit roll the occured before the move missed to avoid missing
405
+ // out on the possibility that the alternative branch encountered an error
406
+ if (kind != .hit and !self.pending.glitch) {
407
+ return assert(!self.pending.crit or self.pending.crit_probablity > 0);
408
+ }
347
409
 
348
410
  if (self.pending.crit_probablity != 0) {
349
411
  try self.probability.update(self.pending.crit_probablity, 256);
350
412
  action.critical_hit = if (self.pending.crit) .true else .false;
351
- }
413
+ } else assert(!self.pending.glitch);
352
414
 
353
- if (self.pending.damage_roll > 0) {
415
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
354
416
  try self.probability.update(1, 39);
355
417
  action.damage = self.pending.damage_roll;
356
418
  }
357
419
  }
358
420
 
359
- pub fn clearPending(self: *Self) void {
421
+ pub fn glitch(self: *Self) void {
360
422
  if (!enabled) return;
361
423
 
362
- self.pending = .{};
424
+ assert(!showdown);
425
+ self.pending.glitch = true;
363
426
  }
364
427
 
365
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
428
+ pub fn clearPending(self: *Self) void {
366
429
  if (!enabled) return;
367
430
 
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
- }
431
+ assert(!showdown);
432
+ self.pending = .{};
377
433
  }
378
434
 
379
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
435
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
380
436
  if (!enabled) return;
381
437
 
382
- assert(in >= 1 and in <= 6);
383
- assert(out >= 1 and out <= 6);
438
+ assert(slot >= 1 and slot <= 6);
384
439
 
385
- var action = self.actions.get(player);
440
+ var d = self.durations.get(player);
386
441
 
387
- const slp = action.durations.sleep;
388
- action.durations = .{};
442
+ const out = Sleeps.get(d.sleeps, 0);
443
+ d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
444
+ d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
389
445
 
390
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
391
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
446
+ d.confusion = 0;
447
+ d.disable = 0;
448
+ d.attacking = 0;
449
+ d.binding = 0;
392
450
 
393
- self.actions.get(player.foe()).durations.binding = 0;
451
+ self.durations.get(player.foe()).binding = 0;
394
452
  }
395
453
 
396
454
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -489,108 +547,188 @@ pub fn Chance(comptime Rational: type) type {
489
547
  self.actions.get(player).multi_hit = n;
490
548
  }
491
549
 
492
- pub fn duration(
550
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
551
+ if (!enabled) return;
552
+
553
+ self.actions.get(player).duration = if (options.key) 2 else turns;
554
+ }
555
+
556
+ pub fn observe(
493
557
  self: *Self,
494
- comptime field: Duration.Field,
558
+ comptime field: Action.Field,
495
559
  player: Player,
496
- target: Player,
497
- turns: u4,
560
+ obs: Optional(Observation(.confusion)),
498
561
  ) void {
499
562
  if (!enabled) return;
500
563
 
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;
564
+ var a = self.actions.get(player);
565
+ const val = @field(a, @tagName(field));
566
+ switch (obs) {
567
+ .overwritten => {
568
+ assert(field == .confusion);
569
+ assert(val == .started or val == .continuing);
570
+ assert(a.duration > 0);
571
+ a.confusion = obs;
572
+ self.durations.get(player).confusion = 1;
573
+ },
574
+ .None => {
575
+ @field(a, @tagName(field)) = .None;
576
+ if (field == .sleep) {
577
+ var d = self.durations.get(player);
578
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
579
+ } else {
580
+ @field(self.durations.get(player), @tagName(field)) = 0;
581
+ }
582
+ },
583
+ .started => {
584
+ assert(val == .None or val == .ended);
585
+ assert(switch (field) {
586
+ .confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
587
+ .sleep, .disable => self.actions.get(player.foe()).duration > 0,
588
+ else => a.duration > 0,
589
+ });
590
+ @field(a, @tagName(field)) = .started;
591
+ if (field == .sleep) {
592
+ var d = self.durations.get(player);
593
+ d.sleeps = Sleeps.set(d.sleeps, 0, 1);
594
+ } else {
595
+ @field(self.durations.get(player), @tagName(field)) = 1;
596
+ }
597
+ },
598
+ else => unreachable,
509
599
  }
510
600
  }
511
601
 
512
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
602
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
513
603
  if (!enabled) return;
514
604
 
515
- var durations = &self.actions.get(player).durations;
516
- const n = durations.sleep;
517
- if (turns == 0) {
605
+ var a = self.actions.get(player);
606
+ assert(a.sleep == .None or a.sleep == .started);
607
+ a.sleep = obs;
608
+
609
+ var d = self.durations.get(player);
610
+ const n = Sleeps.get(d.sleeps, 0);
611
+
612
+ if (obs == .None) {
613
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
614
+ } else if (obs == .ended) {
518
615
  assert(n >= 1 and n <= 7);
519
616
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
520
- durations.sleep = 0;
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
521
618
  } else {
619
+ assert(obs == .continuing);
522
620
  assert(n >= 1 and n < 7);
523
621
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
524
- durations.sleep += 1;
622
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
525
623
  }
526
624
  }
527
625
 
528
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
626
+ pub fn confusion(
627
+ self: *Self,
628
+ player: Player,
629
+ obs: Optional(Observation(.confusion)),
630
+ ) Error!void {
529
631
  if (!enabled) return;
530
632
 
531
- var durations = &self.actions.get(player).durations;
532
- const n = durations.confusion;
533
- if (turns == 0) {
633
+ var a = self.actions.get(player);
634
+ assert(a.confusion == .None or a.confusion == .started);
635
+ a.confusion = obs;
636
+
637
+ var d = self.durations.get(player);
638
+ const n = d.confusion;
639
+
640
+ if (obs == .ended) {
534
641
  assert(n >= 2 and n <= 5);
535
642
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
536
- durations.confusion = 0;
643
+ d.confusion = 0;
537
644
  } else {
645
+ assert(obs == .continuing);
538
646
  assert(n >= 1 and n < 5);
539
647
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
540
- durations.confusion += 1;
648
+ d.confusion += 1;
541
649
  }
542
650
  }
543
651
 
544
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
652
+ pub fn disable(
653
+ self: *Self,
654
+ player: Player,
655
+ obs: Optional(Observation(.disable)),
656
+ ) Error!void {
545
657
  if (!enabled) return;
546
658
 
547
- var durations = &self.actions.get(player).durations;
548
- const n = durations.disable;
549
- if (turns == 0) {
659
+ var a = self.actions.get(player);
660
+ assert(a.disable == .None or a.disable == .started);
661
+ a.disable = obs;
662
+
663
+ var d = self.durations.get(player);
664
+ const n = d.disable;
665
+
666
+ if (obs == .ended) {
550
667
  assert(n >= 1 and n <= 8);
551
668
  if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
552
- durations.disable = 0;
669
+ d.disable = 0;
553
670
  } else {
671
+ assert(obs == .continuing);
554
672
  assert(n >= 1 and n < 8);
555
673
  try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
556
- durations.disable += 1;
674
+ d.disable += 1;
557
675
  }
558
676
  }
559
677
 
560
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
678
+ pub fn attacking(
679
+ self: *Self,
680
+ player: Player,
681
+ obs: Optional(Observation(.attacking)),
682
+ ) Error!void {
561
683
  if (!enabled) return;
562
684
 
563
- var durations = &self.actions.get(player).durations;
564
- const n = durations.attacking;
565
- if (turns == 0) {
685
+ var a = self.actions.get(player);
686
+ assert(a.attacking == .None or a.attacking == .started);
687
+ a.attacking = obs;
688
+
689
+ var d = self.durations.get(player);
690
+ const n = d.attacking;
691
+ if (obs == .ended) {
692
+ // "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
566
693
  assert(n >= 2 and n <= 3);
567
694
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
568
- durations.attacking = 0;
695
+ d.attacking = 0;
569
696
  } else {
697
+ assert(obs == .continuing);
570
698
  assert(n >= 1 and n < 3);
571
699
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
572
- durations.attacking += 1;
700
+ d.attacking += 1;
573
701
  }
574
702
  }
575
703
 
576
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
704
+ pub fn binding(
705
+ self: *Self,
706
+ player: Player,
707
+ obs: Optional(Observation(.binding)),
708
+ ) Error!void {
577
709
  if (!enabled) return;
578
710
 
579
- var durations = &self.actions.get(player).durations;
580
- const n = durations.binding;
711
+ var a = self.actions.get(player);
712
+ assert(a.binding == .None or a.binding == .started);
713
+ a.binding = obs;
714
+
715
+ var d = self.durations.get(player);
716
+ const n = d.binding;
581
717
 
582
718
  assert(n > 0);
583
719
  const p: u4 = if (n < 3) 3 else 1;
584
720
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
585
721
 
586
- if (turns == 0) {
722
+ if (obs == .ended) {
723
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
587
724
  assert(n >= 1 and n <= 4);
588
725
  if (n != 4) try self.probability.update(p, q);
589
- durations.binding = 0;
726
+ d.binding = 0;
590
727
  } else {
728
+ assert(obs == .continuing);
591
729
  assert(n >= 1 and n < 4);
592
730
  try self.probability.update(q - p, q);
593
- durations.binding += 1;
731
+ d.binding += 1;
594
732
  }
595
733
  }
596
734
 
@@ -607,6 +745,15 @@ pub fn Chance(comptime Rational: type) type {
607
745
  try self.probability.update(1, Move.METRONOME.len);
608
746
  self.actions.get(player).metronome = move;
609
747
  }
748
+
749
+ pub fn pp(self: *Self, player: Player, n: u4) bool {
750
+ if (!enabled) return true;
751
+
752
+ var a = self.actions.get(player);
753
+ if (n > 1 and a.pp == n) return false;
754
+ if (a.metronome != .None) a.pp += n;
755
+ return true;
756
+ }
610
757
  };
611
758
  }
612
759
 
@@ -707,16 +854,16 @@ test "Chance.damage" {
707
854
 
708
855
  try chance.damage(.P1, 217);
709
856
  if (showdown) {
710
- try expectValue(217, chance.actions.p1.damage);
857
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
711
858
  try expectProbability(&chance.probability, 1, 39);
712
859
  } else {
713
860
  try expectProbability(&chance.probability, 1, 1);
714
- try expectValue(0, chance.actions.p1.damage);
861
+ try expectValue(@as(u8, 0), chance.actions.p1.damage);
715
862
 
716
863
  try chance.commit(.P1, .hit);
717
864
 
718
865
  try expectProbability(&chance.probability, 1, 39);
719
- try expectValue(217, chance.actions.p1.damage);
866
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
720
867
  }
721
868
  }
722
869
 
@@ -768,13 +915,13 @@ test "Chance.moveSlot" {
768
915
 
769
916
  try chance.moveSlot(.P2, 2, &ms, 2);
770
917
  try expectProbability(&chance.probability, 1, 2);
771
- try expectValue(2, chance.actions.p2.move_slot);
918
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
772
919
 
773
920
  chance.reset();
774
921
 
775
922
  try chance.moveSlot(.P1, 1, &ms, 0);
776
923
  try expectProbability(&chance.probability, 1, 3);
777
- try expectValue(1, chance.actions.p1.move_slot);
924
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
778
925
  }
779
926
 
780
927
  test "Chance.multiHit" {
@@ -782,51 +929,43 @@ test "Chance.multiHit" {
782
929
 
783
930
  try chance.multiHit(.P1, 3);
784
931
  try expectProbability(&chance.probability, 3, 8);
785
- try expectValue(3, chance.actions.p1.multi_hit);
932
+ try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
786
933
 
787
934
  chance.reset();
788
935
 
789
936
  try chance.multiHit(.P2, 5);
790
937
  try expectProbability(&chance.probability, 1, 8);
791
- try expectValue(5, chance.actions.p2.multi_hit);
938
+ try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
792
939
  }
793
940
 
794
941
  test "Chance.duration" {
795
942
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
796
943
 
797
- chance.duration(.sleep, .P1, .P2, 2);
798
- try expectValue(2, chance.actions.p1.duration);
799
- try expectValue(1, chance.actions.p2.durations.sleep);
944
+ chance.duration(.P1, 2);
945
+ try expectValue(@as(u4, 2), chance.actions.p1.duration);
800
946
 
801
947
  chance.reset();
802
948
 
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);
949
+ chance.duration(.P2, 4);
950
+ try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
951
  }
813
952
 
814
953
  test "Chance.sleep" {
815
954
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
816
955
 
817
956
  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);
957
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
958
+ try chance.sleep(.P1, .ended);
820
959
  try expectProbability(&chance.probability, 1, d);
821
- try expectValue(0, chance.actions.p1.durations.sleep);
960
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
822
961
 
823
962
  chance.reset();
824
963
 
825
964
  if (i < 7) {
826
- chance.actions.p1.durations.sleep = @intCast(i);
827
- try chance.sleep(.P1, 1);
965
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
966
+ try chance.sleep(.P1, .continuing);
828
967
  try expectProbability(&chance.probability, d - 1, d);
829
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
968
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
830
969
 
831
970
  chance.reset();
832
971
  }
@@ -838,19 +977,19 @@ test "Chance.confusion" {
838
977
 
839
978
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
840
979
  if (i > 1) {
841
- chance.actions.p2.durations.confusion = @intCast(i);
842
- try chance.confusion(.P2, 0);
980
+ chance.durations.p2.confusion = @intCast(i);
981
+ try chance.confusion(.P2, .ended);
843
982
  try expectProbability(&chance.probability, 1, d);
844
- try expectValue(0, chance.actions.p2.durations.confusion);
983
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
845
984
 
846
985
  chance.reset();
847
986
  }
848
987
 
849
988
  if (i < 5) {
850
- chance.actions.p2.durations.confusion = @intCast(i);
851
- try chance.confusion(.P2, 1);
989
+ chance.durations.p2.confusion = @intCast(i);
990
+ try chance.confusion(.P2, .continuing);
852
991
  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);
992
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
854
993
 
855
994
  chance.reset();
856
995
  }
@@ -861,18 +1000,18 @@ test "Chance.disable" {
861
1000
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
862
1001
 
863
1002
  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);
1003
+ chance.durations.p1.disable = @intCast(i);
1004
+ try chance.disable(.P1, .ended);
866
1005
  try expectProbability(&chance.probability, 1, d);
867
- try expectValue(0, chance.actions.p1.durations.disable);
1006
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
868
1007
 
869
1008
  chance.reset();
870
1009
 
871
1010
  if (i < 8) {
872
- chance.actions.p1.durations.disable = @intCast(i);
873
- try chance.disable(.P1, 1);
1011
+ chance.durations.p1.disable = @intCast(i);
1012
+ try chance.disable(.P1, .continuing);
874
1013
  try expectProbability(&chance.probability, d - 1, d);
875
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
1014
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
876
1015
 
877
1016
  chance.reset();
878
1017
  }
@@ -884,19 +1023,19 @@ test "Chance.attacking" {
884
1023
 
885
1024
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
886
1025
  if (i > 1) {
887
- chance.actions.p2.durations.attacking = @intCast(i);
888
- try chance.attacking(.P2, 0);
1026
+ chance.durations.p2.attacking = @intCast(i);
1027
+ try chance.attacking(.P2, .ended);
889
1028
  try expectProbability(&chance.probability, 1, d);
890
- try expectValue(0, chance.actions.p2.durations.attacking);
1029
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
891
1030
 
892
1031
  chance.reset();
893
1032
  }
894
1033
 
895
1034
  if (i < 3) {
896
- chance.actions.p2.durations.attacking = @intCast(i);
897
- try chance.attacking(.P2, 1);
1035
+ chance.durations.p2.attacking = @intCast(i);
1036
+ try chance.attacking(.P2, .continuing);
898
1037
  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);
1038
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
900
1039
 
901
1040
  chance.reset();
902
1041
  }
@@ -910,18 +1049,18 @@ test "Chance.binding" {
910
1049
  const qs = [_]u8{ 8, 5, 2, 1 };
911
1050
 
912
1051
  for (ps, qs, 1..5) |p, q, i| {
913
- chance.actions.p1.durations.binding = @intCast(i);
914
- try chance.binding(.P1, 0);
1052
+ chance.durations.p1.binding = @intCast(i);
1053
+ try chance.binding(.P1, .ended);
915
1054
  try expectProbability(&chance.probability, p, q);
916
- try expectValue(0, chance.actions.p1.durations.binding);
1055
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
917
1056
 
918
1057
  chance.reset();
919
1058
 
920
1059
  if (i < 4) {
921
- chance.actions.p1.durations.binding = @intCast(i);
922
- try chance.binding(.P1, 1);
1060
+ chance.durations.p1.binding = @intCast(i);
1061
+ try chance.binding(.P1, .continuing);
923
1062
  try expectProbability(&chance.probability, q - p, q);
924
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1063
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
925
1064
 
926
1065
  chance.reset();
927
1066
  }
@@ -933,7 +1072,7 @@ test "Chance.psywave" {
933
1072
 
934
1073
  try chance.psywave(.P2, 100, 150);
935
1074
  try expectProbability(&chance.probability, 1, 150);
936
- try expectValue(101, chance.actions.p2.psywave);
1075
+ try expectValue(@as(u8, 101), chance.actions.p2.psywave);
937
1076
  }
938
1077
 
939
1078
  test "Chance.metronome" {
@@ -949,7 +1088,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
949
1088
 
950
1089
  r.reduce();
951
1090
  if (r.p != p or r.q != q) {
952
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
1091
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
953
1092
  return error.TestExpectedEqual;
954
1093
  }
955
1094
  }
@@ -971,16 +1110,16 @@ const Null = struct {
971
1110
  _ = .{ self, player, kind };
972
1111
  }
973
1112
 
974
- pub fn clearPending(self: Null) void {
1113
+ pub fn glitch(self: Null) void {
975
1114
  _ = .{self};
976
1115
  }
977
1116
 
978
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
979
- _ = .{ self, player, haze };
1117
+ pub fn clearPending(self: Null) void {
1118
+ _ = .{self};
980
1119
  }
981
1120
 
982
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
983
- _ = .{ self, player, in, out };
1121
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1122
+ _ = .{ self, player, slot };
984
1123
  }
985
1124
 
986
1125
  pub fn speedTie(self: Null, p1: bool) Error!void {
@@ -1019,34 +1158,53 @@ const Null = struct {
1019
1158
  _ = .{ self, player, n };
1020
1159
  }
1021
1160
 
1022
- pub fn duration(
1161
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1162
+ _ = .{ self, player, turns };
1163
+ }
1164
+
1165
+ pub fn observe(
1023
1166
  self: Null,
1024
- comptime field: Duration.Field,
1167
+ comptime field: Action.Field,
1025
1168
  player: Player,
1026
- target: Player,
1027
- turns: u4,
1169
+ obs: Optional(Observation(.confusion)),
1028
1170
  ) void {
1029
- _ = .{ self, field, player, target, turns };
1171
+ _ = .{ self, field, player, obs };
1030
1172
  }
1031
1173
 
1032
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1033
- _ = .{ self, player, turns };
1174
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
1175
+ _ = .{ self, player, obs };
1034
1176
  }
1035
1177
 
1036
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1037
- _ = .{ self, player, turns };
1178
+ pub fn confusion(
1179
+ self: Null,
1180
+ player: Player,
1181
+ obs: Optional(Observation(.confusion)),
1182
+ ) Error!void {
1183
+ _ = .{ self, player, obs };
1038
1184
  }
1039
1185
 
1040
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1041
- _ = .{ self, player, turns };
1186
+ pub fn disable(
1187
+ self: Null,
1188
+ player: Player,
1189
+ obs: Optional(Observation(.disable)),
1190
+ ) Error!void {
1191
+ _ = .{ self, player, obs };
1042
1192
  }
1043
1193
 
1044
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1045
- _ = .{ self, player, turns };
1194
+ pub fn attacking(
1195
+ self: Null,
1196
+ player: Player,
1197
+ obs: Optional(Observation(.attacking)),
1198
+ ) Error!void {
1199
+ _ = .{ self, player, obs };
1046
1200
  }
1047
1201
 
1048
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1049
- _ = .{ self, player, turns };
1202
+ pub fn binding(
1203
+ self: Null,
1204
+ player: Player,
1205
+ obs: Optional(Observation(.binding)),
1206
+ ) Error!void {
1207
+ _ = .{ self, player, obs };
1050
1208
  }
1051
1209
 
1052
1210
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1056,4 +1214,9 @@ const Null = struct {
1056
1214
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1057
1215
  _ = .{ self, player, move };
1058
1216
  }
1217
+
1218
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1219
+ _ = .{ self, player, n };
1220
+ return true;
1221
+ }
1059
1222
  };