@pkmn/engine 0.1.0-dev.eaae4aa6 → 0.1.0-dev.f3aab252

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