@pkmn/engine 0.1.0-dev.d56bdadd → 0.1.0-dev.d73cd76c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +66 -32
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +47 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +110 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon/wasm.d.ts +2 -0
  10. package/build/pkg/addon/wasm.js +53 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +26 -4
  13. package/build/pkg/addon.js +39 -71
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/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 +42 -91
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +29 -1
  26. package/build/pkg/protocol.js +109 -107
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +84 -73
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +66 -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 +360 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +157 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +357 -135
  39. package/package.json +61 -132
  40. package/src/bin/install-pkmn-engine +47 -21
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +201 -0
  43. package/src/lib/{binding → bindings}/node.zig +15 -32
  44. package/src/lib/bindings/wasm.zig +51 -0
  45. package/src/lib/common/array.zig +70 -0
  46. package/src/lib/common/data.zig +4 -4
  47. package/src/lib/common/debug.zig +45 -29
  48. package/src/lib/common/js.zig +3 -7
  49. package/src/lib/common/optional.zig +27 -23
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +565 -552
  52. package/src/lib/common/rational.zig +340 -253
  53. package/src/lib/common/rng.zig +7 -9
  54. package/src/lib/common/util.zig +53 -1
  55. package/src/lib/gen1/calc.zig +724 -175
  56. package/src/lib/gen1/chance.zig +486 -300
  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 +3 -4
  60. package/src/lib/gen1/data.zig +29 -32
  61. package/src/lib/gen1/helpers.zig +113 -561
  62. package/src/lib/gen1/mechanics.zig +611 -488
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +111 -511
  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 +27 -29
  70. package/src/lib/gen2/generated.zig +22 -29
  71. package/src/lib/gen2/helpers.zig +68 -39
  72. package/src/lib/gen2/mechanics.zig +504 -430
  73. package/src/lib/napi.h +2 -0
  74. package/src/lib/node.zig +14 -0
  75. package/src/lib/pkmn.zig +125 -29
  76. package/src/lib/wasm.zig +13 -0
  77. package/src/tools/display/dom.ts +108 -0
  78. package/src/tools/display/ui.css +354 -0
  79. package/build/test/display.js +0 -400
  80. package/build/test/display.js.map +0 -1
  81. package/src/lib/binding/wasm.zig +0 -12
  82. package/src/lib/gen1/README.md +0 -894
  83. package/src/lib/gen2/README.md +0 -329
  84. 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,40 +38,43 @@ 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
- return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
48
+ return @as(u64, @bitCast(a.p1)) == @as(u64, @bitCast(b.p1)) and
49
+ @as(u64, @bitCast(a.p2)) == @as(u64, @bitCast(b.p2));
54
50
  }
55
51
 
56
52
  /// Returns true if `a` has the same "shape" as `b`, where `Actions` are defined to have the
57
53
  /// same shape if they have the same fields set (though those fields need not necessarily be
58
54
  /// set to the same value).
59
55
  pub fn matches(a: Actions, b: Actions) bool {
60
- inline for (@typeInfo(Actions).Struct.fields) |player| {
61
- inline for (@typeInfo(Action).Struct.fields) |field| {
62
- const a_val = @field(@field(a, player.name), field.name);
63
- const b_val = @field(@field(b, player.name), field.name);
56
+ inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
57
+ @typeInfo(Actions).@"struct".fields
58
+ else
59
+ @typeInfo(Actions).@"struct".field_names) |player|
60
+ {
61
+ const player_name = if (@hasField(@TypeOf(player), "name"))
62
+ player.name
63
+ else
64
+ player;
65
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
66
+ @typeInfo(Action).@"struct".fields
67
+ else
68
+ @typeInfo(Action).@"struct".field_names) |field|
69
+ {
70
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
71
+ const a_val = @field(@field(a, player_name), field_name);
72
+ const b_val = @field(@field(b, player_name), field_name);
64
73
 
65
74
  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))
75
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
76
  return false,
73
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
77
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
74
78
  else => unreachable,
75
79
  }
76
80
  }
@@ -78,7 +82,26 @@ pub const Actions = extern struct {
78
82
  return true;
79
83
  }
80
84
 
81
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
85
+ /// TODO
86
+ pub fn matchesAny(a: Actions, bs: []Actions, i: usize) bool {
87
+ for (bs, 0..) |b, j| {
88
+ // TODO: is skipping this redundant check worth it?
89
+ if (i == j) continue;
90
+ if (b.matches(a)) return true;
91
+ }
92
+ return false;
93
+ }
94
+
95
+ /// TODO
96
+ pub fn relax(actions: Actions) Actions {
97
+ if (!options.overwrite) return actions;
98
+ var a = actions;
99
+ if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
100
+ if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
101
+ return a;
102
+ }
103
+
104
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
82
105
  try writer.writeAll("<P1 = ");
83
106
  try self.p1.fmt(writer, shape);
84
107
  try writer.writeAll(", P2 = ");
