@pkmn/engine 0.1.0-dev.00879b8b → 0.1.0-dev.00f4e740

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