@pkmn/engine 0.1.0-dev.d4119c62 → 0.1.0-dev.d4bd80b1

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 (83) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +46 -18
  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/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +27 -5
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +20 -1
  26. package/build/pkg/protocol.js +109 -107
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +53 -78
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +152 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +359 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +33 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +330 -104
  39. package/package.json +60 -134
  40. package/src/bin/install-pkmn-engine +32 -21
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +255 -0
  43. package/src/lib/{binding → bindings}/node.zig +8 -21
  44. package/src/lib/bindings/wasm.zig +59 -0
  45. package/src/lib/common/array.zig +77 -0
  46. package/src/lib/common/data.zig +4 -4
  47. package/src/lib/common/debug.zig +39 -8
  48. package/src/lib/common/js.zig +4 -2
  49. package/src/lib/common/optional.zig +14 -10
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +63 -67
  52. package/src/lib/common/rational.zig +128 -32
  53. package/src/lib/common/rng.zig +7 -9
  54. package/src/lib/common/util.zig +50 -1
  55. package/src/lib/gen1/calc.zig +686 -159
  56. package/src/lib/gen1/chance.zig +405 -280
  57. package/src/lib/gen1/data/moves.zig +2 -4
  58. package/src/lib/gen1/data/species.zig +2 -4
  59. package/src/lib/gen1/data/types.zig +1 -2
  60. package/src/lib/gen1/data.zig +24 -27
  61. package/src/lib/gen1/helpers.zig +92 -548
  62. package/src/lib/gen1/mechanics.zig +379 -258
  63. package/src/lib/gen2/calc.zig +37 -27
  64. package/src/lib/gen2/chance.zig +84 -496
  65. package/src/lib/gen2/data/items.zig +1 -3
  66. package/src/lib/gen2/data/moves.zig +2 -4
  67. package/src/lib/gen2/data/species.zig +1 -3
  68. package/src/lib/gen2/data/types.zig +1 -2
  69. package/src/lib/gen2/data.zig +19 -22
  70. package/src/lib/gen2/generated.zig +19 -26
  71. package/src/lib/gen2/helpers.zig +46 -25
  72. package/src/lib/gen2/mechanics.zig +217 -163
  73. package/src/lib/node.zig +18 -0
  74. package/src/lib/pkmn.zig +37 -15
  75. package/src/lib/wasm.zig +17 -0
  76. package/src/tools/display/dom.ts +95 -0
  77. package/src/tools/display/ui.css +354 -0
  78. package/build/test/display.js +0 -400
  79. package/build/test/display.js.map +0 -1
  80. package/src/lib/binding/wasm.zig +0 -12
  81. package/src/lib/gen1/README.md +0 -894
  82. package/src/lib/gen2/README.md +0 -329
  83. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,26 +1,31 @@
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 data = @import("./data.zig");
18
-
12
+ const Array = array.Array;
13
+ const assert = std.debug.assert;
19
14
  const enabled = options.chance;
20
- const showdown = options.showdown;
21
-
15
+ const expect = std.testing.expect;
16
+ const expectEqual = std.testing.expectEqual;
17
+ const isPointerTo = util.isPointerTo;
22
18
  const Move = data.Move;
23
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;
24
+ const showdown = options.showdown;
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;
24
29
 
25
30
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
26
31
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
@@ -37,17 +42,11 @@ pub const Actions = extern struct {
37
42
  }
38
43
 
39
44
  /// Returns the `Action` for the given `player`.
40
- pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
41
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
45
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
46
+ assert(isPointerTo(self, Actions));
42
47
  return if (player == .P1) &self.p1 else &self.p2;
43
48
  }
44
49
 
45
- /// Perform a reset by clearing fields which should not persist across updates.
46
- pub fn reset(self: *Actions) void {
47
- self.p1.reset();
48
- self.p2.reset();
49
- }
50
-
51
50
  /// Returns true if `a` is equal to `b`.
