@pkmn/engine 0.1.0-dev.9f75c7ae → 0.1.0-dev.a0167a9d

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 (42) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  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 +82 -90
  17. package/package.json +31 -23
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +4 -4
  20. package/src/lib/common/debug.zig +2 -2
  21. package/src/lib/common/optional.zig +1 -1
  22. package/src/lib/common/options.zig +2 -0
  23. package/src/lib/common/protocol.zig +393 -396
  24. package/src/lib/common/rational.zig +109 -15
  25. package/src/lib/common/rng.zig +11 -11
  26. package/src/lib/gen1/calc.zig +269 -97
  27. package/src/lib/gen1/chance.zig +126 -160
  28. package/src/lib/gen1/data/species.zig +1 -1
  29. package/src/lib/gen1/data.zig +21 -21
  30. package/src/lib/gen1/helpers.zig +191 -206
  31. package/src/lib/gen1/mechanics.zig +308 -285
  32. package/src/lib/gen2/chance.zig +211 -121
  33. package/src/lib/gen2/data.zig +13 -11
  34. package/src/lib/gen2/generated.zig +15 -24
  35. package/src/lib/gen2/helpers.zig +37 -7
  36. package/src/lib/gen2/mechanics.zig +855 -413
  37. package/src/lib/{binding/node.zig → node.zig} +9 -12
  38. package/src/lib/pkmn.zig +27 -12
  39. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  40. package/src/test/display/pkmn.html.tmpl +5 -5
  41. package/src/lib/gen1/README.md +0 -894
  42. package/src/lib/gen2/README.md +0 -330
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const builtin = @import("builtin");
2
3
 
3
4
  const common = @import("../common/data.zig");
4
5
  const optional = @import("../common/optional.zig");
@@ -34,6 +35,9 @@ const Status = data.Status;
34
35
 
35
36
  const Action = chance.Action;
36
37
  const Actions = chance.Actions;
38
+ const Duration = chance.Duration;
39
+
40
+ const endian = builtin.cpu.arch.endian();
37
41
 
38
42
  /// Configuration options controlling the domain of valid Pokémon that can be generated by the
39
43
  /// random generator helpers.
@@ -43,6 +47,8 @@ pub const Options = struct {
43
47
  /// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
44
48
  /// unimplementable in the engine.
45
49
  block: bool = showdown,
50
+ // FIXME: remove once durations work with -Dchance
51
+ durations: bool = false,
46
52
  };
47
53
 
48
54
  /// Helpers to simplify initialization of a Generation I battle.
@@ -86,18 +92,38 @@ pub const Battle = struct {
86
92
  };
87
93
 
88
94
  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
95
  return .{
92
96
  .src = .{
93
97
  .seed = if (showdown)
94
98
  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
- },
99
+ else seed: {
100
+ // GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
101
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
102
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
103
+
104
+ // Bytes in seed can only range from 0-252, not 0-255
105
+ const max: u8 = 253;
106
+ // Instructions take multiples of 4 cycles to execute
107
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
108
+ var add: u8 = rand.range(u8, 0, 256);
109
+ var carry: u8 = 0;
110
+
111
+ var i: u8 = 0;
112
+ while (i < seed.len) {
113
+ const div: u8 = @intCast(timer >> 8);
114
+ add +%= div +% carry;
115
+ if (add < max) {
116
+ seed[i] = add;
117
+ i += 1;
118
+ timer +%= 472;
119
+ carry = 1;
120
+ } else {
121
+ timer +%= 452;
122
+ carry = 0;
123
+ }
124
+ }
125
+ break :seed seed;
126
+ },
101
127
  },
102
128
  };
103
129
  }
