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

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