52
51
  pub fn eql(a: Actions, b: Actions) bool {
53
52
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
@@ -57,20 +56,15 @@ pub const Actions = extern struct {
57
56
  /// same shape if they have the same fields set (though those fields need not necessarily be
58
57
  /// set to the same value).
59
58
  pub fn matches(a: Actions, b: Actions) bool {
60
- inline for (@typeInfo(Actions).Struct.fields) |player| {
61
- 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| {
62
61
  const a_val = @field(@field(a, player.name), field.name);
63
62
  const b_val = @field(@field(b, player.name), field.name);
64
63
 
65
64
  switch (@typeInfo(@TypeOf(a_val))) {
66
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
67
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
68
- return false;
69
- }
70
- },
71
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
65
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
66
  return false,
73
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
67
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
74
68
  else => unreachable,
75
69
  }
76
70
  }
@@ -78,6 +72,25 @@ pub const Actions = extern struct {
78
72
  return true;
79
73
  }
80
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
+
81
94
  pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
82
95
  try writer.writeAll("<P1 = ");
83
96
  try self.p1.fmt(writer, shape);
@@ -96,9 +109,6 @@ test Actions {
96
109
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
97
110
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
98
111
  const c: Actions = .{ .p1 = .{ .hit = .true } };
99
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
100
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
101
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
112
 
103
113
  try expect(a.eql(a));
104
114
  try expect(!a.eql(b));
@@ -111,10 +121,22 @@ test Actions {
111
121
  try expect(b.matches(a));
112
122
  try expect(!a.matches(c));
113
123
  try expect(!c.matches(a));
114
- try expect(d.matches(e));
115
- try expect(!d.matches(f));
116
124
  }
117
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
+
118
140
  /// Information about the RNG that was observed during a Generation I battle `update` for a
119
141
  /// single player.
120
142
  pub const Action = packed struct(u64) {
@@ -139,12 +161,25 @@ pub const Action = packed struct(u64) {
139
161
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
140
162
  duration: u4 = 0,
141
163
 
142
- /// Observed values of various durations. Does not influence future RNG calls. TODO
143
- 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,
144
176
 
145
177
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
146
178
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
147
179
  move_slot: u4 = 0,
180
+
181
+ /// TODO
182
+ pp: u4 = 0,
148
183
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
149
184
  multi_hit: u4 = 0,
150
185
 
@@ -154,15 +189,8 @@ pub const Action = packed struct(u64) {
154
189
  /// If not None, the Move to return for Rolls.metronome.
155
190
  metronome: Move = .None,
156
191
 
157
- pub const DURATIONS: u64 = 0x000000FFFF000000;
158
-
159
192
  pub const Field = std.meta.FieldEnum(Action);
160
193
 
161
- /// Perform a reset by clearing fields which should not persist across updates.
162
- pub fn reset(self: *Action) void {
163
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
164
- }
165
-
166
194
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
167
195
  _ = .{ f, o };
168
196
  try fmt(a, w, false);
@@ -171,22 +199,10 @@ pub const Action = packed struct(u64) {
171
199
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
172
200
  try writer.writeByte('(');
173
201
  var printed = false;
174
- inline for (@typeInfo(Action).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
175
203
  const val = @field(self, field.name);
176
204
  switch (@typeInfo(@TypeOf(val))) {
177
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
178
- const v = @field(val, f.name);
179
- if (v != 0) {
180
- if (printed) try writer.writeAll(", ");
181
- if (shape) {
182
- try writer.print("{s}:?", .{f.name});
183
- } else {
184
- try writer.print("{s}:{d}", .{ f.name, v });
185
- }
186
- printed = true;
187
- }
188
- },
189
- .Enum => if (val != .None) {
205
+ Enum => if (val != .None) {
190
206
  if (printed) try writer.writeAll(", ");
191
207
  if (shape) {
192
208
  try writer.print("{s}:?", .{field.name});
@@ -195,12 +211,19 @@ pub const Action = packed struct(u64) {
195
211
  if (val == .false) "!" else "",
196
212
  field.name,
197
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
+ });
198
221
  } else {
199
222
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
200
223
  }
201
224
  printed = true;
202
225
  },
203
- .Int => if (val != 0) {
226
+ Int => if (val != 0) {
204
227
  if (printed) try writer.writeAll(", ");
205
228
  if (shape) {
206
229
  try writer.print("{s}:?", .{field.name});
@@ -216,43 +239,82 @@ pub const Action = packed struct(u64) {
216
239
  }
217
240
  };
218
241
 
219
- test Action {
220
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
221
- a.reset();
222
-
223
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
224
- }
225
-
226
242
  /// TODO
227
243
  pub const Durations = extern struct {
228
- /// Information about the durations for Player 1.
244
+ /// TODO
229
245
  p1: Duration = .{},
230
- /// Information about the durations for Player 2.
246
+ /// TODO
231
247
  p2: Duration = .{},
232
248
 
233
249
  comptime {
234
- 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 });
235
267
  }
236
268
  };
237
269
 
238
- /// Observed values for various durations that need to be tracked in order to properly
239
- /// deduplicate transitions with a primary key. TODO
240
- pub const Duration = packed struct(u16) {
241
- /// The number of turns a Pokémon has been observed to be sleeping.
242
- sleep: u3 = 0,
243
- /// The number of turns a Pokémon has been observed to be confused.
270
+ pub const Sleeps = Array(6, u3);
271
+
272
+ /// TODO
273
+ pub const Duration = packed struct(u32) {
274
+ sleeps: Sleeps.T = 0,
244
275
  confusion: u3 = 0,
245
- /// The number of turns a Pokémon has been observed to be disabled.
246
276
  disable: u4 = 0,
247
- /// The number of turns a Pokémon has been observed to be attacking.
248
277
  attacking: u3 = 0,
249
- /// The number of turns a Pokémon has been observed to be binding their opponent.
250
278
  binding: u3 = 0,
251
279
 
252
- pub const Field = std.meta.FieldEnum(Duration);
280
+ _: u1 = 0,
281
+
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(')');
312
+ }
253
313
  };
254
314
 
255
- 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;
256
318
 
257
319
  /// Tracks chance actions and their associated probability during a Generation I battle update when
258
320
  /// `options.chance` is enabled.
@@ -265,14 +327,8 @@ pub fn Chance(comptime Rational: type) type {
265
327
  /// The actions taken by a hypothetical "chance player" that convey information about which
266
328
  /// RNG events were observed during a battle `update`.
267
329
  actions: Actions = .{},
268
-
269
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
270
- // other durations which are all tied to volatiles, sleep's counter persists through
271
- // switching and so we must store it here. The indices of these arrays correspond to the
272
- // `order` field of a Side. This information could be stored in actions but size is a
273
- // concern so this is tracked separately as actions only purports to track RNG events
274
- // observed during a single `update` (not across updates).
275
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
330
+ /// TODO
331
+ durations: Durations = .{},
276
332
 
277
333
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
278
334
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -290,16 +346,13 @@ pub fn Chance(comptime Rational: type) type {
290
346
  //
291
347
  // Finally, because each player's actions are processed sequentially we only need a single
292
348
  // shared structure to track this information (though it needs to be cleared appropriately)
293
- pending: if (showdown) struct {
294
- hit: bool = false,
295
- hit_probablity: u8 = 0,
296
- } else struct {
349
+ pending: if (showdown) struct {} else struct {
297
350
  crit: bool = false,
298
351
  crit_probablity: u8 = 0,
299
352
  damage_roll: u8 = 0,
300
353
  hit: bool = false,
301
- hit_probablity: u8 = 0,
302
- binding: u8 = 0,
354
+ hit_probablity: uN = 0,
355
+ glitch: bool = false,
303
356
  } = .{},
304
357
 
305
358
  /// Possible error returned by operations tracking chance probability.
@@ -307,86 +360,90 @@ pub fn Chance(comptime Rational: type) type {
307
360
 
308
361
  /// Convenience helper to clear fields which typically should be cleared between updates.
309
362
  pub fn reset(self: *Self) void {
363
+ if (!enabled) return;
364
+
310
365
  self.probability.reset();
311
- self.actions.reset();
366
+ self.actions = .{};
312
367
  self.pending = .{};
313
368
  }
314
369
 
315
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
+
316
377
  var action = self.actions.get(player);
317
378
 
318
- // We need to handle binding specially because we still need to commit it when attacking
319
- // into a Pokémon that is immune
320
- if (!showdown) {
321
- if (kind != .miss and self.pending.binding != 0) {
322
- assert(action.duration == 0);
323
- action.duration = @intCast(self.pending.binding);
324
- assert(action.durations.binding == 0);
325
- action.durations.binding = 1;
326
- }
327
- if (kind == .binding) return;
328
- } else assert(kind != .binding);
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
+ }
329
384
 
330
385
  // Always commit the hit result if we make it here (commit won't be called at all if the
331
- // target is immune/behind a sub/etc)
386
+ // target is immune*/behind a sub/etc)
332
387
  if (self.pending.hit_probablity != 0) {
333
388
  try self.probability.update(self.pending.hit_probablity, 256);
334
389
  action.hit = if (self.pending.hit) .true else .false;
335
390
  }
336
391
 
337
- if (showdown) return;
338
-
339
- // If the move actually lands we can commit any past critical hit / damage rolls. We
340
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
341
- // to a damage roll is actually a no-op
342
- if (kind == .hit and self.pending.damage_roll > 0) {
343
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
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
+ }
344
404
 
345
- if (self.pending.crit_probablity != 0) {
346
- try self.probability.update(self.pending.crit_probablity, 256);
347
- action.critical_hit = if (self.pending.crit) .true else .false;
348
- }
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);
349
409
 
410
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
350
411
  try self.probability.update(1, 39);
351
412
  action.damage = self.pending.damage_roll;
352
413
  }
353
414
  }
354
415
 
355
- pub fn clearPending(self: *Self) void {
416
+ pub fn glitch(self: *Self) void {
356
417
  if (!enabled) return;
357
418
 
358
- self.pending = .{};
419
+ assert(!showdown);
420
+ self.pending.glitch = true;
359
421
  }
360
422
 
361
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
423
+ pub fn clearPending(self: *Self) void {
362
424
  if (!enabled) return;
363
425
 
364
- var action = self.actions.get(player);
365
- if (haze) |status| {
366
- if (status) action.durations.sleep = 0;
367
- action.durations.confusion = 0;
368
- action.durations.disable = 0;
369
- } else {
370
- action.durations.attacking = 0;
371
- action.durations.binding = 0;
372
- }
426
+ assert(!showdown);
427
+ self.pending = .{};
373
428
  }
374
429
 
375
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
430
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
376
431
  if (!enabled) return;
377
432
 
378
- assert(in >= 1 and in <= 6);
379
- assert(out >= 1 and out <= 6);
433
+ assert(slot >= 1 and slot <= 6);
380
434
 
381
- var action = self.actions.get(player);
435
+ var d = self.durations.get(player);
382
436
 
383
- const slp = action.durations.sleep;
384
- 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);
385
440
 
386
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
387
- 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;
388
445
 
389
- self.actions.get(player.foe()).durations.binding = 0;
446
+ self.durations.get(player.foe()).binding = 0;
390
447
  }
391
448
 
392
449
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -397,12 +454,17 @@ pub fn Chance(comptime Rational: type) type {
397
454
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
398
455
  }
399
456
 
400
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
457
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
401
458
  if (!enabled) return;
402
459
 
403
460
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
404
- self.pending.hit = ok;
405
- 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
+ }
406
468
  }
407
469
 
408
470
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -480,108 +542,172 @@ pub fn Chance(comptime Rational: type) type {
480
542
  self.actions.get(player).multi_hit = n;
481
543
  }
482
544
 
483
- 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(
484
552
  self: *Self,
485
- comptime field: Duration.Field,
553
+ comptime field: Action.Field,
486
554
  player: Player,
487
- target: Player,
488
- turns: u4,
555
+ obs: Optional(Confusion),
489
556
  ) void {
490
557
  if (!enabled) return;
491
558
 
492
- if (!showdown and field == .binding) {
493
- self.pending.binding = if (options.key) 1 else turns;
494
- } else {
495
- var durations = &self.actions.get(target).durations;
496
- assert(@field(durations, @tagName(field)) == 0 or
497
- (field == .confusion and player == target));
498
- @field(durations, @tagName(field)) = 1;
499
- self.actions.get(player).duration = if (options.key) 1 else turns;
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,
500
594
  }
501
595
  }
502
596
 
503
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
597
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
504
598
  if (!enabled) return;
505
599
 
506
- var durations = &self.actions.get(player).durations;
507
- const n = durations.sleep;
508
- 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) {
509
610
  assert(n >= 1 and n <= 7);
510
611
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
511
- durations.sleep = 0;
612
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
512
613
  } else {
614
+ assert(obs == .continuing);
513
615
  assert(n >= 1 and n < 7);
514
616
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
515
- durations.sleep += 1;
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
516
618
  }
517
619
  }
518
620
 
519
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
621
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
520
622
  if (!enabled) return;
521
623
 
522
- var durations = &self.actions.get(player).durations;
523
- const n = durations.confusion;
524
- if (turns == 0) {
624
+ var a = self.actions.get(player);
625
+ assert(a.confusion == .None or a.confusion == .started);
626
+ a.confusion = obs;
627
+
628
+ var d = self.durations.get(player);
629
+ const n = d.confusion;
630
+
631
+ if (obs == .ended) {
525
632
  assert(n >= 2 and n <= 5);
526
633
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
527
- durations.confusion = 0;
634
+ d.confusion = 0;
528
635
  } else {
636
+ assert(obs == .continuing);
529
637
  assert(n >= 1 and n < 5);
530
638
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
531
- durations.confusion += 1;
639
+ d.confusion += 1;
532
640
  }
533
641
  }
534
642
 
535
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
643
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
536
644
  if (!enabled) return;
537
645
 
538
- var durations = &self.actions.get(player).durations;
539
- const n = durations.disable;
540
- if (turns == 0) {
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) {
541
654
  assert(n >= 1 and n <= 8);
542
655
  if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
543
- durations.disable = 0;
656
+ d.disable = 0;
544
657
  } else {
658
+ assert(obs == .continuing);
545
659
  assert(n >= 1 and n < 8);
546
660
  try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
547
- durations.disable += 1;
661
+ d.disable += 1;
548
662
  }
549
663
  }
550
664
 
551
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
665
+ pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
552
666
  if (!enabled) return;
553
667
 
554
- var durations = &self.actions.get(player).durations;
555
- const n = durations.attacking;
556
- 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
557
676
  assert(n >= 2 and n <= 3);
558
677
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
559
- durations.attacking = 0;
678
+ d.attacking = 0;
560
679
  } else {
680
+ assert(obs == .continuing);
561
681
  assert(n >= 1 and n < 3);
562
682
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
563
- durations.attacking += 1;
683
+ d.attacking += 1;
564
684
  }
565
685
  }