@@ -210,6 +236,7 @@ pub const Pokemon = struct {
210
236
  var m: Move = .None;
211
237
  sample: while (true) {
212
238
  m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
239
+ if (opt.durations and durationsX(m)) continue :sample;
213
240
  if (opt.block and blocked(m)) continue :sample;
214
241
  for (0..i) |j| if (ms[j].id == m) continue :sample;
215
242
  break;
@@ -247,6 +274,23 @@ fn blocked(m: Move) bool {
247
274
  };
248
275
  }
249
276
 
277
+ fn durationsX(m: Move) bool {
278
+ // Any moves that have effects that result in duration tracking with -Dchance
279
+ return switch (Move.get(m).effect) {
280
+ .Confusion,
281
+ .Bide,
282
+ .Sleep,
283
+ .Rage,
284
+ .Binding,
285
+ .Thrashing,
286
+ .ConfusionChance,
287
+ .Disable,
288
+ .Metronome,
289
+ => true,
290
+ else => false,
291
+ };
292
+ }
293
+
250
294
  /// Convenience helper to create a move-type choice for the provided move slot.
251
295
  pub fn move(slot: u4) Choice {
252
296
  return .{ .type = .Move, .data = slot };
@@ -267,7 +311,7 @@ pub const Rolls = struct {
267
311
 
268
312
  /// Returns a slice with the correct range of values for speed ties given the `action` state.
269
313
  pub fn speedTie(action: Action) []const Optional(Player) {
270
- return if (@field(action, "speed_tie") == .None) &PLAYER_NONE else &PLAYERS;
314
+ return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
271
315
  }
272
316
 
273
317
  const BOOL_NONE = [_]Optional(bool){.None};
@@ -277,25 +321,19 @@ pub const Rolls = struct {
277
321
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
278
322
  pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
279
323
  if (parent == .true) return &BOOL_NONE;
280
- return if (@field(action, "hit") == .None) &BOOL_NONE else &BOOLS;
324
+ return if (action.hit == .None) &BOOL_NONE else &BOOLS;
281
325
  }
282
326
 
283
327
  /// Returns a slice with the correct range of values for critical hits given the `action` state
284
328
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
285
- pub fn criticalHit(
286
- action: Action,
287
- parent: Optional(bool),
288
- ) []const Optional(bool) {
329
+ pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
289
330
  if (parent == .false) return &BOOL_NONE;
290
- return if (@field(action, "critical_hit") == .None) &BOOL_NONE else &BOOLS;
331
+ return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
291
332
  }
292
333
 
293
334
  /// Returns a slice with the correct range of values for secondary chances hits given the
294
335
  /// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
295
- pub fn secondaryChance(
296
- action: Action,
297
- parent: Optional(bool),
298
- ) []const Optional(bool) {
336
+ pub fn secondaryChance(action: Action, parent: Optional(bool)) []const Optional(bool) {
299
337
  if (parent == .false) return &BOOL_NONE;
300
338
  return if (@field(action, "secondary_chance") == .None) &BOOL_NONE else &BOOLS;
301
339
  }
@@ -306,7 +344,7 @@ pub const Rolls = struct {
306
344
  /// Returns the range bounding damage rolls given the `action` state and the state of
307
345
  /// the `parent` (whether the player's Pokémon's move hit).
308
346
  pub fn damage(action: Action, parent: Optional(bool)) Range {
309
- return if (parent == .false or @field(action, "damage") == 0)
347
+ return if (parent == .false or action.damage == 0)
310
348
  .{ .min = 0, .max = 1 }
311
349
  else
312
350
  .{ .min = 217, .max = 256 };
@@ -327,14 +365,14 @@ pub const Rolls = struct {
327
365
  /// Returns a slice with the correct range of values for confused given the `action` state
328
366
  /// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
329
367
  pub fn confused(action: Action, parent: u4) []const Optional(bool) {
330
- return if (parent == 0 or @field(action, "confused") == .None) &BOOL_NONE else &BOOLS;
368
+ return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
331
369
  }
332
370
 
333
371
  /// Returns a slice with the correct range of values for paralysis given the `action` state
334
372
  /// and the state of the `parent` (whether the player's Pokémon was confused).
335
373
  pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
336
374
  if (parent == .true) return &BOOL_NONE;
337
- return if (@field(action, "paralyzed") == .None) &BOOL_NONE else &BOOLS;
375
+ return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
338
376
  }
339
377
 
340
378
  const DURATION_NONE = [_]u1{0};
@@ -344,40 +382,38 @@ pub const Rolls = struct {
344
382
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
345
383
  pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
346
384
  if (parent == .false) return &DURATION_NONE;
347
- return if (@field(action, "duration") == 0) &DURATION_NONE else &DURATION;
385
+ return if (action.duration == 0) &DURATION_NONE else &DURATION;
348
386
  }
349
387
 
350
388
  const U3_NONE = [_]u3{0};
351
389
  const U3_FORCED = [_]u3{1};
352
390
 
353
- /// Returns a slice with a range of values for sleep given the `action` and `origin` states.
354
- pub inline fn sleep(action: Action, origin: Action) []const u3 {
355
- const a = @field(action.durations, "sleep");
356
- const o = @field(origin.durations, "sleep");
357
- if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
358
- assert(a == 0 or a == o + 1);
391
+ /// TODO
392
+ pub inline fn sleep(dur: Duration, origin: Duration) []const u3 {
393
+ const d = dur.sleep;
394
+ const o = origin.sleep;
395
+ if (o == d) return if (o == 0) &U3_NONE else &[_]u3{o};
396
+ assert(d == 0 or d == o + 1);
359
397
  return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
360
398
  }
361
399
 
362
400
  const DISABLE_NONE = [_]u4{0};
363
401
 
364
- /// Returns a slice with a range of values for disable given the `action` and `origin` states
365
- /// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
366
- pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
402
+ /// TODO
403
+ pub inline fn disable(dur: Duration, origin: Duration, parent: u4) []const u4 {
367
404
  if (parent > 0) return &DISABLE_NONE;
368
- const a = @field(action.durations, "disable");
369
- const o = @field(origin.durations, "disable");
405
+ const a = dur.disable;
406
+ const o = origin.disable;
370
407
  if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
371
408
  assert(a == 0 or a == o + 1);
372
409
  return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
373
410
  }
374
411
 
375
- /// Returns a slice with a range of values for confusion given the `action` and `origin` states
376
- /// and the state of the `parent` (the player's remaining sleep turns).
377
- pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
412
+ /// TODO
413
+ pub inline fn confusion(dur: Duration, origin: Duration, parent: u4) []const u3 {
378
414
  if (parent > 0) return &U3_NONE;
379
- const a = @field(action.durations, "confusion");
380
- const o = @field(origin.durations, "confusion");
415
+ const a = dur.confusion;
416
+ const o = origin.confusion;
381
417
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
382
418
  assert(a == 0 or a == o + 1);
383
419
  return if (o >= 5) &U3_NONE else if (o < 2)
@@ -386,12 +422,11 @@ pub const Rolls = struct {
386
422
  &[_]u3{ 0, o + 1 };
387
423
  }
388
424
 
389
- /// Returns a slice with a range of values for attacking given the `action` and `origin` states
390
- /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
391
- pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
425
+ /// TODO
426
+ pub inline fn attacking(dur: Duration, origin: Duration, parent: Optional(bool)) []const u3 {
392
427
  if (parent == .true) return &U3_NONE;
393
- const a = @field(action.durations, "attacking");
394
- const o = @field(origin.durations, "attacking");
428
+ const a = dur.attacking;
429
+ const o = origin.attacking;
395
430
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
396
431
  assert(a == 0 or a == o + 1);
397
432
  return if (o >= 3) &U3_NONE else if (o < 2)
@@ -400,12 +435,11 @@ pub const Rolls = struct {
400
435
  &[_]u3{ 0, o + 1 };
401
436
  }
402
437
 
403
- /// Returns a slice with a range of values for binding given the `action` and `origin` states
404
- /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
405
- pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
438
+ /// TODO
439
+ pub inline fn binding(dur: Duration, origin: Duration, parent: Optional(bool)) []const u3 {
406
440
  if (parent == .true) return &U3_NONE;
407
- const a = @field(action.durations, "binding");
408
- const o = @field(origin.durations, "binding");
441
+ const a = dur.binding;
442
+ const o = origin.binding;
409
443
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
410
444
  assert(a == 0 or a == o + 1);
411
445
  return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -420,7 +454,7 @@ pub const Rolls = struct {
420
454
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
421
455
  pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
422
456
  if (parent == .false) return &SLOT_NONE;
423
- return if (@field(action, "move_slot") == 0) &SLOT_NONE else &SLOT;
457
+ return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
424
458
  }
425
459
 
426
460
  const MULTI_NONE = [_]u4{0};
@@ -430,7 +464,7 @@ pub const Rolls = struct {
430
464
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
431
465
  pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
432
466
  if (parent == .false) return &MULTI_NONE;
433
- return if (@field(action, "multi_hit") == 0) &MULTI_NONE else &MULTI;
467
+ return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
434
468
  }
435
469
 
436
470
  const PSYWAVE_NONE = [_]u8{0};
@@ -448,7 +482,7 @@ pub const Rolls = struct {
448
482
  parent: Optional(bool),
449
483
  ) []const u8 {
450
484
  if (parent == .false) return &PSYWAVE_NONE;
451
- return if (@field(action, "psywave") == 0)
485
+ return if (action.psywave == 0)
452
486
  &PSYWAVE_NONE
453
487
  else
454
488
  PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
@@ -458,7 +492,7 @@ pub const Rolls = struct {
458
492
 
459
493
  /// Returns a slice with the correct range of values for metronome given the `action` state.
460
494
  pub fn metronome(action: Action) []const Move {
461
- return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
495
+ return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
462
496
  }
463
497
  };
464
498
 
@@ -478,12 +512,12 @@ test "Rolls.damage" {
478
512
  test "Rolls.coalesce" {
479
513
  var summaries =
480
514
  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));
515
+ try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
516
+ try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
517
+ try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
484
518
  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));
519
+ try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
520
+ try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
487
521
  }
488
522
 
489
523
  test "Rolls.hit" {
@@ -539,206 +573,113 @@ test "Rolls.duration" {
539
573
  }
540
574
 
541
575
  test "Rolls.sleep" {
542
- try expectEqualSlices(u3, &.{0}, Rolls.sleep(
543
- .{ .durations = .{ .sleep = 0 } },
544
- .{ .durations = .{ .sleep = 0 } },
545
- ));
546
- try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.sleep(
547
- .{ .durations = .{ .sleep = 1 } },
548
- .{ .durations = .{ .sleep = 0 } },
549
- ));
550
- try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.sleep(
551
- .{ .durations = .{ .sleep = 0 } },
552
- .{ .durations = .{ .sleep = 1 } },
553
- ));
554
- try expectEqualSlices(u3, &.{1}, Rolls.sleep(
555
- .{ .durations = .{ .sleep = 1 } },
556
- .{ .durations = .{ .sleep = 1 } },
557
- ));
558
- try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.sleep(
559
- .{ .durations = .{ .sleep = 0 } },
560
- .{ .durations = .{ .sleep = 2 } },
561
- ));
562
- try expectEqualSlices(u3, &.{ 0, 7 }, Rolls.sleep(
563
- .{ .durations = .{ .sleep = 7 } },
564
- .{ .durations = .{ .sleep = 6 } },
565
- ));
566
- try expectEqualSlices(u3, &.{0}, Rolls.sleep(
567
- .{ .durations = .{ .sleep = 0 } },
568
- .{ .durations = .{ .sleep = 7 } },
569
- ));
576
+ try expectEqualSlices(u3, &.{0}, Rolls.sleep(.{ .sleep = 0 }, .{ .sleep = 0 }));
577
+ try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.sleep(.{ .sleep = 1 }, .{ .sleep = 0 }));
578
+ try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.sleep(.{ .sleep = 0 }, .{ .sleep = 1 }));
579
+ try expectEqualSlices(u3, &.{1}, Rolls.sleep(.{ .sleep = 1 }, .{ .sleep = 1 }));
580
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.sleep(.{ .sleep = 0 }, .{ .sleep = 2 }));
581
+ try expectEqualSlices(u3, &.{ 0, 7 }, Rolls.sleep(.{ .sleep = 7 }, .{ .sleep = 6 }));
582
+ try expectEqualSlices(u3, &.{0}, Rolls.sleep(.{ .sleep = 0 }, .{ .sleep = 7 }));
570
583
  }
571
584
 
572
585
  test "Rolls.disable" {
573
- try expectEqualSlices(u4, &.{0}, Rolls.disable(
574
- .{ .durations = .{ .disable = 0 } },
575
- .{ .durations = .{ .disable = 0 } },
576
- 0,
577
- ));
578
- try expectEqualSlices(u4, &.{ 0, 1 }, Rolls.disable(
579
- .{ .durations = .{ .disable = 1 } },
580
- .{ .durations = .{ .disable = 0 } },
581
- 0,
582
- ));
583
- try expectEqualSlices(u4, &.{ 0, 2 }, Rolls.disable(
584
- .{ .durations = .{ .disable = 0 } },
585
- .{ .durations = .{ .disable = 1 } },
586
- 0,
587
- ));
588
- try expectEqualSlices(u4, &.{1}, Rolls.disable(
589
- .{ .durations = .{ .disable = 1 } },
590
- .{ .durations = .{ .disable = 1 } },
591
- 0,
592
- ));
593
- try expectEqualSlices(u4, &.{ 0, 3 }, Rolls.disable(
594
- .{ .durations = .{ .disable = 0 } },
595
- .{ .durations = .{ .disable = 2 } },
596
- 0,
597
- ));
598
- try expectEqualSlices(u4, &.{0}, Rolls.disable(
599
- .{ .durations = .{ .disable = 0 } },
600
- .{ .durations = .{ .disable = 2 } },
601
- 1,
602
- ));
603
- try expectEqualSlices(u4, &.{ 0, 8 }, Rolls.disable(
604
- .{ .durations = .{ .disable = 8 } },
605
- .{ .durations = .{ .disable = 7 } },
606
- 0,
607
- ));
608
- try expectEqualSlices(u4, &.{0}, Rolls.disable(
609
- .{ .durations = .{ .disable = 0 } },
610
- .{ .durations = .{ .disable = 8 } },
611
- 0,
612
- ));
586
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .disable = 0 }, .{ .disable = 0 }, 0));
587
+ try expectEqualSlices(u4, &.{ 0, 1 }, Rolls.disable(.{ .disable = 1 }, .{ .disable = 0 }, 0));
588
+ try expectEqualSlices(u4, &.{ 0, 2 }, Rolls.disable(.{ .disable = 0 }, .{ .disable = 1 }, 0));
589
+ try expectEqualSlices(u4, &.{1}, Rolls.disable(.{ .disable = 1 }, .{ .disable = 1 }, 0));
590
+ try expectEqualSlices(u4, &.{ 0, 3 }, Rolls.disable(.{ .disable = 0 }, .{ .disable = 2 }, 0));
591
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .disable = 0 }, .{ .disable = 2 }, 1));
592
+ try expectEqualSlices(u4, &.{ 0, 8 }, Rolls.disable(.{ .disable = 8 }, .{ .disable = 7 }, 0));
593
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .disable = 0 }, .{ .disable = 8 }, 0));
613
594
  }
