@pkmn/engine 0.1.0-dev.a93847b2 → 0.1.0-dev.abd68573

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 (44) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +39 -16
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +4 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +2 -1
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +5 -5
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +2 -2
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +5 -4
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +90 -76
  17. package/package.json +31 -23
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +8 -8
  20. package/src/lib/common/optional.zig +1 -1
  21. package/src/lib/common/protocol.zig +393 -396
  22. package/src/lib/common/rational.zig +11 -7
  23. package/src/lib/common/rng.zig +20 -11
  24. package/src/lib/gen1/calc.zig +7 -7
  25. package/src/lib/gen1/chance.zig +31 -30
  26. package/src/lib/gen1/data/moves.zig +9 -9
  27. package/src/lib/gen1/data/species.zig +2 -2
  28. package/src/lib/gen1/data/types.zig +4 -4
  29. package/src/lib/gen1/data.zig +32 -32
  30. package/src/lib/gen1/helpers.zig +48 -28
  31. package/src/lib/gen1/mechanics.zig +277 -255
  32. package/src/lib/gen2/calc.zig +1 -1
  33. package/src/lib/gen2/chance.zig +42 -41
  34. package/src/lib/gen2/data/items.zig +2 -2
  35. package/src/lib/gen2/data/moves.zig +2 -2
  36. package/src/lib/gen2/data/types.zig +9 -9
  37. package/src/lib/gen2/data.zig +23 -21
  38. package/src/lib/gen2/generated.zig +27 -78
  39. package/src/lib/gen2/mechanics.zig +1052 -459
  40. package/src/lib/{binding/node.zig → node.zig} +9 -12
  41. package/src/lib/pkmn.zig +27 -12
  42. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  43. package/src/lib/gen1/README.md +0 -894
  44. package/src/lib/gen2/README.md +0 -330
@@ -70,24 +70,24 @@ pub fn Battle(comptime RNG: anytype) type {
70
70
  rng: RNG,
71
71
 
72
72
  /// Returns the `Side` for the given `player`.
73
- pub inline fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
73
+ pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
74
74
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
75
75
  return &self.sides[@intFromEnum(player)];
76
76
  }
77
77
 
78
78
  /// Returns the `Side` of the opponent for the given `player`
79
- pub inline fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
79
+ pub fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
80
80
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
81
81
  return &self.sides[@intFromEnum(player.foe())];
82
82
  }
83
83
 
84
84
  /// Returns an identifier for the active Pokémon of `player`.
