@pkmn/engine 0.1.0-dev.dc33f678 → 0.1.0-dev.deda8593

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 (49) 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 +100 -102
  17. package/package.json +33 -22
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +8 -8
  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 +397 -384
  24. package/src/lib/common/rational.zig +11 -8
  25. package/src/lib/common/rng.zig +20 -11
  26. package/src/lib/gen1/calc.zig +217 -47
  27. package/src/lib/gen1/chance.zig +91 -92
  28. package/src/lib/gen1/data/moves.zig +10 -10
  29. package/src/lib/gen1/data/species.zig +3 -3
  30. package/src/lib/gen1/data/types.zig +5 -5
  31. package/src/lib/gen1/data.zig +35 -35
  32. package/src/lib/gen1/helpers.zig +136 -55
  33. package/src/lib/gen1/mechanics.zig +333 -312
  34. package/src/lib/gen2/calc.zig +143 -0
  35. package/src/lib/gen2/chance.zig +1315 -0
  36. package/src/lib/gen2/data/items.zig +3 -3
  37. package/src/lib/gen2/data/moves.zig +5 -5
  38. package/src/lib/gen2/data/species.zig +1 -1
  39. package/src/lib/gen2/data/types.zig +10 -10
  40. package/src/lib/gen2/data.zig +27 -37
  41. package/src/lib/gen2/generated.zig +806 -0
  42. package/src/lib/gen2/helpers.zig +37 -7
  43. package/src/lib/gen2/mechanics.zig +1621 -456
  44. package/src/lib/{binding/node.zig → node.zig} +9 -12
  45. package/src/lib/pkmn.zig +27 -12
  46. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  47. package/src/test/display/pkmn.html.tmpl +5 -5
  48. package/src/lib/gen1/README.md +0 -894
  49. package/src/lib/gen2/README.md +0 -330
@@ -22,11 +22,11 @@ const expect = std.testing.expect;
22
22
 
23
23
  /// The minimum size in bytes required to hold all Generation I choice options.
24
24
  pub const MAX_CHOICES: usize = 9; // move 1..4, switch 2..6
25
- /// The optimal size in bytes required to hold all Generation I choice options.
26
- /// At least as large as MAX_CHOICES.
25
+ /// The maximum number of bytes possibly logged by a single Generation I update.
27
26
  pub const MAX_LOGS: usize = 180;
28
27
 
29
- /// The maximum number of bytes possibly logged by a single Generation I update.
28
+ /// The optimal size in bytes required to hold all Generation I choice options.
29
+ /// At least as large as MAX_CHOICES.
30
30
  pub const CHOICES_SIZE = if (builtin.mode == .ReleaseSmall)
31
31
  MAX_CHOICES
32
32
  else
@@ -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
  }
@@ -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");
@@ -35,6 +36,8 @@ const Status = data.Status;
35
36
  const Action = chance.Action;
36
37
  const Actions = chance.Actions;
37
38
 
39
+ const endian = builtin.cpu.arch.endian();
40
+
38
41
  /// Configuration options controlling the domain of valid Pokémon that can be generated by the
39
42
  /// random generator helpers.
40
43
  pub const Options = struct {
@@ -43,6 +46,8 @@ pub const Options = struct {
43
46
  /// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
44
47
  /// unimplementable in the engine.
45
48
  block: bool = showdown,
49
+ // FIXME: remove once durations work with -Dchance
50
+ durations: bool = false,
46
51
  };
47
52
 
48
53
  /// Helpers to simplify initialization of a Generation I battle.
@@ -86,18 +91,38 @@ pub const Battle = struct {
86
91
  };
87
92
 
88
93
  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
94
  return .{
92
95
  .src = .{
93
96
  .seed = if (showdown)
94
97
  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
- },
98
+ else seed: {
99
+ // GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
100
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
101
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
102
+
103
+ // Bytes in seed can only range from 0-252, not 0-255
104
+ const max: u8 = 253;
105
+ // Instructions take multiples of 4 cycles to execute
106
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
107
+ var add: u8 = rand.range(u8, 0, 256);
108
+ var carry: u8 = 0;
109
+
110
+ var i: u8 = 0;
111
+ while (i < seed.len) {
112
+ const div: u8 = @intCast(timer >> 8);
113
+ add +%= div +% carry;
114
+ if (add < max) {
115
+ seed[i] = add;
116
+ i += 1;
117
+ timer +%= 472;
118
+ carry = 1;
119
+ } else {
120
+ timer +%= 452;
121
+ carry = 0;
122
+ }
123
+ }
124
+ break :seed seed;
125
+ },
101
126
  },
102
127
  };