566
686
 
567
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
687
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
568
688
  if (!enabled) return;
569
689
 
570
- var durations = &self.actions.get(player).durations;
571
- 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;
572
696
 
573
697
  assert(n > 0);
574
698
  const p: u4 = if (n < 3) 3 else 1;
575
699
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
576
700
 
577
- if (turns == 0) {
701
+ if (obs == .ended) {
702
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
578
703
  assert(n >= 1 and n <= 4);
579
704
  if (n != 4) try self.probability.update(p, q);
580
- durations.binding = 0;
705
+ d.binding = 0;
581
706
  } else {
707
+ assert(obs == .continuing);
582
708
  assert(n >= 1 and n < 4);
583
709
  try self.probability.update(q - p, q);
584
- durations.binding += 1;
710
+ d.binding += 1;
585
711
  }
586
712
  }
587
713
 
@@ -598,6 +724,15 @@ pub fn Chance(comptime Rational: type) type {
598
724
  try self.probability.update(1, Move.METRONOME.len);
599
725
  self.actions.get(player).metronome = move;
600
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
+ }
601
736
  };
602
737
  }
603
738
 
@@ -620,19 +755,25 @@ test "Chance.speedTie" {
620
755
  test "Chance.hit" {
621
756
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
622
757
 
623
- chance.hit(true, 229);
624
- try expectProbability(&chance.probability, 1, 1);
625
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
626
- try chance.commit(.P1, .hit);
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
+ }
627
765
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
628
766
  try expectProbability(&chance.probability, 229, 256);
629
767
 
630
768
  chance.reset();
631
769
 
632
- chance.hit(false, 255);
633
- try expectProbability(&chance.probability, 1, 1);
634
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
635
- try chance.commit(.P2, .miss);
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
+ }
636
777
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
637
778
  try expectProbability(&chance.probability, 1, 256);