85
- pub inline fn active(self: *const Self, player: Player) ID {
85
+ pub fn active(self: *const Self, player: Player) ID {
86
86
  return player.ident(@intCast(self.side(player).order[0]));
87
87
  }
88
88
 
89
89
  /// Returns the `MoveDetails` for the given `player`.
90
- pub inline fn lastMove(
90
+ pub fn lastMove(
91
91
  self: anytype,
92
92
  player: Player,
93
93
  ) PointerType(@TypeOf(self), MoveDetails) {
@@ -112,7 +112,7 @@ pub fn Battle(comptime RNG: anytype) type {
112
112
  /// make (i.e. on the cartridge a soft-lock occurs).
113
113
  ///
114
114
  /// This function will always return choices > 0 if `pkmn.options.showdown` is true.
115
- pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
115
+ pub fn choices(self: *const Self, player: Player, request: Choice.Type, out: []Choice) u8 {
116
116
  return mechanics.choices(self, player, request, out);
117
117
  }
118
118
  };
@@ -141,7 +141,7 @@ pub const Side = extern struct {
141
141
  }
142
142
 
143
143
  /// Returns the stored `Pokemon` corresponding to the one-indexed party `slot`.
144
- pub inline fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
144
+ pub fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
145
145
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
146
146
  assert(slot > 0 and slot <= 6);
147
147
  const id = self.order[slot - 1];
@@ -150,7 +150,7 @@ pub const Side = extern struct {
150
150
  }
151
151
 
152
152
  /// Returns the stored `Pokemon` corresponding to the `active` Pokémon (slot 1).
153
- pub inline fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
153
+ pub fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
154
154
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
155
155
  return self.get(1);
156
156
  }
@@ -176,7 +176,7 @@ pub const ActivePokemon = extern struct {
176
176
  }
177
177
 
178
178
  /// Returns the active Pokémon's current move slot located at the one-indexed `mslot`.
179
- pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
179
+ pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
180
180
  assert(@typeInfo(@TypeOf(self)).Pointer.child == ActivePokemon);
181
181
  assert(mslot > 0 and mslot <= 4);
182
182
  assert(self.moves[mslot - 1].id != .None);
@@ -206,7 +206,7 @@ pub const Pokemon = extern struct {
206
206
  }
207
207
 
208
208
  /// Returns the Pokémon's original move slot located at the one-indexed `mslot`.
209
- pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
209
+ pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
210
210
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
211
211
  assert(mslot > 0 and mslot <= 4);
212
212
  assert(self.moves[mslot - 1].id != .None);
@@ -258,36 +258,36 @@ pub const Status = enum(u8) {
258
258
  const SLP = 0b111;
259
259
 
260
260
  /// Whether or not the status `num` is the same as `status`.
261
- pub inline fn is(num: u8, status: Status) bool {
261
+ pub fn is(num: u8, status: Status) bool {
262
262
  if (status == .SLP) return Status.duration(num) > 0;
263
263
  return ((num >> @intCast(@intFromEnum(status))) & 1) != 0;
264
264
  }
265
265
 
266
266
  /// Initializes a non-sleep `status`. Use `slp` or `slf` to initialize a sleep status.
267
- pub inline fn init(status: Status) u8 {
267
+ pub fn init(status: Status) u8 {
268
268
  assert(status != .SLP and status != .EXT);
269
269
  return @as(u8, 1) << @intCast(@intFromEnum(status));
270
270
  }
271
271
 
272
272
  /// Initializes a non self-inflicted sleep status with duration `dur`.
273
- pub inline fn slp(dur: u3) u8 {
273
+ pub fn slp(dur: u3) u8 {
274
274
  assert(dur > 0);
275
275
  return @as(u8, dur);
276
276
  }
277
277
 
278
278
  /// Initializes a self-inflicted sleep status with duration `dur`.
279
- pub inline fn slf(dur: u3) u8 {
279
+ pub fn slf(dur: u3) u8 {
280
280
  assert(dur > 0);
281
281
  return 0x80 | slp(dur);
282
282
  }
283
283
 
284
284
  /// Returns the duration of a sleep status.
285
- pub inline fn duration(num: u8) u3 {
285
+ pub fn duration(num: u8) u3 {
286
286
  return @intCast(num & SLP);
287
287
  }
288
288
 
289
289
  /// Returns whether `num` reflects any status.
290
- pub inline fn any(num: u8) bool {
290
+ pub fn any(num: u8) bool {
291
291
  return num > 0;
292
292
  }
293
293
 
@@ -316,12 +316,12 @@ test Status {
316
316
  try expect(Status.is(Status.slp(5), .SLP));
317
317
  try expect(!Status.is(Status.slp(5), .EXT));
318
318
  try expect(!Status.is(Status.slp(7), .PSN));
319
- try expectEqual(@as(u3, 5), Status.duration(Status.slp(5)));
319
+ try expectEqual(5, Status.duration(Status.slp(5)));
320
320
  try expect(Status.is(Status.slf(2), .SLP));
321
321
  try expect(Status.is(Status.slf(2), .EXT));
322
322
  try expect(Status.is(Status.TOX, .PSN));
323
323
  try expect(Status.is(Status.TOX, .EXT));
324
- try expectEqual(@as(u3, 1), Status.duration(Status.slp(1)));
324
+ try expectEqual(1, Status.duration(Status.slp(1)));
325
325
  }
326
326
 
327
327
  /// Bitfield representation of volatile statuses and associated data in Generation I.
@@ -405,13 +405,13 @@ test Volatiles {
405
405
  try expect(!volatiles.Transform);
406
406
  try expect(!volatiles.MultiHit);
407
407
 
408
- try expectEqual(@as(u16, 235), volatiles.state);
409
- try expectEqual(@as(u8, 42), volatiles.substitute);
410
- try expectEqual(@as(u4, 2), volatiles.disable_move);
411
- try expectEqual(@as(u4, 4), volatiles.disable_duration);
412
- try expectEqual(@as(u4, 2), volatiles.confusion);
413
- try expectEqual(@as(u5, 4), volatiles.toxic);
414
- try expectEqual(@as(u4, 3), volatiles.attacks);
408
+ try expectEqual(235, volatiles.state);
409
+ try expectEqual(42, volatiles.substitute);
410
+ try expectEqual(2, volatiles.disable_move);
411
+ try expectEqual(4, volatiles.disable_duration);
412
+ try expectEqual(2, volatiles.confusion);
413
+ try expectEqual(4, volatiles.toxic);
414
+ try expectEqual(3, volatiles.attacks);
415
415
  }
416
416
 
417
417
  /// Representaiton of a Pokémon's stats in Generation I.
@@ -442,9 +442,9 @@ test Stats {
442
442
  try expectEqual(0, stats.def);
443
443
 
444
444
  var base = Species.get(.Gyarados).stats;
445
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
445
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
446
446
  base = Species.get(.Pidgeot).stats;
447
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
447
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
448
448
  }
449
449
 
450
450
  /// Representation of a Pokémon's boosts in Generation I.
@@ -537,7 +537,7 @@ pub const Species = species.Species;
537
537
 
538
538
  test Species {
539
539
  try expectEqual(2, @intFromEnum(Species.Ivysaur));
540
- try expectEqual(@as(u8, 100), Species.get(.Mew).stats.def);
540
+ try expectEqual(100, Species.get(.Mew).stats.def);
541
541
  }
542
542
 
543
543
  /// Representation of a Generation I type in Pokémon.
@@ -576,8 +576,8 @@ test Types {
576
576
  try expectEqual(Effectiveness.Resisted, Type.effectiveness(.Fire, .Water));
577
577
  try expectEqual(Effectiveness.Neutral, Type.effectiveness(.Normal, .Grass));
578
578
 
579
- try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
580
- try expectEqual(@as(u8, 26), Type.precedence(.Bug, .Ghost));
579
+ try expectEqual(3, Type.precedence(.Water, .Water));
580
+ try expectEqual(26, Type.precedence(.Bug, .Ghost));
581
581
  try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
582
582
 
583
583
  const t: Types = .{ .type1 = .Rock, .type2 = .Ground };
@@ -617,18 +617,18 @@ pub const DVs = struct {
617
617
 
618
618
  test DVs {
619
619
  var dvs: DVs = .{ .spc = 15, .spe = 15 };
620
- try expectEqual(@as(u4, 15), dvs.hp());
620
+ try expectEqual(15, dvs.hp());
621
621
  dvs = .{
622
622
  .atk = 5,
623
623
  .def = 15,
624
624
  .spe = 13,
625
625
  .spc = 13,
626
626
  };
627
- try expectEqual(@as(u4, 15), dvs.hp());
627
+ try expectEqual(15, dvs.hp());
628
628
  dvs = .{
629
629
  .def = 3,
630
630
  .spe = 10,
631
631
  .spc = 11,
632
632
  };
633
- try expectEqual(@as(u4, 13), dvs.hp());
633
+ try expectEqual(13, dvs.hp());
634
634
  }
@@ -86,18 +86,38 @@ pub const Battle = struct {
86
86
  };
87
87
 
88
88
  fn prng(rand: *PSRNG) data.PRNG {
89
- // GLITCH: initial bytes in seed can only range from 0-252, not 0-255
90
- const max: u8 = 253;
91
89
  return .{
92
90
  .src = .{
93
91
  .seed = if (showdown)
94
92
  rand.newSeed()
95
- else
96
- .{
97
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
98
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
99
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
100
- },
93
+ else seed: {
94
+ // GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
95
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
96
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
97
+
98
+ // Bytes in seed can only range from 0-252, not 0-255
99
+ const max: u8 = 253;
100
+ // Instructions take multiples of 4 cycles to execute
101
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
102
+ var add: u8 = rand.range(u8, 0, 256);
103
+ var carry: u8 = 0;
104
+
105
+ var i: u8 = 0;
106
+ while (i < seed.len) {
107
+ const div: u8 = @intCast(timer >> 8);
108
+ add +%= div +% carry;
109
+ if (add < max) {
110
+ seed[i] = add;
111
+ i += 1;
112
+ timer +%= 472;
113
+ carry = 1;
114
+ } else {
115
+ timer +%= 452;
116
+ carry = 0;
117
+ }
118
+ }
119
+ break :seed seed;
120
+ },
101
121
  },
102
122
  };
103
123
  }
@@ -266,7 +286,7 @@ pub const Rolls = struct {
266
286
  const PLAYERS = [_]Optional(Player){ .P1, .P2 };
267
287
 
268
288
  /// Returns a slice with the correct range of values for speed ties given the `action` state.
269
- pub inline fn speedTie(action: Action) []const Optional(Player) {
289
+ pub fn speedTie(action: Action) []const Optional(Player) {
270
290
  return if (@field(action, "speed_tie") == .None) &PLAYER_NONE else &PLAYERS;
271
291
  }
272
292
 
@@ -275,14 +295,14 @@ pub const Rolls = struct {
275
295
 
276
296
  /// Returns a slice with the correct range of values for hits given the `action` state
277
297
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
278
- pub inline fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
298
+ pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
279
299
  if (parent == .true) return &BOOL_NONE;
280
300
  return if (@field(action, "hit") == .None) &BOOL_NONE else &BOOLS;
281
301
  }
282
302
 
283
303
  /// Returns a slice with the correct range of values for critical hits given the `action` state
284
304
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
285
- pub inline fn criticalHit(
305
+ pub fn criticalHit(
286
306
  action: Action,
287
307
  parent: Optional(bool),
288
308
  ) []const Optional(bool) {
@@ -292,7 +312,7 @@ pub const Rolls = struct {
292
312
 
293
313
  /// Returns a slice with the correct range of values for secondary chances hits given the
294
314
  /// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
295
- pub inline fn secondaryChance(
315
+ pub fn secondaryChance(
296
316
  action: Action,
297
317
  parent: Optional(bool),
298
318
  ) []const Optional(bool) {
@@ -305,7 +325,7 @@ pub const Rolls = struct {
305
325
 
306
326
  /// Returns the range bounding damage rolls given the `action` state and the state of
307
327
  /// the `parent` (whether the player's Pokémon's move hit).
308
- pub inline fn damage(action: Action, parent: Optional(bool)) Range {
328
+ pub fn damage(action: Action, parent: Optional(bool)) Range {
309
329
  return if (parent == .false or @field(action, "damage") == 0)
310
330
  .{ .min = 0, .max = 1 }
311
331
  else
@@ -314,7 +334,7 @@ pub const Rolls = struct {
314
334
 
315
335
  /// Returns the max damage roll which will produce the same damage as `roll`
316
336
  /// given the base damage in `summaries`.
317
- pub inline fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
337
+ pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
318
338
  if (roll == 0) return roll;
319
339
 
320
340
  const dmg = summaries.get(player.foe()).damage;
@@ -326,13 +346,13 @@ pub const Rolls = struct {
326
346
 
327
347
  /// Returns a slice with the correct range of values for confused given the `action` state
328
348
  /// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
329
- pub inline fn confused(action: Action, parent: u4) []const Optional(bool) {
349
+ pub fn confused(action: Action, parent: u4) []const Optional(bool) {
330
350
  return if (parent == 0 or @field(action, "confused") == .None) &BOOL_NONE else &BOOLS;
331
351
  }
332
352
 
333
353
  /// Returns a slice with the correct range of values for paralysis given the `action` state
334
354
  /// and the state of the `parent` (whether the player's Pokémon was confused).
335
- pub inline fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
355
+ pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
336
356
  if (parent == .true) return &BOOL_NONE;
337
357
  return if (@field(action, "paralyzed") == .None) &BOOL_NONE else &BOOLS;
338
358
  }
@@ -342,7 +362,7 @@ pub const Rolls = struct {
342
362
 
343
363
  /// Returns a slice with a range of values for duration given the `action` state
344
364
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
345
- pub inline fn duration(action: Action, parent: Optional(bool)) []const u1 {
365
+ pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
346
366
  if (parent == .false) return &DURATION_NONE;
347
367
  return if (@field(action, "duration") == 0) &DURATION_NONE else &DURATION;
348
368
  }
@@ -418,7 +438,7 @@ pub const Rolls = struct {
418
438
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
419
439
  ///
420
440
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
421
- pub inline fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
441
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
422
442
  if (parent == .false) return &SLOT_NONE;
423
443
  return if (@field(action, "move_slot") == 0) &SLOT_NONE else &SLOT;
424
444
  }
@@ -428,7 +448,7 @@ pub const Rolls = struct {
428
448
 
429
449
  /// Returns a slice with the correct range of values for multi hit given the `action` state
430
450
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
431
- pub inline fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
451
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
432
452
  if (parent == .false) return &MULTI_NONE;
433
453
  return if (@field(action, "multi_hit") == 0) &MULTI_NONE else &MULTI;
434
454
  }
@@ -442,7 +462,7 @@ pub const Rolls = struct {
442
462
 
443
463
  /// Returns a slice with the correct range of values for psywave given the `action` state,
444
464
  /// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
445
- pub inline fn psywave(
465
+ pub fn psywave(
446
466
  action: Action,
447
467
  side: *data.Side,
448
468
  parent: Optional(bool),
@@ -457,7 +477,7 @@ pub const Rolls = struct {
457
477
  const MOVE_NONE = [_]Move{.None};
458
478
 
459
479
  /// Returns a slice with the correct range of values for metronome given the `action` state.
460
- pub inline fn metronome(action: Action) []const Move {
480
+ pub fn metronome(action: Action) []const Move {
461
481
  return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
462
482
  }
463
483
  };
@@ -478,12 +498,12 @@ test "Rolls.damage" {
478
498
  test "Rolls.coalesce" {
479
499
  var summaries =
480
500
  calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
481
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
482
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
483
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
501
+ try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
502
+ try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
503
+ try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
484
504
  summaries.p1.damage.final = 74;
485
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
486
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
505
+ try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
506
+ try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
487
507
  }
488
508
 
489
509
  test "Rolls.hit" {
@@ -771,9 +791,9 @@ test "Rolls.psywave" {
771
791
 
772
792
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
773
793
  var rolls = Rolls.psywave(actions.p2, &side, .None);
774
- try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
794
+ try expectEqual(150, rolls[rolls.len - 1]);
775
795
  side.stored().level = 81;
776
796
  rolls = Rolls.psywave(actions.p2, &side, .None);
777
- try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
797
+ try expectEqual(121, rolls[rolls.len - 1]);
778
798
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
779
799
  }