614
595
 
615
596
  test "Rolls.confusion" {
616
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(
617
- .{ .durations = .{ .confusion = 0 } },
618
- .{ .durations = .{ .confusion = 0 } },
619
- 0,
620
- ));
621
- try expectEqualSlices(u3, &.{1}, Rolls.confusion(
622
- .{ .durations = .{ .confusion = 1 } },
623
- .{ .durations = .{ .confusion = 0 } },
624
- 0,
625
- ));
626
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(
627
- .{ .durations = .{ .confusion = 0 } },
628
- .{ .durations = .{ .confusion = 1 } },
629
- 0,
630
- ));
631
- try expectEqualSlices(u3, &.{1}, Rolls.confusion(
632
- .{ .durations = .{ .confusion = 1 } },
633
- .{ .durations = .{ .confusion = 1 } },
634
- 0,
635
- ));
597
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{ .confusion = 0 }, .{ .confusion = 0 }, 0));
598
+ try expectEqualSlices(u3, &.{1}, Rolls.confusion(.{ .confusion = 1 }, .{ .confusion = 0 }, 0));
599
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{ .confusion = 0 }, .{ .confusion = 1 }, 0));
600
+ try expectEqualSlices(u3, &.{1}, Rolls.confusion(.{ .confusion = 1 }, .{ .confusion = 1 }, 0));
636
601
  try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(
637
- .{ .durations = .{ .confusion = 0 } },
638
- .{ .durations = .{ .confusion = 2 } },
602
+ .{ .confusion = 0 },
603
+ .{ .confusion = 2 },
639
604
  0,
640
605
  ));
