@pkmn/engine 0.1.0-dev.c5fe6d07 → 0.1.0-dev.d1b39ff0

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