@@ -86,8 +109,7 @@ pub const Actions = extern struct {
86
109
  try writer.writeAll(">");
87
110
  }
88
111
 
89
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
90
- _ = .{ f, o };
112
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
91
113
  try fmt(a, w, false);
92
114
  }
93
115
  };
@@ -96,9 +118,6 @@ test Actions {
96
118
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
97
119
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
98
120
  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
121
 
103
122
  try expect(a.eql(a));
104
123
  try expect(!a.eql(b));
@@ -111,8 +130,43 @@ test Actions {
111
130
  try expect(b.matches(a));
112
131
  try expect(!a.matches(c));
113
132
  try expect(!c.matches(a));
114
- try expect(d.matches(e));
115
- try expect(!d.matches(f));
133
+ }
134
+
135
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
136
+ pub fn Observation(comptime field: Duration.Field) type {
137
+ return switch (field) {
138
+ .attacking, .binding => enum(u2) {
139
+ started,
140
+ continuing,
141
+ ended,
142
+ },
143
+ .sleep, .disable => enum(u3) {
144
+ started,
145
+ continuing,
146
+ ended,
147
+ // TODO haze support
148
+ unused1,
149
+ unused2,
150
+ unused3,
151
+ unused4,
152
+ },
153
+ .confusion => enum(u4) {
154
+ started,
155
+ continuing,
156
+ ended,
157
+ /// An indicator that an existing confusion duration has been overwritten by a new
158
+ /// confusion duration due to a Thrashing move ending ("hidden" confusion). This is not
159
+ /// immediately observable to an opponent, to compute probabilities from an opponent's
160
+ /// point of view based solely on public information extra work must be done to correct
161
+ /// for the information leak
162
+ overwritten,
163
+ // TODO haze support
164
+ unused1,
165
+ unused2,
166
+ unused3,
167
+ unused4,
168
+ },
169
+ };
116
170
  }
117
171
 
118
172
  /// Information about the RNG that was observed during a Generation I battle `update` for a
@@ -139,14 +193,25 @@ pub const Action = packed struct(u64) {
139
193
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
140
194
  duration: u4 = 0,
141
195
 
142
- /// Observed values of various durations. Does not influence future RNG calls. TODO
143
- durations: Duration = .{},
196
+ /// TODO
197
+ sleep: Optional(Observation(.sleep)) = .None,
198
+ /// TODO
199
+ confusion: Optional(Observation(.confusion)) = .None,
200
+ /// TODO
201
+ disable: Optional(Observation(.disable)) = .None,
202
+ /// TODO
203
+ attacking: Optional(Observation(.attacking)) = .None,
204
+ /// TODO
205
+ binding: Optional(Observation(.binding)) = .None,
144
206
 
145
207
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
146
208
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
147
- move_slot: u4 = 0,
209
+ move_slot: u3 = 0,
148
210
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
149
- multi_hit: u4 = 0,
211
+ multi_hit: u3 = 0,
212
+
213
+ /// TODO
214
+ pp: u4 = 0,
150
215
 
151
216
  /// If not 0, psywave - 1 should be returned as the damage roll for Rolls.psywave.
152
217
  psywave: u8 = 0,
@@ -154,58 +219,47 @@ pub const Action = packed struct(u64) {
154
219
  /// If not None, the Move to return for Rolls.metronome.
155
220
  metronome: Move = .None,
156
221
 
157
- pub const DURATIONS: u64 = 0x000000FFFF000000;
158
-
159
222
  pub const Field = std.meta.FieldEnum(Action);
160
223
 
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
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
167
- _ = .{ f, o };
224
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
168
225
  try fmt(a, w, false);
169
226
  }
170
227
 
171
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
228
+ const SYMBOLS = [_][]const u8{ "+", "", "-", "#" };
229
+
230
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
172
231
  try writer.writeByte('(');
173
232
  var printed = false;
174
- inline for (@typeInfo(Action).Struct.fields) |field| {
175
- const val = @field(self, field.name);
233
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
234
+ @typeInfo(Action).@"struct".fields
235
+ else
236
+ @typeInfo(Action).@"struct".field_names) |field|
237
+ {
238
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
239
+ const val = @field(self, field_name);
176
240
  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) {
241
+ .@"enum" => if (val != .None) {
190
242
  if (printed) try writer.writeAll(", ");
191
243
  if (shape) {
192
- try writer.print("{s}:?", .{field.name});
244
+ try writer.print("{s}:?", .{field_name});
193
245
  } else if (@TypeOf(val) == Optional(bool)) {
194
246
  try writer.print("{s}{s}", .{
195
247
  if (val == .false) "!" else "",
196
- field.name,
248
+ field_name,
197
249
  });
250
+ } else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
251
+ try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field_name });
198
252
  } else {
199
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
253
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
200
254
  }
201
255
  printed = true;
202
256
  },