641
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(
642
- .{ .durations = .{ .confusion = 0 } },
643
- .{ .durations = .{ .confusion = 2 } },
644
- 1,
645
- ));
606
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{ .confusion = 0 }, .{ .confusion = 2 }, 1));
646
607
  try expectEqualSlices(u3, &.{ 0, 5 }, Rolls.confusion(
647
- .{ .durations = .{ .confusion = 5 } },
648
- .{ .durations = .{ .confusion = 4 } },
649
- 0,
650
- ));
651
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(
652
- .{ .durations = .{ .confusion = 0 } },
653
- .{ .durations = .{ .confusion = 5 } },
608
+ .{ .confusion = 5 },
609
+ .{ .confusion = 4 },
654
610
  0,
655
611
  ));
612
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{ .confusion = 0 }, .{ .confusion = 5 }, 0));
656
613
  }
657
614
 
658
615
  test "Rolls.attacking" {
659
616
  try expectEqualSlices(u3, &.{0}, Rolls.attacking(
660
- .{ .durations = .{ .attacking = 0 } },
661
- .{ .durations = .{ .attacking = 0 } },
617
+ .{ .attacking = 0 },
618
+ .{ .attacking = 0 },
662
619
  .false,
663
620
  ));
664
621
  try expectEqualSlices(u3, &.{1}, Rolls.attacking(
665
- .{ .durations = .{ .attacking = 1 } },
666
- .{ .durations = .{ .attacking = 0 } },
622
+ .{ .attacking = 1 },
623
+ .{ .attacking = 0 },
667
624
  .false,
668
625
  ));
669
626
  try expectEqualSlices(u3, &.{0}, Rolls.attacking(
670
- .{ .durations = .{ .attacking = 0 } },
671
- .{ .durations = .{ .attacking = 1 } },
627
+ .{ .attacking = 0 },
628
+ .{ .attacking = 1 },
672
629
  .false,
673
630
  ));
674
631
  try expectEqualSlices(u3, &.{1}, Rolls.attacking(
675
- .{ .durations = .{ .attacking = 1 } },
676
- .{ .durations = .{ .attacking = 1 } },
632
+ .{ .attacking = 1 },
633
+ .{ .attacking = 1 },
677
634
  .false,
678
635
  ));
679
636
  try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
680
- .{ .durations = .{ .attacking = 0 } },
681
- .{ .durations = .{ .attacking = 2 } },
637
+ .{ .attacking = 0 },
638
+ .{ .attacking = 2 },
682
639
  .false,
683
640
  ));