638
779
  }
@@ -648,26 +789,18 @@ test "Chance.criticalHit" {
648
789
  try expectProbability(&chance.probability, 1, 1);
649
790
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
650
791
 
651
- try chance.commit(.P1, .hit);
652
- try expectProbability(&chance.probability, 1, 1);
653
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
792
+ try chance.hit(.P1, true, 128);
654
793
 
655
- chance.hit(true, 128);
656
-
657
- try chance.commit(.P1, .hit);
658
- try expectProbability(&chance.probability, 1, 2);
794
+ try expectProbability(&chance.probability, 1, 1);
659
795
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
660
796
 
661
- chance.probability.reset();
662
797
  try chance.damage(.P1, 217);
663
798
 
664
- try chance.commit(.P1, .miss);
665
- try expectProbability(&chance.probability, 1, 2);
799
+ try expectProbability(&chance.probability, 1, 1);
666
800
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
667
801
 
668
- chance.probability.reset();
669
-
670
802
  try chance.commit(.P1, .hit);
803
+
671
804
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
672
805
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
673
806
  }
@@ -682,20 +815,14 @@ test "Chance.criticalHit" {
682
815
  try expectProbability(&chance.probability, 1, 1);
683
816
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
684
817
 
685
- try chance.commit(.P1, .hit);
686
- try expectProbability(&chance.probability, 1, 1);
687
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
688
-
689
- chance.hit(true, 128);
818
+ try chance.hit(.P1, true, 128);
690
819
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 2);
820
+ try expectProbability(&chance.probability, 1, 1);
693
821
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
694
822
 
