@pkmn/engine 0.1.0-dev.305a932f → 0.1.0-dev.30895c5a

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