203
- .Int => if (val != 0) {
257
+ .int => if (val != 0) {
204
258
  if (printed) try writer.writeAll(", ");
205
259
  if (shape) {
206
- try writer.print("{s}:?", .{field.name});
260
+ try writer.print("{s}:?", .{field_name});
207
261
  } else {
208
- try writer.print("{s}:{d}", .{ field.name, val });
262
+ try writer.print("{s}:{d}", .{ field_name, val });
209
263
  }
210
264
  printed = true;
211
265
  },
@@ -216,43 +270,80 @@ pub const Action = packed struct(u64) {
216
270
  }
217
271
  };
218
272
 
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
273
  /// TODO
227
274
  pub const Durations = extern struct {
228
- /// Information about the durations for Player 1.
275
+ /// TODO
229
276
  p1: Duration = .{},
230
- /// Information about the durations for Player 2.
277
+ /// TODO
231
278
  p2: Duration = .{},
232
279
 
233
280
  comptime {
234
- assert(@sizeOf(Durations) == 4);
281
+ assert(@sizeOf(Durations) == 8);
282
+ }
283
+
284
+ /// TODO
285
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
286
+ assert(isPointerTo(self, Durations));
287
+ return if (player == .P1) &self.p1 else &self.p2;
288
+ }
289
+
290
+ pub fn format(self: Durations, writer: *std.Io.Writer) !void {
291
+ try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
235
292
  }
236
293
  };
237
294
 
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.
295
+ pub const Sleeps = Array(6, u3);
296
+
297
+ /// TODO
298
+ pub const Duration = packed struct(u32) {
299
+ sleeps: Sleeps.T = 0,
244
300
  confusion: u3 = 0,
245
- /// The number of turns a Pokémon has been observed to be disabled.
246
301
  disable: u4 = 0,
247
- /// The number of turns a Pokémon has been observed to be attacking.
248
302
  attacking: u3 = 0,
249
- /// The number of turns a Pokémon has been observed to be binding their opponent.
250
303
  binding: u3 = 0,
251
304
 
252
- pub const Field = std.meta.FieldEnum(Duration);
305
+ _: u1 = 0,
306
+
307
+ pub const Field = enum { attacking, binding, sleep, disable, confusion };
308
+
309
+ pub fn format(
310
+ self: Duration,
311
+ writer: *std.Io.Writer,
312
+ ) !void {
313
+ try writer.writeByte('(');
314
+ var printed = false;
315
+ inline for (if (@hasField(@TypeOf(@typeInfo(Duration).@"struct"), "fields"))
316
+ @typeInfo(Duration).@"struct".fields
317
+ else
318
+ @typeInfo(Duration).@"struct".field_names) |field|
319
+ {
320
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
321
+ const val = @field(self, field_name);
322
+ switch (@typeInfo(@TypeOf(val))) {
323
+ .int => if (val != 0) {
324
+ if (printed) try writer.writeAll(", ");
325
+ if (comptime std.mem.eql(u8, field_name, "sleeps")) {
326
+ try writer.print("{s}:[", .{field_name});
327
+ for (0..6) |i| {
328
+ if (i != 0) try writer.writeByte(',');
329
+ try writer.print("{d}", .{Sleeps.get(val, i)});
330
+ }
331
+ try writer.writeAll("]");
332
+ } else {
333
+ try writer.print("{s}:{d}", .{ field_name, val });
334
+ }
335
+ printed = true;
336
+ },
337
+ else => unreachable,
338
+ }
339
+ }
340
+ try writer.writeByte(')');
341
+ }
253
342
  };
254
343
 
255
- pub const Commit = enum { hit, miss, binding };
344
+ pub const Commit = enum { hit, miss, binding, err, glitch };
345
+
346
+ const uN = if (options.miss) u8 else u9;
256
347
 
257
348
  /// Tracks chance actions and their associated probability during a Generation I battle update when
258
349
  /// `options.chance` is enabled.
@@ -265,14 +356,8 @@ pub fn Chance(comptime Rational: type) type {
265
356
  /// The actions taken by a hypothetical "chance player" that convey information about which
266
357
  /// RNG events were observed during a battle `update`.
267
358
  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 },
359
+ /// TODO
360
+ durations: Durations = .{},
276
361
 