684
641
  try expectEqualSlices(u3, &.{0}, Rolls.attacking(
685
- .{ .durations = .{ .attacking = 0 } },
686
- .{ .durations = .{ .attacking = 2 } },
642
+ .{ .attacking = 0 },
643
+ .{ .attacking = 2 },
687
644
  .true,
688
645
  ));
689
646
  try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
690
- .{ .durations = .{ .attacking = 3 } },
691
- .{ .durations = .{ .attacking = 2 } },
647
+ .{ .attacking = 3 },
648
+ .{ .attacking = 2 },
692
649
  .false,
693
650
  ));
694
651
  try expectEqualSlices(u3, &.{0}, Rolls.attacking(
695
- .{ .durations = .{ .attacking = 0 } },
696
- .{ .durations = .{ .attacking = 3 } },
652
+ .{ .attacking = 0 },
653
+ .{ .attacking = 3 },
697
654
  .false,
698
655
  ));
699
656
  }
700
657
 
701
658
  test "Rolls.binding" {
702
- try expectEqualSlices(u3, &.{0}, Rolls.binding(
703
- .{ .durations = .{ .binding = 0 } },
704
- .{ .durations = .{ .binding = 0 } },
705
- .false,
706
- ));
659
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(.{ .binding = 0 }, .{ .binding = 0 }, .false));
707
660
  try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.binding(
708
- .{ .durations = .{ .binding = 1 } },
709
- .{ .durations = .{ .binding = 0 } },
661
+ .{ .binding = 1 },
662
+ .{ .binding = 0 },
710
663
  .false,
711
664
  ));