695
- chance.probability.reset();
696
823
  try chance.damage(.P1, 217);
697
-
698
824
  try chance.commit(.P1, .hit);
825
+
699
826
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
700
827
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
701
828
  }
@@ -712,11 +839,8 @@ test "Chance.damage" {
712
839
  try expectProbability(&chance.probability, 1, 1);
713
840
  try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
841
 
715
- try chance.commit(.P1, .miss);
716
- try expectProbability(&chance.probability, 1, 1);
717
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
718
-
719
842
  try chance.commit(.P1, .hit);
843
+
720
844
  try expectProbability(&chance.probability, 1, 39);
721
845
  try expectValue(@as(u8, 217), chance.actions.p1.damage);
722
846
  }
@@ -796,38 +920,31 @@ test "Chance.multiHit" {
796
920
  test "Chance.duration" {
797
921
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
798
922
 
799
- chance.duration(.sleep, .P1, .P2, 2);
923
+ chance.duration(.P1, 2);
800
924
  try expectValue(@as(u4, 2), chance.actions.p1.duration);
801
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
802
925
 
803
926
  chance.reset();
804
927
 
805
- chance.duration(.binding, .P2, .P2, 4);
806
- if (!showdown) {
807
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
808
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
809
- try chance.commit(.P2, .hit);
810
- }
928
+ chance.duration(.P2, 4);
811
929
  try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
813
930
  }
814
931
 
815
932
  test "Chance.sleep" {
816
933
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
817
934
 
818
935
  for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
819
- chance.actions.p1.durations.sleep = @intCast(i);
820
- try chance.sleep(.P1, 0);
936
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
937
+ try chance.sleep(.P1, .ended);
821
938
  try expectProbability(&chance.probability, 1, d);
822
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
939
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
823
940
 
824
941
  chance.reset();
825
942
 
826
943
  if (i < 7) {
827
- chance.actions.p1.durations.sleep = @intCast(i);
828
- 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);
829
946
  try expectProbability(&chance.probability, d - 1, d);
830
- 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));
831
948
 