103
128
  }
@@ -210,6 +235,7 @@ pub const Pokemon = struct {
210
235
  var m: Move = .None;
211
236
  sample: while (true) {
212
237
  m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
238
+ if (opt.durations and durations(m)) continue :sample;
213
239
  if (opt.block and blocked(m)) continue :sample;
214
240
  for (0..i) |j| if (ms[j].id == m) continue :sample;
215
241
  break;
@@ -247,6 +273,23 @@ fn blocked(m: Move) bool {
247
273
  };
248
274
  }
249
275
 
276
+ fn durations(m: Move) bool {
277
+ // Any moves that have effects that result in duration tracking with -Dchance
278
+ return switch (Move.get(m).effect) {
279
+ .Confusion,
280
+ .Bide,
281
+ .Sleep,
282
+ .Rage,
283
+ .Binding,
284
+ .Thrashing,
285
+ .ConfusionChance,
286
+ .Disable,
287
+ .Metronome,
288
+ => true,
289
+ else => false,
290
+ };
291
+ }
292
+
250
293
  /// Convenience helper to create a move-type choice for the provided move slot.
251
294
  pub fn move(slot: u4) Choice {
252
295
  return .{ .type = .Move, .data = slot };
@@ -266,8 +309,8 @@ pub const Rolls = struct {
266
309
  const PLAYERS = [_]Optional(Player){ .P1, .P2 };
267
310
 
268
311
  /// 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) {
270
- return if (@field(action, "speed_tie") == .None) &PLAYER_NONE else &PLAYERS;
312
+ pub fn speedTie(action: Action) []const Optional(Player) {
313
+ return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
271
314
  }
272
315
 
273
316
  const BOOL_NONE = [_]Optional(bool){.None};
@@ -275,27 +318,21 @@ pub const Rolls = struct {
275
318
 
276
319
  /// Returns a slice with the correct range of values for hits given the `action` state
277
320
  /// 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) {
321
+ pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
279
322
  if (parent == .true) return &BOOL_NONE;
280
- return if (@field(action, "hit") == .None) &BOOL_NONE else &BOOLS;
323
+ return if (action.hit == .None) &BOOL_NONE else &BOOLS;
281
324
  }
282
325
 
283
326
  /// Returns a slice with the correct range of values for critical hits given the `action` state
284
327
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
285
- pub inline fn criticalHit(
286
- action: Action,
287
- parent: Optional(bool),
288
- ) []const Optional(bool) {
328
+ pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
289
329
  if (parent == .false) return &BOOL_NONE;
290
- return if (@field(action, "critical_hit") == .None) &BOOL_NONE else &BOOLS;
330
+ return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
291
331
  }
292
332
 
293
333
  /// Returns a slice with the correct range of values for secondary chances hits given the
294
334
  /// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
295
- pub inline fn secondaryChance(
296
- action: Action,
297
- parent: Optional(bool),
298
- ) []const Optional(bool) {
335
+ pub fn secondaryChance(action: Action, parent: Optional(bool)) []const Optional(bool) {
299
336
  if (parent == .false) return &BOOL_NONE;
300
337
  return if (@field(action, "secondary_chance") == .None) &BOOL_NONE else &BOOLS;
301
338
  }
@@ -305,8 +342,8 @@ pub const Rolls = struct {
305
342
 
306
343
  /// Returns the range bounding damage rolls given the `action` state and the state of
307
344
  /// the `parent` (whether the player's Pokémon's move hit).
308
- pub inline fn damage(action: Action, parent: Optional(bool)) Range {
309
- return if (parent == .false or @field(action, "damage") == 0)
345
+ pub fn damage(action: Action, parent: Optional(bool)) Range {
346
+ return if (parent == .false or action.damage == 0)
310
347
  .{ .min = 0, .max = 1 }
311
348
  else
312
349
  .{ .min = 217, .max = 256 };
@@ -314,7 +351,7 @@ pub const Rolls = struct {
314
351
 
315
352
  /// Returns the max damage roll which will produce the same damage as `roll`
316
353
  /// given the base damage in `summaries`.
317
- pub inline fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
354
+ pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
318
355
  if (roll == 0) return roll;
319
356
 
320
357
  const dmg = summaries.get(player.foe()).damage;
@@ -326,15 +363,15 @@ pub const Rolls = struct {
326
363
 
327
364
  /// Returns a slice with the correct range of values for confused given the `action` state
328
365
  /// 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) {
330
- return if (parent == 0 or @field(action, "confused") == .None) &BOOL_NONE else &BOOLS;
366
+ pub fn confused(action: Action, parent: u4) []const Optional(bool) {
367
+ return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
331
368
  }
332
369
 
333
370
  /// Returns a slice with the correct range of values for paralysis given the `action` state
334
371
  /// 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) {
372
+ pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
336
373
  if (parent == .true) return &BOOL_NONE;
337
- return if (@field(action, "paralyzed") == .None) &BOOL_NONE else &BOOLS;
374
+ return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
338
375
  }
339
376
 
340
377
  const DURATION_NONE = [_]u1{0};
@@ -342,9 +379,9 @@ pub const Rolls = struct {
342
379
 
343
380
  /// Returns a slice with a range of values for duration given the `action` state
344
381
  /// 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 {
382
+ pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
346
383
  if (parent == .false) return &DURATION_NONE;
347
- return if (@field(action, "duration") == 0) &DURATION_NONE else &DURATION;
384
+ return if (action.duration == 0) &DURATION_NONE else &DURATION;
348
385
  }
349
386
 
350
387
  const U3_NONE = [_]u3{0};
@@ -352,8 +389,8 @@ pub const Rolls = struct {
352
389
 
353
390
  /// Returns a slice with a range of values for sleep given the `action` and `origin` states.
354
391
  pub inline fn sleep(action: Action, origin: Action) []const u3 {
355
- const a = @field(action.durations, "sleep");
356
- const o = @field(origin.durations, "sleep");
392
+ const a = action.durations.sleep;
393
+ const o = origin.durations.sleep;
357
394
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
358
395
  assert(a == 0 or a == o + 1);
359
396
  return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -365,8 +402,8 @@ pub const Rolls = struct {
365
402
  /// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
366
403
  pub inline fn disable(action: Action, origin: Action, 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 = action.durations.disable;
406
+ const o = origin.durations.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 };
@@ -376,8 +413,8 @@ pub const Rolls = struct {
376
413
  /// and the state of the `parent` (the player's remaining sleep turns).
377
414
  pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
378
415
  if (parent > 0) return &U3_NONE;
379
- const a = @field(action.durations, "confusion");
380
- const o = @field(origin.durations, "confusion");
416
+ const a = action.durations.confusion;
417
+ const o = origin.durations.confusion;
381
418
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
382
419
  assert(a == 0 or a == o + 1);
383
420
  return if (o >= 5) &U3_NONE else if (o < 2)
@@ -390,8 +427,8 @@ pub const Rolls = struct {
390
427
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
391
428
  pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
392
429
  if (parent == .true) return &U3_NONE;
393
- const a = @field(action.durations, "attacking");
394
- const o = @field(origin.durations, "attacking");
430
+ const a = action.durations.attacking;
431
+ const o = origin.durations.attacking;
395
432
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
396
433
  assert(a == 0 or a == o + 1);
397
434
  return if (o >= 3) &U3_NONE else if (o < 2)
@@ -404,8 +441,8 @@ pub const Rolls = struct {
404
441
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
405
442
  pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
406
443
  if (parent == .true) return &U3_NONE;
407
- const a = @field(action.durations, "binding");
408
- const o = @field(origin.durations, "binding");
444
+ const a = action.durations.binding;
445
+ const o = origin.durations.binding;
409
446
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
410
447
  assert(a == 0 or a == o + 1);
411
448
  return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -418,9 +455,9 @@ pub const Rolls = struct {
418
455
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
419
456
  ///
420
457
  /// 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 {
458
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
422
459
  if (parent == .false) return &SLOT_NONE;
423
- return if (@field(action, "move_slot") == 0) &SLOT_NONE else &SLOT;
460
+ return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
424
461
  }
425
462
 
426
463
  const MULTI_NONE = [_]u4{0};
@@ -428,9 +465,9 @@ pub const Rolls = struct {
428
465
 
429
466
  /// Returns a slice with the correct range of values for multi hit given the `action` state
430
467
  /// 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 {
468
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
432
469
  if (parent == .false) return &MULTI_NONE;
433
- return if (@field(action, "multi_hit") == 0) &MULTI_NONE else &MULTI;
470
+ return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
434
471
  }
435
472
 
436
473
  const PSYWAVE_NONE = [_]u8{0};
@@ -442,13 +479,13 @@ pub const Rolls = struct {
442
479
 
443
480
  /// Returns a slice with the correct range of values for psywave given the `action` state,
444
481
  /// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
445
- pub inline fn psywave(
482
+ pub fn psywave(
446
483
  action: Action,
447
484
  side: *data.Side,
448
485
  parent: Optional(bool),
449
486
  ) []const u8 {
450
487
  if (parent == .false) return &PSYWAVE_NONE;
451
- return if (@field(action, "psywave") == 0)
488
+ return if (action.psywave == 0)
452
489
  &PSYWAVE_NONE
453
490
  else
454
491
  PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
@@ -457,8 +494,8 @@ pub const Rolls = struct {
457
494
  const MOVE_NONE = [_]Move{.None};
458
495
 
459
496
  /// Returns a slice with the correct range of values for metronome given the `action` state.
460
- pub inline fn metronome(action: Action) []const Move {
461
- return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
497
+ pub fn metronome(action: Action) []const Move {
498
+ return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
462
499
  }
463
500
  };
464
501
 
@@ -478,12 +515,12 @@ test "Rolls.damage" {
478
515
  test "Rolls.coalesce" {
479
516
  var summaries =
480
517
  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));
518
+ try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
519
+ try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
520
+ try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
484
521
  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));
522
+ try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
523
+ try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
487
524
  }
488
525
 
489
526
  test "Rolls.hit" {
@@ -771,9 +808,53 @@ test "Rolls.psywave" {
771
808
 
772
809
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
773
810
  var rolls = Rolls.psywave(actions.p2, &side, .None);
774
- try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
811
+ try expectEqual(150, rolls[rolls.len - 1]);
775
812
  side.stored().level = 81;
776
813
  rolls = Rolls.psywave(actions.p2, &side, .None);
777
- try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
814
+ try expectEqual(121, rolls[rolls.len - 1]);
778
815
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
779
816
  }
817
+
818
+ /// Empirically determined maximum number of bytes possibly required to store the diff between two
819
+ /// Generation I battles.
820
+ pub const MAX_DIFFS: usize = 117;
821
+
822
+ /// The optimal size in bytes required to hold the diff between two Generation I battles.
823
+ /// At least as large as MAX_DIFFS.
824
+ pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
825
+ MAX_DIFFS
826
+ else
827
+ std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
828
+
829
+ /// TODO
830
+ pub fn diff(
831
+ a: *const data.Battle(data.PRNG),
832
+ b: *const data.Battle(data.PRNG),
833
+ w: protocol.ByteStream.Writer,
834
+ ) !usize {
835
+ const pos = w.stream.pos;
836
+ const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
837
+ assert(size % 2 == 0);
838
+ assert(size / 2 < 255);
839
+
840
+ var i: usize = 0;
841
+ while (i < size) : (i += 2) {
842
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
843
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
844
+ try w.writeByte(@intCast(i / 2));
845
+ try w.writeInt(u16, val, endian);
846
+ }
847
+ }
848
+
849
+ return w.stream.pos - pos;
850
+ }
851
+
852
+ /// TODO
853
+ pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
854
+ var i: usize = 0;
855
+ while (i < buf.len) : (i += 3) {
856
+ const off = @as(u16, @intCast(buf[i])) * 2;
857
+ const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
858
+ std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
859
+ }
860
+ }