712
665
  try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(
713
- .{ .durations = .{ .binding = 0 } },
714
- .{ .durations = .{ .binding = 1 } },
715
- .false,
716
- ));
717
- try expectEqualSlices(u3, &.{1}, Rolls.binding(
718
- .{ .durations = .{ .binding = 1 } },
719
- .{ .durations = .{ .binding = 1 } },
666
+ .{ .binding = 0 },
667
+ .{ .binding = 1 },
720
668
  .false,
721
669
  ));
670
+ try expectEqualSlices(u3, &.{1}, Rolls.binding(.{ .binding = 1 }, .{ .binding = 1 }, .false));
722
671
  try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.binding(
723
- .{ .durations = .{ .binding = 0 } },
724
- .{ .durations = .{ .binding = 2 } },
672
+ .{ .binding = 0 },
673
+ .{ .binding = 2 },
725
674
  .false,
726
675
  ));
727
- try expectEqualSlices(u3, &.{0}, Rolls.binding(
728
- .{ .durations = .{ .binding = 0 } },
729
- .{ .durations = .{ .binding = 2 } },
730
- .true,
731
- ));
676
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(.{ .binding = 0 }, .{ .binding = 2 }, .true));
732
677
  try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.binding(
733
- .{ .durations = .{ .binding = 4 } },
734
- .{ .durations = .{ .binding = 3 } },
735
- .false,
736
- ));
737
- try expectEqualSlices(u3, &.{0}, Rolls.binding(
738
- .{ .durations = .{ .binding = 0 } },
739
- .{ .durations = .{ .binding = 4 } },
678
+ .{ .binding = 4 },
679
+ .{ .binding = 3 },
740
680
  .false,
741
681
  ));