277
362
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
278
363
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -290,16 +375,13 @@ pub fn Chance(comptime Rational: type) type {
290
375
  //
291
376
  // Finally, because each player's actions are processed sequentially we only need a single
292
377
  // 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 {
378
+ pending: if (showdown) struct {} else struct {
297
379
  crit: bool = false,
298
380
  crit_probablity: u8 = 0,
299
381
  damage_roll: u8 = 0,
300
382
  hit: bool = false,
301
- hit_probablity: u8 = 0,
302
- binding: u8 = 0,
383
+ hit_probablity: uN = 0,
384
+ glitch: bool = false,
303
385
  } = .{},
304
386
 
305
387
  /// Possible error returned by operations tracking chance probability.
@@ -307,86 +389,90 @@ pub fn Chance(comptime Rational: type) type {
307
389
 
308
390
  /// Convenience helper to clear fields which typically should be cleared between updates.
309
391
  pub fn reset(self: *Self) void {
392
+ if (!enabled) return;
393
+
310
394
  self.probability.reset();
311
- self.actions.reset();
395
+ self.actions = .{};
312
396
  self.pending = .{};
313
397
  }
314
398
 
315
399
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
400
+ if (!enabled) return;
401
+
402
+ assert(!showdown);
403
+
404
+ if (kind == .glitch and !self.pending.glitch) return;
405
+
316
406
  var action = self.actions.get(player);
317
407
 
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);
408
+ if (kind == .err and self.pending.crit_probablity != 0) {
409
+ try self.probability.update(self.pending.crit_probablity, 256);
410
+ action.critical_hit = if (self.pending.crit) .true else .false;
411
+ return;
412
+ }
329
413
 
330
414
  // 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)
415
+ // target is immune*/behind a sub/etc)
332
416
  if (self.pending.hit_probablity != 0) {
333
417
  try self.probability.update(self.pending.hit_probablity, 256);
334
418
  action.hit = if (self.pending.hit) .true else .false;
335
419
  }
336
420
 
337
- if (showdown) return;
421
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
422
+ // we can't rely on the presence or absence of a damage roll to determine whether a
423
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
424
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
425
+ // passed to the critical hit helper).
426
+ //
427
+ // There is a special edge case with the division-by-zero freeze where we need to commit
428
+ // the status of critical hit roll the occured before the move missed to avoid missing
429
+ // out on the possibility that the alternative branch encountered an error
430
+ if (kind != .hit and !self.pending.glitch) {
431
+ return assert(!self.pending.crit or self.pending.crit_probablity > 0);
432
+ }
338
433
 
339
- // If the move actually lands we can commit any past critical hit / damage rolls. We
340
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
341
- // to a damage roll is actually a no-op
342
- if (kind == .hit and self.pending.damage_roll > 0) {
343
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
344
-
345
- if (self.pending.crit_probablity != 0) {
346
- try self.probability.update(self.pending.crit_probablity, 256);
347
- action.critical_hit = if (self.pending.crit) .true else .false;
348
- }
434
+ if (self.pending.crit_probablity != 0) {
435
+ try self.probability.update(self.pending.crit_probablity, 256);
436
+ action.critical_hit = if (self.pending.crit) .true else .false;
437
+ } else assert(!self.pending.glitch);
349
438
 
439
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
350
440
  try self.probability.update(1, 39);
351
441
  action.damage = self.pending.damage_roll;
352
442
  }
353
443
  }
354
444
 
355
- pub fn clearPending(self: *Self) void {
445
+ pub fn glitch(self: *Self) void {
356
446
  if (!enabled) return;
357
447
 
358
- self.pending = .{};
448
+ assert(!showdown);
449
+ self.pending.glitch = true;
359
450
  }
360
451
 
361
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
452
+ pub fn clearPending(self: *Self) void {
362
453
  if (!enabled) return;
363
454
 
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
- }
455
+ assert(!showdown);
456
+ self.pending = .{};
373
457
  }
374
458
 
375
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
459
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
376
460
  if (!enabled) return;
377
461
 
378
- assert(in >= 1 and in <= 6);
379
- assert(out >= 1 and out <= 6);
462
+ assert(slot >= 1 and slot <= 6);
380
463
 
381
- var action = self.actions.get(player);
464
+ var d = self.durations.get(player);
382
465
 
383
- const slp = action.durations.sleep;
384
- action.durations = .{};
466
+ const out = Sleeps.get(d.sleeps, 0);
467
+ d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
468
+ d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
385
469
 
386
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
387
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
470
+ d.confusion = 0;
471
+ d.disable = 0;
472
+ d.attacking = 0;
473
+ d.binding = 0;
388
474
 
389
- self.actions.get(player.foe()).durations.binding = 0;
475
+ self.durations.get(player.foe()).binding = 0;
390
476
  }
391
477
 
392
478
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -397,12 +483,17 @@ pub fn Chance(comptime Rational: type) type {
397
483
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
398
484
  }
399
485
 
400
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
486
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
401
487
  if (!enabled) return;
402
488
 
403
489
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
404
- self.pending.hit = ok;
405
- self.pending.hit_probablity = p;
490
+ if (showdown) {
491
+ try self.probability.update(p, 256);
492
+ self.actions.get(player).hit = if (ok) .true else .false;
493
+ } else {
494
+ self.pending.hit = ok;
495
+ self.pending.hit_probablity = p;
496
+ }
406
497
  }
407
498
 
408
499
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -480,108 +571,188 @@ pub fn Chance(comptime Rational: type) type {
480
571
  self.actions.get(player).multi_hit = n;
481
572
  }
482
573
 
