@pkmn/engine 0.1.0-dev.f3aaee39 → 0.1.0-dev.f3b63b86

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