832
949
  chance.reset();
833
950
  }
@@ -839,19 +956,19 @@ test "Chance.confusion" {
839
956
 
840
957
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
841
958
  if (i > 1) {
842
- chance.actions.p2.durations.confusion = @intCast(i);
843
- try chance.confusion(.P2, 0);
959
+ chance.durations.p2.confusion = @intCast(i);
960
+ try chance.confusion(.P2, .ended);
844
961
  try expectProbability(&chance.probability, 1, d);
845
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
962
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
846
963
 
847
964
  chance.reset();
848
965
  }
849
966
 
850
967
  if (i < 5) {
851
- chance.actions.p2.durations.confusion = @intCast(i);
852
- try chance.confusion(.P2, 1);
968
+ chance.durations.p2.confusion = @intCast(i);
969
+ try chance.confusion(.P2, .continuing);
853
970
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
854
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
971
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
855
972
 
856
973
  chance.reset();
857
974
  }
@@ -862,18 +979,18 @@ test "Chance.disable" {
862
979
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
863
980
 
864
981
  for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
865
- chance.actions.p1.durations.disable = @intCast(i);
866
- try chance.disable(.P1, 0);
982
+ chance.durations.p1.disable = @intCast(i);
983
+ try chance.disable(.P1, .ended);
867
984
  try expectProbability(&chance.probability, 1, d);
868
- try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
985
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
869
986
 
870
987
  chance.reset();
871
988
 
872
989
  if (i < 8) {
873
- chance.actions.p1.durations.disable = @intCast(i);
874
- try chance.disable(.P1, 1);
990
+ chance.durations.p1.disable = @intCast(i);
991
+ try chance.disable(.P1, .continuing);
875
992
  try expectProbability(&chance.probability, d - 1, d);
876
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
993
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
877
994
 
878
995
  chance.reset();
879
996
  }
@@ -885,19 +1002,19 @@ test "Chance.attacking" {
885
1002
 
886
1003
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
887
1004
  if (i > 1) {
888
- chance.actions.p2.durations.attacking = @intCast(i);
889
- try chance.attacking(.P2, 0);
1005
+ chance.durations.p2.attacking = @intCast(i);
1006
+ try chance.attacking(.P2, .ended);
890
1007
  try expectProbability(&chance.probability, 1, d);
891
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
1008
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
892
1009
 
893
1010
  chance.reset();
894
1011
  }
895
1012
 
896
1013
  if (i < 3) {
897
- chance.actions.p2.durations.attacking = @intCast(i);
898
- try chance.attacking(.P2, 1);
1014
+ chance.durations.p2.attacking = @intCast(i);
1015
+ try chance.attacking(.P2, .continuing);
899
1016
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
900
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
1017
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
901
1018
 
902
1019
  chance.reset();
903
1020
  }
@@ -911,18 +1028,18 @@ test "Chance.binding" {
911
1028
  const qs = [_]u8{ 8, 5, 2, 1 };
912
1029
 
913
1030
  for (ps, qs, 1..5) |p, q, i| {
914
- chance.actions.p1.durations.binding = @intCast(i);
915
- try chance.binding(.P1, 0);
1031
+ chance.durations.p1.binding = @intCast(i);
1032
+ try chance.binding(.P1, .ended);
916
1033
  try expectProbability(&chance.probability, p, q);
917
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
1034
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
918
1035
 
919
1036
  chance.reset();
920
1037
 
921
1038
  if (i < 4) {
922
- chance.actions.p1.durations.binding = @intCast(i);
923
- try chance.binding(.P1, 1);
1039
+ chance.durations.p1.binding = @intCast(i);
1040
+ try chance.binding(.P1, .continuing);
924
1041
  try expectProbability(&chance.probability, q - p, q);
925
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1042
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
926
1043
 
927
1044
  chance.reset();
928
1045
  }
@@ -972,24 +1089,24 @@ const Null = struct {
972
1089
  _ = .{ self, player, kind };
973
1090
  }
974
1091
 
975
- pub fn clearPending(self: Null) void {
1092
+ pub fn glitch(self: Null) void {
976
1093
  _ = .{self};
977
1094
  }
978
1095
 
979
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
980
- _ = .{ self, player, haze };
1096
+ pub fn clearPending(self: Null) void {
1097
+ _ = .{self};
981
1098
  }
982
1099
 
983
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
984
- _ = .{ self, player, in, out };
1100
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1101
+ _ = .{ self, player, slot };
985
1102
  }
986
1103
 
987
1104
  pub fn speedTie(self: Null, p1: bool) Error!void {
988
1105
  _ = .{ self, p1 };
989
1106
  }
990
1107
 
991
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
992
- _ = .{ self, ok, accuracy };
1108
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
1109
+ _ = .{ self, player, ok, accuracy };
993
1110
  }
994
1111
 
995
1112
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -1020,34 +1137,37 @@ const Null = struct {
1020
1137
  _ = .{ self, player, n };
1021
1138
  }
1022
1139
 
1023
- pub fn duration(
1140
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1141
+ _ = .{ self, player, turns };
1142
+ }
1143
+
1144
+ pub fn observe(
1024
1145
  self: Null,
1025
- comptime field: Duration.Field,
1146
+ comptime field: Action.Field,
1026
1147
  player: Player,
1027
- target: Player,
1028
- turns: u4,
1148
+ obs: Optional(Confusion),
1029
1149
  ) void {
1030
- _ = .{ self, field, player, target, turns };
1150
+ _ = .{ self, field, player, obs };
1031
1151
  }
1032
1152
 
1033
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1034
- _ = .{ self, player, turns };
1153
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1154
+ _ = .{ self, player, obs };
1035
1155
  }
1036
1156
 
1037
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1038
- _ = .{ self, player, turns };
1157
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1158
+ _ = .{ self, player, obs };
1039
1159
  }
1040
1160
 
1041
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1042
- _ = .{ self, player, turns };
1161
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1162
+ _ = .{ self, player, obs };
1043
1163
  }
1044
1164
 
1045
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1046
- _ = .{ self, player, turns };
1165
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1166
+ _ = .{ self, player, obs };
1047
1167
  }
1048
1168
 
1049
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1050
- _ = .{ self, player, turns };
1169
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1170
+ _ = .{ self, player, obs };
1051
1171
  }
1052
1172
 
1053
1173
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1057,4 +1177,9 @@ const Null = struct {
1057
1177
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1058
1178
  _ = .{ self, player, move };
1059
1179
  }
1180
+
1181
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1182
+ _ = .{ self, player, n };
1183
+ return true;
1184
+ }
1060
1185
  };