483
- pub fn duration(
574
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
575
+ if (!enabled) return;
576
+
577
+ self.actions.get(player).duration = if (options.key) 2 else turns;
578
+ }
579
+
580
+ pub fn observe(
484
581
  self: *Self,
485
- comptime field: Duration.Field,
582
+ comptime field: Action.Field,
486
583
  player: Player,
487
- target: Player,
488
- turns: u4,
584
+ obs: Optional(Observation(.confusion)),
489
585
  ) void {
490
586
  if (!enabled) return;
491
587
 
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;
588
+ var a = self.actions.get(player);
589
+ const val = @field(a, @tagName(field));
590
+ switch (obs) {
591
+ .overwritten => {
592
+ assert(field == .confusion);
593
+ assert(val == .started or val == .continuing);
594
+ assert(a.duration > 0);
595
+ a.confusion = obs;
596
+ self.durations.get(player).confusion = 1;
597
+ },
598
+ .None => {
599
+ @field(a, @tagName(field)) = .None;
600
+ if (field == .sleep) {
601
+ var d = self.durations.get(player);
602
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
603
+ } else {
604
+ @field(self.durations.get(player), @tagName(field)) = 0;
605
+ }
606
+ },
607
+ .started => {
608
+ assert(val == .None or val == .ended);
609
+ assert(switch (field) {
610
+ .confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
611
+ .sleep, .disable => self.actions.get(player.foe()).duration > 0,
612
+ else => a.duration > 0,
613
+ });
614
+ @field(a, @tagName(field)) = .started;
615
+ if (field == .sleep) {
616
+ var d = self.durations.get(player);
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, 1);
618
+ } else {
619
+ @field(self.durations.get(player), @tagName(field)) = 1;
620
+ }
621
+ },
622
+ else => unreachable,
500
623
  }
501
624
  }
502
625
 
