@pkmn/engine 0.1.0-dev.5570b5c5 → 0.1.0-dev.587596a6

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