@pkmn/engine 0.1.0-dev.a02611a4 → 0.1.0-dev.a15b6c17

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