503
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
626
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation(.sleep))) Error!void {
504
627
  if (!enabled) return;
505
628
 
506
- var durations = &self.actions.get(player).durations;
507
- const n = durations.sleep;
508
- if (turns == 0) {
629
+ var a = self.actions.get(player);
630
+ assert(a.sleep == .None or a.sleep == .started);
631
+ a.sleep = obs;
632
+
633
+ var d = self.durations.get(player);
634
+ const n = Sleeps.get(d.sleeps, 0);
635
+
636
+ if (obs == .None) {
637
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
638
+ } else if (obs == .ended) {
509
639
  assert(n >= 1 and n <= 7);
510
640
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
511
- durations.sleep = 0;
641
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
512
642
  } else {
643
+ assert(obs == .continuing);
513
644
  assert(n >= 1 and n < 7);
514
645
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
515
- durations.sleep += 1;
646
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
516
647
  }
517
648
  }
518
649
 
519
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
650
+ pub fn confusion(
651
+ self: *Self,
652
+ player: Player,
653
+ obs: Optional(Observation(.confusion)),
654
+ ) Error!void {
520
655
  if (!enabled) return;
521
656
 
522
- var durations = &self.actions.get(player).durations;
523
- const n = durations.confusion;
524
- if (turns == 0) {
657
+ var a = self.actions.get(player);
658
+ assert(a.confusion == .None or a.confusion == .started);
659
+ a.confusion = obs;
660
+
661
+ var d = self.durations.get(player);
662
+ const n = d.confusion;
663
+
664
+ if (obs == .ended) {
525
665
  assert(n >= 2 and n <= 5);
526
666
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
527
- durations.confusion = 0;
667
+ d.confusion = 0;
528
668
  } else {
669
+ assert(obs == .continuing);
529
670
  assert(n >= 1 and n < 5);
530
671
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
531
- durations.confusion += 1;
672
+ d.confusion += 1;
532
673
  }
533
674
  }
534
675
 
535
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
676
+ pub fn disable(
677
+ self: *Self,
678
+ player: Player,
679
+ obs: Optional(Observation(.disable)),
680
+ ) Error!void {
536
681
  if (!enabled) return;
537
682
 
538
- var durations = &self.actions.get(player).durations;
539
- const n = durations.disable;
540
- if (turns == 0) {
683
+ var a = self.actions.get(player);
684
+ assert(a.disable == .None or a.disable == .started);
685
+ a.disable = obs;
686
+
687
+ var d = self.durations.get(player);
688
+ const n = d.disable;
689
+
690
+ if (obs == .ended) {
541
691
  assert(n >= 1 and n <= 8);
542
692
  if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
543
- durations.disable = 0;
693
+ d.disable = 0;
544
694
  } else {
695
+ assert(obs == .continuing);
545
696
  assert(n >= 1 and n < 8);
546
697
  try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
547
- durations.disable += 1;
698
+ d.disable += 1;
548
699
  }
549
700
  }
550
701
 
551
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
702
+ pub fn attacking(
703
+ self: *Self,
704
+ player: Player,
705
+ obs: Optional(Observation(.attacking)),
706
+ ) Error!void {
552
707
  if (!enabled) return;
553
708
 
554
- var durations = &self.actions.get(player).durations;
555
- const n = durations.attacking;
556
- if (turns == 0) {
709
+ var a = self.actions.get(player);
710
+ assert(a.attacking == .None or a.attacking == .started);
711
+ a.attacking = obs;
712
+
713
+ var d = self.durations.get(player);
714
+ const n = d.attacking;
715
+ if (obs == .ended) {
716
+ // "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
557
717
  assert(n >= 2 and n <= 3);
558
718
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
559
- durations.attacking = 0;
719
+ d.attacking = 0;
560
720
  } else {
721
+ assert(obs == .continuing);
561
722
  assert(n >= 1 and n < 3);
562
723
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
563
- durations.attacking += 1;
724
+ d.attacking += 1;
564
725
  }
565
726
  }
566
727
 
567
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
728
+ pub fn binding(
729
+ self: *Self,
730
+ player: Player,
731
+ obs: Optional(Observation(.binding)),
732
+ ) Error!void {
568
733
  if (!enabled) return;
569
734
 
570
- var durations = &self.actions.get(player).durations;
571
- const n = durations.binding;
735
+ var a = self.actions.get(player);
736
+ assert(a.binding == .None or a.binding == .started);
737
+ a.binding = obs;
738
+
739
+ var d = self.durations.get(player);
740
+ const n = d.binding;
572
741
 
573
742
  assert(n > 0);
574
743
  const p: u4 = if (n < 3) 3 else 1;
575
744
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
576
745
 
577
- if (turns == 0) {
746
+ if (obs == .ended) {
747
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
578
748
  assert(n >= 1 and n <= 4);
579
749
  if (n != 4) try self.probability.update(p, q);
580
- durations.binding = 0;
750
+ d.binding = 0;
581
751
  } else {
752
+ assert(obs == .continuing);
582
753
  assert(n >= 1 and n < 4);
583
754
  try self.probability.update(q - p, q);
584
- durations.binding += 1;
755
+ d.binding += 1;
585
756
  }
586
757
  }
587
758
 
@@ -598,6 +769,15 @@ pub fn Chance(comptime Rational: type) type {
598
769
  try self.probability.update(1, Move.METRONOME.len);
599
770
  self.actions.get(player).metronome = move;
600
771
  }
772
+
773
+ pub fn pp(self: *Self, player: Player, n: u4) bool {
774
+ if (!enabled) return true;
775
+
776
+ var a = self.actions.get(player);
777
+ if (n > 1 and a.pp == n) return false;
778
+ if (a.metronome != .None) a.pp += n;
779
+ return true;
780
+ }
601
781
  };
602
782
  }
603
783
 
@@ -620,19 +800,25 @@ test "Chance.speedTie" {
620
800
  test "Chance.hit" {
621
801
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
622
802
 
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);
803
+ try chance.hit(.P1, true, 229);
804
+ if (!showdown) {
805
+ try expectProbability(&chance.probability, 1, 1);
806
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
807
+
808
+ try chance.commit(.P1, .hit);
809
+ }
627
810
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
628
811
  try expectProbability(&chance.probability, 229, 256);
629
812
 
630
813
  chance.reset();
631
814
 
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);
815
+ try chance.hit(.P2, false, 255);
816
+ if (!showdown) {
817
+ try expectProbability(&chance.probability, 1, 1);
818
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
819
+
820
+ try chance.commit(.P2, .miss);
821
+ }
636
822
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
637
823
  try expectProbability(&chance.probability, 1, 256);
638
824
  }
@@ -648,26 +834,18 @@ test "Chance.criticalHit" {
648
834
  try expectProbability(&chance.probability, 1, 1);
649
835
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
650
836
 
651
- try chance.commit(.P1, .hit);
652
- try expectProbability(&chance.probability, 1, 1);
653
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
654
-
655
- chance.hit(true, 128);
837
+ try chance.hit(.P1, true, 128);
656
838
 
657
- try chance.commit(.P1, .hit);
658
- try expectProbability(&chance.probability, 1, 2);
839
+ try expectProbability(&chance.probability, 1, 1);
659
840
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
660
841
 
661
- chance.probability.reset();
662
842
  try chance.damage(.P1, 217);
663
843
 
664
- try chance.commit(.P1, .miss);
665
- try expectProbability(&chance.probability, 1, 2);
844
+ try expectProbability(&chance.probability, 1, 1);
666
845
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
667
846
 
668
- chance.probability.reset();
669
-
670
847
  try chance.commit(.P1, .hit);
848
+
671
849
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
672
850
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
673
851
  }
@@ -682,20 +860,14 @@ test "Chance.criticalHit" {
682
860
  try expectProbability(&chance.probability, 1, 1);
683
861
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
684
862
 
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);
863
+ try chance.hit(.P1, true, 128);
690
864
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 2);
865
+ try expectProbability(&chance.probability, 1, 1);
693
866
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
694
867
 
695
- chance.probability.reset();
696
868
  try chance.damage(.P1, 217);
697
-
698
869
  try chance.commit(.P1, .hit);
870
+
699
871
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
700
872
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
701
873
  }