682
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(.{ .binding = 0 }, .{ .binding = 4 }, .false));
742
683
  }
743
684
 
744
685
  test "Rolls.moveSlot" {
@@ -771,9 +712,53 @@ test "Rolls.psywave" {
771
712
 
772
713
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
773
714
  var rolls = Rolls.psywave(actions.p2, &side, .None);
774
- try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
715
+ try expectEqual(150, rolls[rolls.len - 1]);
775
716
  side.stored().level = 81;
776
717
  rolls = Rolls.psywave(actions.p2, &side, .None);
777
- try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
718
+ try expectEqual(121, rolls[rolls.len - 1]);
778
719
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
779
720
  }
721
+
722
+ /// Empirically determined maximum number of bytes possibly required to store the diff between two
723
+ /// Generation I battles.
724
+ pub const MAX_DIFFS: usize = 117;
725
+
726
+ /// The optimal size in bytes required to hold the diff between two Generation I battles.
727
+ /// At least as large as MAX_DIFFS.
728
+ pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
729
+ MAX_DIFFS
730
+ else
731
+ std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
732
+
733
+ /// TODO
734
+ pub fn diff(
735
+ a: *const data.Battle(data.PRNG),
736
+ b: *const data.Battle(data.PRNG),
737
+ w: protocol.ByteStream.Writer,
738
+ ) !usize {
739
+ const pos = w.stream.pos;
740
+ const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
741
+ assert(size % 2 == 0);
742
+ assert(size / 2 < 255);
743
+
744
+ var i: usize = 0;
745
+ while (i < size) : (i += 2) {
746
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
747
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
748
+ try w.writeByte(@intCast(i / 2));
749
+ try w.writeInt(u16, val, endian);
750
+ }
751
+ }
752
+
753
+ return w.stream.pos - pos;
754
+ }
755
+
756
+ /// TODO
757
+ pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
758
+ var i: usize = 0;
759
+ while (i < buf.len) : (i += 3) {
760
+ const off = @as(u16, @intCast(buf[i])) * 2;
761
+ const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
762
+ std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
763
+ }
764
+ }