@@ -712,11 +884,8 @@ test "Chance.damage" {
712
884
  try expectProbability(&chance.probability, 1, 1);
713
885
  try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
886
 
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
887
  try chance.commit(.P1, .hit);
888
+
720
889
  try expectProbability(&chance.probability, 1, 39);
721
890
  try expectValue(@as(u8, 217), chance.actions.p1.damage);
722
891
  }
@@ -770,13 +939,13 @@ test "Chance.moveSlot" {
770
939
 
771
940
  try chance.moveSlot(.P2, 2, &ms, 2);
772
941
  try expectProbability(&chance.probability, 1, 2);
773
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
942
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
774
943
 
775
944
  chance.reset();
776
945
 
777
946
  try chance.moveSlot(.P1, 1, &ms, 0);
778
947
  try expectProbability(&chance.probability, 1, 3);
779
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
948
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
780
949
  }
781
950
 
782
951
  test "Chance.multiHit" {
@@ -796,38 +965,31 @@ test "Chance.multiHit" {
796
965
  test "Chance.duration" {
797
966
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
798
967
 
799
- chance.duration(.sleep, .P1, .P2, 2);
968
+ chance.duration(.P1, 2);
800
969
  try expectValue(@as(u4, 2), chance.actions.p1.duration);
801
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
802
970
 
803
971
  chance.reset();
804
972
 
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
- }
973
+ chance.duration(.P2, 4);
811
974
  try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
813
975
  }
814
976
 
815
977
  test "Chance.sleep" {
816
978
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
817
979
 
818
980
  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);
981
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
982
+ try chance.sleep(.P1, .ended);
821
983
  try expectProbability(&chance.probability, 1, d);
822
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
984
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
823
985
 
824
986
  chance.reset();
825
987
 
826
988
  if (i < 7) {
827
- chance.actions.p1.durations.sleep = @intCast(i);
828
- try chance.sleep(.P1, 1);
989
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
990
+ try chance.sleep(.P1, .continuing);
829
991
  try expectProbability(&chance.probability, d - 1, d);
830
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
992
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
831
993
 
832
994
  chance.reset();
833
995
  }
@@ -839,19 +1001,19 @@ test "Chance.confusion" {
839
1001
 
840
1002
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
841
1003
  if (i > 1) {
842
- chance.actions.p2.durations.confusion = @intCast(i);
843
- try chance.confusion(.P2, 0);
1004
+ chance.durations.p2.confusion = @intCast(i);
1005
+ try chance.confusion(.P2, .ended);
844
1006
  try expectProbability(&chance.probability, 1, d);
845
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
1007
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
846
1008
 
847
1009
  chance.reset();
848
1010
  }
849
1011
 
850
1012
  if (i < 5) {
851
- chance.actions.p2.durations.confusion = @intCast(i);
852
- try chance.confusion(.P2, 1);
1013
+ chance.durations.p2.confusion = @intCast(i);
1014
+ try chance.confusion(.P2, .continuing);
853
1015
  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);
1016
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
855
1017
 
856
1018
  chance.reset();
857
1019
  }
@@ -862,18 +1024,18 @@ test "Chance.disable" {
862
1024
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
863
1025
 
864
1026
  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);
1027
+ chance.durations.p1.disable = @intCast(i);
1028
+ try chance.disable(.P1, .ended);
867
1029
  try expectProbability(&chance.probability, 1, d);
868
- try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
1030
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
869
1031
 
870
1032
  chance.reset();
871
1033
 
872
1034
  if (i < 8) {
873
- chance.actions.p1.durations.disable = @intCast(i);
874
- try chance.disable(.P1, 1);
1035
+ chance.durations.p1.disable = @intCast(i);
1036
+ try chance.disable(.P1, .continuing);
875
1037
  try expectProbability(&chance.probability, d - 1, d);
876
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
1038
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
877
1039
 
878
1040
  chance.reset();
879
1041
  }
@@ -885,19 +1047,19 @@ test "Chance.attacking" {
885
1047
 
886
1048
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
887
1049
  if (i > 1) {
888
- chance.actions.p2.durations.attacking = @intCast(i);
889
- try chance.attacking(.P2, 0);
1050
+ chance.durations.p2.attacking = @intCast(i);
1051
+ try chance.attacking(.P2, .ended);
890
1052
  try expectProbability(&chance.probability, 1, d);
891
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
1053
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
892
1054
 
893
1055
  chance.reset();
894
1056
  }
895
1057
 
896
1058
  if (i < 3) {
897
- chance.actions.p2.durations.attacking = @intCast(i);
898
- try chance.attacking(.P2, 1);
1059
+ chance.durations.p2.attacking = @intCast(i);
1060
+ try chance.attacking(.P2, .continuing);
899
1061
  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);
1062
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
901
1063
 
902
1064
  chance.reset();
903
1065
  }
@@ -911,18 +1073,18 @@ test "Chance.binding" {
911
1073
  const qs = [_]u8{ 8, 5, 2, 1 };
912
1074
 
913
1075
  for (ps, qs, 1..5) |p, q, i| {
914
- chance.actions.p1.durations.binding = @intCast(i);
915
- try chance.binding(.P1, 0);
1076
+ chance.durations.p1.binding = @intCast(i);
1077
+ try chance.binding(.P1, .ended);
916
1078
  try expectProbability(&chance.probability, p, q);
917
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
1079
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
918
1080
 
919
1081
  chance.reset();
920
1082
 
921
1083
  if (i < 4) {
922
- chance.actions.p1.durations.binding = @intCast(i);
923
- try chance.binding(.P1, 1);
1084
+ chance.durations.p1.binding = @intCast(i);
1085
+ try chance.binding(.P1, .continuing);
924
1086
  try expectProbability(&chance.probability, q - p, q);
925
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1087
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
926
1088
 
927
1089
  chance.reset();
928
1090
  }
@@ -950,7 +1112,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
950
1112
 
951
1113
  r.reduce();
952
1114
  if (r.p != p or r.q != q) {
953
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
1115
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
954
1116
  return error.TestExpectedEqual;
955
1117
  }
956
1118
  }
@@ -972,24 +1134,24 @@ const Null = struct {
972
1134
  _ = .{ self, player, kind };
973
1135
  }
974
1136
 
975
- pub fn clearPending(self: Null) void {
1137
+ pub fn glitch(self: Null) void {
976
1138
  _ = .{self};
977
1139
  }
978
1140
 
979
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
980
- _ = .{ self, player, haze };
1141
+ pub fn clearPending(self: Null) void {
1142
+ _ = .{self};
981
1143
  }
982
1144
 
983
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
984
- _ = .{ self, player, in, out };
1145
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1146
+ _ = .{ self, player, slot };
985
1147
  }
986
1148
 
987
1149
  pub fn speedTie(self: Null, p1: bool) Error!void {
988
1150
  _ = .{ self, p1 };
989
1151
  }
990
1152
 
991
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
992
- _ = .{ self, ok, accuracy };
1153
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
1154
+ _ = .{ self, player, ok, accuracy };
993
1155
  }
994
1156
 
995
1157
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -1020,34 +1182,53 @@ const Null = struct {
1020
1182
  _ = .{ self, player, n };
1021
1183
  }
1022
1184
 
1023
- pub fn duration(
1185
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1186
+ _ = .{ self, player, turns };
1187
+ }
1188
+
1189
+ pub fn observe(
1024
1190
  self: Null,
1025
- comptime field: Duration.Field,
1191
+ comptime field: Action.Field,
1026
1192
  player: Player,
1027
- target: Player,
1028
- turns: u4,
1193
+ obs: Optional(Observation(.confusion)),
1029
1194
  ) void {
1030
- _ = .{ self, field, player, target, turns };
1195
+ _ = .{ self, field, player, obs };
1031
1196
  }
1032
1197
 
1033
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1034
- _ = .{ self, player, turns };
1198
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation(.sleep))) Error!void {
1199
+ _ = .{ self, player, obs };
1035
1200
  }
1036
1201
 
1037
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1038
- _ = .{ self, player, turns };
1202
+ pub fn confusion(
1203
+ self: Null,
1204
+ player: Player,
1205
+ obs: Optional(Observation(.confusion)),
1206
+ ) Error!void {
1207
+ _ = .{ self, player, obs };
1039
1208
  }
1040
1209
 
1041
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1042
- _ = .{ self, player, turns };
1210
+ pub fn disable(
1211
+ self: Null,
1212
+ player: Player,
1213
+ obs: Optional(Observation(.disable)),
1214
+ ) Error!void {
1215
+ _ = .{ self, player, obs };
1043
1216
  }
1044
1217
 
1045
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1046
- _ = .{ self, player, turns };
1218
+ pub fn attacking(
1219
+ self: Null,
1220
+ player: Player,
1221
+ obs: Optional(Observation(.attacking)),
1222
+ ) Error!void {
1223
+ _ = .{ self, player, obs };
1047
1224
  }
1048
1225
 
1049
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1050
- _ = .{ self, player, turns };
1226
+ pub fn binding(
1227
+ self: Null,
1228
+ player: Player,
1229
+ obs: Optional(Observation(.binding)),
1230
+ ) Error!void {
1231
+ _ = .{ self, player, obs };
1051
1232
  }
1052
1233
 
1053
1234
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1057,4 +1238,9 @@ const Null = struct {
1057
1238
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1058
1239
  _ = .{ self, player, move };
1059
1240
  }
1241
+
1242
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1243
+ _ = .{ self, player, n };
1244
+ return true;
1245
+ }
1060
1246
  };