@pkmn/engine 0.1.0-dev.21a51fe4 → 0.1.0-dev.2ad317f2

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.
package/README.md CHANGED
@@ -12,6 +12,14 @@
12
12
  <img alt="License" src="https://img.shields.io/badge/License-MIT-blue.svg" />
13
13
  </a>
14
14
  </p>
15
+
16
+ > [!WARNING]
17
+ > **This project is under heavy development** and currently the `main` branch contains numerous breaking
18
+ > changes which may not work and which are not fully documented. Please **wait for the forthcoming
19
+ > initial v0.1 release** before depending on this project (code from the
20
+ > [`old`](https://github.com/pkmn/engine/releases/tag/old) tag can also be used to experiment with a
21
+ > more stable version of the codebase).
22
+
15
23
  <hr />
16
24
 
17
25
  A minimal, complete, Pokémon battle simulation engine optimized for performance and
@@ -50,7 +58,7 @@ $ zig build --prefix /usr/local -Doptimize=ReleaseFast
50
58
 
51
59
  The Zig website has [installation instructions](https://ziglang.org/learn/getting-started/) which
52
60
  walk through how to install Zig on each platform - the engine code should work on Zig v0.11.0 dev
53
- build 3859 or greater, though tracks Zig's master branch so this may change in the future if
61
+ build 4282 or greater, though tracks Zig's master branch so this may change in the future if
54
62
  breaking language changes are introduced:
55
63
 
56
64
  `libpkmn` can be built with `-Dshowdown` to instead produce the Pokémon Showdown compatible
package/build.zig CHANGED
@@ -35,6 +35,7 @@ pub fn build(b: *std.Build) !void {
35
35
  const strip = b.option(bool, "strip", "Strip debugging symbols from binary") orelse false;
36
36
  const pic = b.option(bool, "pic", "Force position independent code") orelse false;
37
37
  const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
38
+ const emit_ll = b.option(bool, "emit-ll", "Output .ll (LLVM IR)") orelse false;
38
39
 
39
40
  const cmd = b.findProgram(&.{"strip"}, &.{}) catch null;
40
41
 
@@ -145,7 +146,8 @@ pub fn build(b: *std.Build) !void {
145
146
  lib.bundle_compiler_rt = true;
146
147
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
147
148
  if (pic) lib.force_pic = pic;
148
- if (emit_asm) lib.emit_asm = .emit;
149
+ if (emit_asm) lib.emit_asm = .{ .emit_to = b.fmt("{s}.S", .{name}) };
150
+ if (emit_ll) lib.emit_llvm_ir = .{ .emit_to = b.fmt("{s}.ll", .{name}) };
149
151
  b.installArtifact(lib);
150
152
  c = true;
151
153
  }
@@ -217,19 +219,15 @@ pub fn build(b: *std.Build) !void {
217
219
  fuzz_config.tool.name = "fuzz";
218
220
  const fuzz = tool(b, "src/test/fuzz.zig", fuzz_config);
219
221
 
222
+ const analyze = tool(b, "src/tools/analyze.zig", tools);
220
223
  const dump = tool(b, "src/tools/dump.zig", tools);
221
224
  const serde = tool(b, "src/tools/serde.zig", tools);
222
225
  const transitions = tool(b, "src/tools/transitions.zig", tools);
223
226
 
224
- const lint_exe =
225
- b.addExecutable(.{ .name = "lint", .root_source_file = .{ .path = "src/tools/lint.zig" } });
226
- if (tests.build) tests.step.dependOn(&lint_exe.step);
227
- const lint = b.addRunArtifact(lint_exe);
228
-
227
+ b.step("analyze", "Run LLVM analysis tool").dependOn(&analyze.step);
229
228
  b.step("benchmark", "Run benchmark code").dependOn(&benchmark.step);
230
229
  b.step("dump", "Run protocol dump tool").dependOn(&dump.step);
231
230
  b.step("fuzz", "Run fuzz tester").dependOn(&fuzz.step);
232
- b.step("lint", "Lint source files").dependOn(&lint.step);
233
231
  b.step("serde", "Run serialization/deserialization tool").dependOn(&serde.step);
234
232
  b.step("test", "Run all tests").dependOn(&tests.step);
235
233
  b.step("tools", "Install tools").dependOn(&ToolsStep.create(b, &exes).step);
@@ -263,12 +261,6 @@ const Config = struct {
263
261
  cmd: ?[]const u8,
264
262
  };
265
263
 
266
- const TESTS = .{
267
- "src/lib/common/test.zig",
268
- "src/lib/gen1/test.zig",
269
- "src/lib/gen2/test.zig",
270
- };
271
-
272
264
  const TestStep = struct {
273
265
  step: std.Build.Step,
274
266
  build: bool,
@@ -283,26 +275,27 @@ const TestStep = struct {
283
275
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
284
276
  self.* = TestStep{ .step = step, .build = test_filter == null };
285
277
 
286
- const paths: []const []const u8 =
287
- if (test_file) |t| &.{t} else if (coverage != null) &.{"src/lib/test.zig"} else &TESTS;
288
- for (paths) |path| {
289
- const tests = b.addTest(.{
290
- .name = std.fs.path.basename(std.fs.path.dirname(path).?),
291
- .root_source_file = .{ .path = path },
292
- .optimize = config.optimize,
293
- .target = config.target,
294
- .filter = test_filter,
295
- });
296
- tests.setMainPkgPath("./");
297
- tests.addOptions("build_options", options);
298
- tests.single_threaded = true;
299
- maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
300
- if (config.pic) tests.force_pic = config.pic;
301
- if (coverage) |c| {
302
- tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
303
- }
304
- self.step.dependOn(&b.addRunArtifact(tests).step);
278
+ const path = test_file orelse "src/lib/test.zig";
279
+ const tests = b.addTest(.{
280
+ .name = b.fmt("{s}-{s}", .{
281
+ std.fs.path.basename(std.fs.path.dirname(path).?),
282
+ std.fs.path.stem(std.fs.path.basename(path)),
283
+ }),
284
+ .root_source_file = .{ .path = path },
285
+ .optimize = config.optimize,
286
+ .target = config.target,
287
+ .filter = test_filter,
288
+ });
289
+ tests.setMainPkgPath("./");
290
+ tests.addOptions("build_options", options);
291
+ tests.single_threaded = true;
292
+ maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
293
+ if (config.pic) tests.force_pic = config.pic;
294
+ if (coverage) |c| {
295
+ tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
305
296
  }
297
+ self.step.dependOn(&b.addRunArtifact(tests).step);
298
+
306
299
  return self;
307
300
  }
308
301
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.21a51fe4",
3
+ "version": "0.1.0-dev.2ad317f2",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -10,7 +10,7 @@ const path = require('path');
10
10
  const {pipeline} = require('stream/promises');
11
11
  const tty = require('tty');
12
12
 
13
- const ZIG_VERSION = {major: 0, minor: 11, patch: 0, dev: 3859};
13
+ const ZIG_VERSION = {major: 0, minor: 11, patch: 0, dev: 4282};
14
14
  const DEV = ZIG_VERSION.dev ? `-dev.${ZIG_VERSION.dev}` : '';
15
15
  const VERSION = `${ZIG_VERSION.major}.${ZIG_VERSION.minor}.${ZIG_VERSION.patch}${DEV}`;
16
16
  const INDEX = 'https://ziglang.org/download/index.json';
@@ -6,7 +6,9 @@ document](../../../docs/DESIGN.md). The Generation I engine is implemented and t
6
6
  following files:
7
7
 
8
8
  - [`data.zig`](data.zig) ([`data`](data)): contains definitions of all of the data structures used
9
- by the engine, described in detail [below](#data-structures)
9
+ to implement the core mechanics of the engine, described in detail [below](#data-structures)
10
+ - [`chance.zig`](chance.zig): TODO
11
+ - [`calc.zig`](calc.zig): TODO
10
12
  - [`helpers.zig`](helpers.zig): helpers used to construct complex data types with
11
13
  sensible defaults (internally used by tests and tools)
12
14
  - [`mechanics.zig`](mechanics.zig): code which manipulates the data structures to implement the
@@ -245,9 +247,17 @@ engine maintains a `Type.PRECEDENCE` table with just the match-ups that are rele
245
247
  pruned for efficiency) and only looks up precedence when necessary to minimize expensive searches.
246
248
  Pokémon Showdown doesn't implement type precedence.
247
249
 
250
+ ### `Chance` / `Actions` / `Action` / `Durations` / `Duration`
251
+
252
+ TODO
253
+
254
+ ### `Calc` / `Overrides` / `Summaries` / `Summary`
255
+
256
+ TODO
257
+
248
258
  ## Information
249
259
 
250
- The information of each field (in terms of [bits of
260
+ The information of each field required for mechanics (in terms of [bits of
251
261
  entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as follows:
252
262
 
253
263
  | Data | Range | Bits | | Data | Range | Bits |
@@ -383,7 +393,7 @@ data in the browser for ease of debugging.
383
393
  - `order` is a 6 byte array where `order[i]` represents the "slot" of `pokemon[i]`, where the slot
384
394
  is usually a unique value from 1 to 6 but can be 0 in situations where a player brings less than six
385
395
  Pokémon to battle (note however that if `order[i]` is 0 than for all j > 0 `order[i+j]` **must**
386
- equal 0).
396
+ equal 0)
387
397
 
388
398
  ### `ActivePokemon`
389
399
 
@@ -469,6 +479,76 @@ data in the browser for ease of debugging.
469
479
  | 22 | 23 | `type1`/`type2` | The Pokémon's stored types |
470
480
  | 23 | 24 | `level` | The Pokémon's level |
471
481
 
482
+
483
+ ### `Action`
484
+
485
+ `Actions` consist of an `Action` for Player 1 followed by an `Action` for Player 2.
486
+
487
+ > **NOTE:** The offsets in the following table represent *bits* and **not** bytes.
488
+
489
+ | Start | End | Data | Description |
490
+ | ----- | --- | ------------------------ | ------------------------------------------------------------------------------- |
491
+ | 0 | 8 | `damage` | The roll to be returned damage (217-255). |
492
+ | 8 | 10 | `hit` | The roll to be returned for accuracy (1 for miss, 2 for hit). |
493
+ | 10 | 12 | `critical_hit` | The roll to be returned for critical hits (1 for no, 2 for yes). |
494
+ | 12 | 14 | `secondary_chance` | The roll to be returned for secondary chance rolls (1 for no-proc, 2 for proc). |
495
+ | 14 | 16 | `speed_tie` | The player to return for speed ties (1 for Player 1, 2 for Player 2). |
496
+ | 16 | 18 | `confused` | The roll to be returned for confusion self-hits (1 for no-proc, 2 for proc). |
497
+ | 18 | 20 | `paralyzed` | The roll to be returned for full paralysis (1 for no-proc, 2 for proc). |
498
+ | 20 | 24 | `duration` | The roll to be returned for the duration (including for binding moves). |
499
+ | 24 | 40 | [`durations`](#duration) | Values of various durations. |
500
+ | 40 | 44 | `move_slot` | The roll to be returned for the move slot (1-4, invalid values ignored). |
501
+ | 44 | 48 | `move_slot` | The roll to be returned for a multi-hit move distribution (2-5). |
502
+ | 48 | 56 | `psywave` | One greater than the roll to be returned for Psywave damage. |
503
+ | 56 | 64 | `metronome` | The move to return for Metronome. |
504
+
505
+ - for any field a value of 0 is considered to be unset
506
+ - **both Player 1 and Player 2 must set the same value for `speed_tie`.**
507
+ - `duration` determines the roll for any effect which lasts over multiple turns, whereas `durations` can either be used to
508
+ track how long these effects have lasted (for the purposes of computing probablities with `-Dchance`) or can be used to
509
+ [override](#overrides) (extend or end) the duration of specfic effects with `-Dcalc`.
510
+
511
+ #### `Duration`
512
+
513
+ > **NOTE:** The offsets in the following table represent *bits* and **not** bytes.
514
+
515
+ | Start | End | Data |
516
+ | ----- | --- | ----------- |
517
+ | 0 | 3 | `sleep` |
518
+ | 3 | 6 | `confusion` |
519
+ | 4 | 10 | `disable` |
520
+ | 10 | 13 | `attacking` |
521
+ | 13 | 16 | `binding` |
522
+
523
+ - the meaning of each field depends on the context in which `Duration` appears:
524
+ - within `Chance` `actions`, the durations tracked refer to the number of turns a Pokémon has been
525
+ observed to be under an effect
526
+ - within `Overrides` `actions`, the durations refer to the value the next `Chance` `actions`
527
+ should be made to take on
528
+ - within `Overrides` `durations`, the durations serve as a mask to determine to whether or not the
529
+ durations in `Overrides` `actions` should be considered to be set or not
530
+ ◊
531
+ ### `Overrides`
532
+
533
+ | Start | End | Data | Description |
534
+ | ----- | --- | --------------------------- | ------------------------------------------------------------------------------------- |
535
+ | 0 | 8 | [`action.p1`](#action) | Rolls to force Player 1's RNG to return for specific events. |
536
+ | 8 | 16 | [`action.p2`](#action) | Rolls to force Player 2's RNG to return for specific events. |
537
+ | 16 | 18 | [`durations.p1`](#duration) | Whether or not to modify Player 1's effect durations to match `actions.p1.durations`. |
538
+ | 18 | 20 | [`durations.p2`](#duration) | Whether or not to modify Player 2's effect durations to match `actions.p1.durations`. |
539
+ | 20 | 24 | - | *Zero padding* |
540
+
541
+ ### `Summaries`
542
+
543
+ | Start | End | Data | Description |
544
+ | ----- | --- | ------------------ | ------------------------------------------------------------------------------- |
545
+ | 0 | 2 | `p1.damage.base` | Player 1's base computed damage before the damage roll is applied. |
546
+ | 2 | 4 | `p1.damage.final` | Player 1's final computed damage that gets applied to the Pokémon. |
547
+ | 4 | 6 | `p1.damage.capped` | Whether higher damage from Player 1 will saturate / result in the same outcome. |
548
+ | 6 | 8 | `p2.damage.base` | Player 2's base computed damage before the damage roll is applied. |
549
+ | 8 | 10 | `p2.damage.final` | Player 2's final computed damage that gets applied to the Pokémon. |
550
+ | 10 | 12 | `p2.damage.capped` | Whether higher damage from Player 2 will saturate / result in the same outcome. |
551
+
472
552
  ## Bugs
473
553
 
474
554
  In addition to its alternative [RNG semantics](#rng), Pokémon Showdown's implementation of the first
@@ -126,7 +126,7 @@ pub const Action = packed struct {
126
126
  /// If not None, the value to be returned for
127
127
  /// Rolls.{confusionChance,secondaryChance,poisonChance}.
128
128
  secondary_chance: Optional(bool) = .None,
129
- /// If not None, the Playr to be returned by Rolls.speedTie.
129
+ /// If not None, the Player to be returned by Rolls.speedTie.
130
130
  speed_tie: Optional(Player) = .None,
131
131
 
132
132
  /// If not None, the value to return for Rolls.confused.
@@ -181,9 +181,9 @@ pub const Action = packed struct {
181
181
  if (v != 0) {
182
182
  if (printed) try writer.writeAll(", ");
183
183
  if (shape) {
184
- try writer.print("{s}:?", .{field.name});
184
+ try writer.print("{s}:?", .{f.name});
185
185
  } else {
186
- try writer.print("{s}:{d}", .{ field.name, v });
186
+ try writer.print("{s}:{d}", .{ f.name, v });
187
187
  }
188
188
  printed = true;
189
189
  }
@@ -350,47 +350,65 @@ pub const Rolls = struct {
350
350
  const U3_NONE = [_]u3{0};
351
351
  const U3_FORCED = [_]u3{1};
352
352
 
353
- /// Returns a slice with a range of values for sleep given the `action` state.
353
+ /// Returns a slice with a range of values for sleep given the `action` and `origin` states.
354
354
  pub inline fn sleep(action: Action, origin: Action) []const u3 {
355
- const turns = @field(action.durations, "sleep");
356
- _ = origin;
357
- return if (turns < 1 or turns >= 7) &U3_NONE else &[_]u3{ 0, turns };
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);
359
+ return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
358
360
  }
359
361
 
360
362
  const DISABLE_NONE = [_]u4{0};
361
363
 
362
- /// Returns a slice with a range of values for disable given the `action` state
364
+ /// Returns a slice with a range of values for disable given the `action` and `origin` states
363
365
  /// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
364
366
  pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
365
- const turns = @field(action.durations, "disable");
366
- _ = origin;
367
- return if (parent > 0 or turns < 1 or turns >= 8) &DISABLE_NONE else &[_]u4{ 0, turns };
367
+ if (parent > 0) return &DISABLE_NONE;
368
+ const a = @field(action.durations, "disable");
369
+ const o = @field(origin.durations, "disable");
370
+ if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
371
+ assert(a == 0 or a == o + 1);
372
+ return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
368
373
  }
369
374
 
370
- /// Returns a slice with a range of values for confusion given the `action` state
375
+ /// Returns a slice with a range of values for confusion given the `action` and `origin` states
371
376
  /// and the state of the `parent` (the player's remaining sleep turns).
372
377
  pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
373
- const turns = @field(action.durations, "confusion");
374
- _ = origin;
375
- if (parent > 0 or turns < 1 or turns >= 5) return &U3_NONE;
376
- return if (turns < 2) &U3_FORCED else &[_]u3{ 0, turns };
378
+ if (parent > 0) return &U3_NONE;
379
+ const a = @field(action.durations, "confusion");
380
+ const o = @field(origin.durations, "confusion");
381
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
382
+ assert(a == 0 or a == o + 1);
383
+ return if (o >= 5) &U3_NONE else if (o < 2)
384
+ if (a == 0) &U3_NONE else &U3_FORCED
385
+ else
386
+ &[_]u3{ 0, o + 1 };
377
387
  }
378
388
 
379
- /// Returns a slice with a range of values for attacking given the `action` state
389
+ /// Returns a slice with a range of values for attacking given the `action` and `origin` states
380
390
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
381
391
  pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
382
- const turns = @field(action.durations, "attacking");
383
- _ = origin;
384
- if (parent == .true or turns < 1 or turns >= 3) return &U3_NONE;
385
- return if (turns < 2) &U3_FORCED else &[_]u3{ 0, turns };
392
+ if (parent == .true) return &U3_NONE;
393
+ const a = @field(action.durations, "attacking");
394
+ const o = @field(origin.durations, "attacking");
395
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
396
+ assert(a == 0 or a == o + 1);
397
+ return if (o >= 3) &U3_NONE else if (o < 2)
398
+ if (a == 0) &U3_NONE else &U3_FORCED
399
+ else
400
+ &[_]u3{ 0, o + 1 };
386
401
  }
387
402
 
388
- /// Returns a slice with a range of values for binding given the `action` state
403
+ /// Returns a slice with a range of values for binding given the `action` and `origin` states
389
404
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
390
405
  pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
391
- const turns = @field(action.durations, "binding");
392
- _ = origin;
393
- return if (parent == .true or turns < 1 or turns >= 4) &U3_NONE else &[_]u3{ 0, turns };
406
+ if (parent == .true) return &U3_NONE;
407
+ const a = @field(action.durations, "binding");
408
+ const o = @field(origin.durations, "binding");
409
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
410
+ assert(a == 0 or a == o + 1);
411
+ return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
394
412
  }
395
413
 
396
414
  const SLOT_NONE = [_]u4{0};
@@ -535,72 +553,207 @@ test "Rolls.duration" {
535
553
  try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p2, .false));
536
554
  }
537
555
 
538
- const FIXME: Action = .{};
539
-
540
556
  test "Rolls.sleep" {
541
- try expectEqualSlices(u3, &.{0}, Rolls.sleep(.{ .durations = .{ .sleep = 0 } }, FIXME));
542
- try expectEqualSlices(u3, &.{0}, Rolls.sleep(.{ .durations = .{ .sleep = 7 } }, FIXME));
543
- try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.sleep(.{ .durations = .{ .sleep = 4 } }, FIXME));
557
+ try expectEqualSlices(u3, &.{0}, Rolls.sleep(
558
+ .{ .durations = .{ .sleep = 0 } },
559
+ .{ .durations = .{ .sleep = 0 } },
560
+ ));
561
+ try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.sleep(
562
+ .{ .durations = .{ .sleep = 1 } },
563
+ .{ .durations = .{ .sleep = 0 } },
564
+ ));
565
+ try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.sleep(
566
+ .{ .durations = .{ .sleep = 0 } },
567
+ .{ .durations = .{ .sleep = 1 } },
568
+ ));
569
+ try expectEqualSlices(u3, &.{1}, Rolls.sleep(
570
+ .{ .durations = .{ .sleep = 1 } },
571
+ .{ .durations = .{ .sleep = 1 } },
572
+ ));
573
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.sleep(
574
+ .{ .durations = .{ .sleep = 0 } },
575
+ .{ .durations = .{ .sleep = 2 } },
576
+ ));
577
+ try expectEqualSlices(u3, &.{ 0, 7 }, Rolls.sleep(
578
+ .{ .durations = .{ .sleep = 7 } },
579
+ .{ .durations = .{ .sleep = 6 } },
580
+ ));
581
+ try expectEqualSlices(u3, &.{0}, Rolls.sleep(
582
+ .{ .durations = .{ .sleep = 0 } },
583
+ .{ .durations = .{ .sleep = 7 } },
584
+ ));
544
585
  }
545
586
 
546
587
  test "Rolls.disable" {
547
- try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .durations = .{ .disable = 0 } }, FIXME, 0));
548
- try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .durations = .{ .disable = 8 } }, FIXME, 0));
549
- try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .durations = .{ .disable = 4 } }, FIXME, 1));
550
- try expectEqualSlices(u4, &.{ 0, 4 }, Rolls.disable(.{
551
- .durations = .{ .disable = 4 },
552
- }, FIXME, 0));
588
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(
589
+ .{ .durations = .{ .disable = 0 } },
590
+ .{ .durations = .{ .disable = 0 } },
591
+ 0,
592
+ ));
593
+ try expectEqualSlices(u4, &.{ 0, 1 }, Rolls.disable(
594
+ .{ .durations = .{ .disable = 1 } },
595
+ .{ .durations = .{ .disable = 0 } },
596
+ 0,
597
+ ));
598
+ try expectEqualSlices(u4, &.{ 0, 2 }, Rolls.disable(
599
+ .{ .durations = .{ .disable = 0 } },
600
+ .{ .durations = .{ .disable = 1 } },
601
+ 0,
602
+ ));
603
+ try expectEqualSlices(u4, &.{1}, Rolls.disable(
604
+ .{ .durations = .{ .disable = 1 } },
605
+ .{ .durations = .{ .disable = 1 } },
606
+ 0,
607
+ ));
608
+ try expectEqualSlices(u4, &.{ 0, 3 }, Rolls.disable(
609
+ .{ .durations = .{ .disable = 0 } },
610
+ .{ .durations = .{ .disable = 2 } },
611
+ 0,
612
+ ));
613
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(
614
+ .{ .durations = .{ .disable = 0 } },
615
+ .{ .durations = .{ .disable = 2 } },
616
+ 1,
617
+ ));
618
+ try expectEqualSlices(u4, &.{ 0, 8 }, Rolls.disable(
619
+ .{ .durations = .{ .disable = 8 } },
620
+ .{ .durations = .{ .disable = 7 } },
621
+ 0,
622
+ ));
623
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(
624
+ .{ .durations = .{ .disable = 0 } },
625
+ .{ .durations = .{ .disable = 8 } },
626
+ 0,
627
+ ));
553
628
  }
554
629
 
555
630
  test "Rolls.confusion" {
556
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{
557
- .durations = .{ .confusion = 0 },
558
- }, FIXME, 0));
559
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{
560
- .durations = .{ .confusion = 5 },
561
- }, FIXME, 0));
562
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{
563
- .durations = .{ .confusion = 3 },
564
- }, FIXME, 1));
565
- try expectEqualSlices(u3, &.{1}, Rolls.confusion(.{
566
- .durations = .{ .confusion = 1 },
567
- }, FIXME, 0));
568
- try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(.{
569
- .durations = .{ .confusion = 3 },
570
- }, FIXME, 0));
631
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
632
+ .{ .durations = .{ .confusion = 0 } },
633
+ .{ .durations = .{ .confusion = 0 } },
634
+ 0,
635
+ ));
636
+ try expectEqualSlices(u3, &.{1}, Rolls.confusion(
637
+ .{ .durations = .{ .confusion = 1 } },
638
+ .{ .durations = .{ .confusion = 0 } },
639
+ 0,
640
+ ));
641
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
642
+ .{ .durations = .{ .confusion = 0 } },
643
+ .{ .durations = .{ .confusion = 1 } },
644
+ 0,
645
+ ));
646
+ try expectEqualSlices(u3, &.{1}, Rolls.confusion(
647
+ .{ .durations = .{ .confusion = 1 } },
648
+ .{ .durations = .{ .confusion = 1 } },
649
+ 0,
650
+ ));
651
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(
652
+ .{ .durations = .{ .confusion = 0 } },
653
+ .{ .durations = .{ .confusion = 2 } },
654
+ 0,
655
+ ));
656
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
657
+ .{ .durations = .{ .confusion = 0 } },
658
+ .{ .durations = .{ .confusion = 2 } },
659
+ 1,
660
+ ));
661
+ try expectEqualSlices(u3, &.{ 0, 5 }, Rolls.confusion(
662
+ .{ .durations = .{ .confusion = 5 } },
663
+ .{ .durations = .{ .confusion = 4 } },
664
+ 0,
665
+ ));
666
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
667
+ .{ .durations = .{ .confusion = 0 } },
668
+ .{ .durations = .{ .confusion = 5 } },
669
+ 0,
670
+ ));
571
671
  }
572
672
 
573
673
  test "Rolls.attacking" {
574
- try expectEqualSlices(u3, &.{0}, Rolls.attacking(.{
575
- .durations = .{ .attacking = 0 },
576
- }, FIXME, .false));
577
- try expectEqualSlices(u3, &.{0}, Rolls.attacking(.{
578
- .durations = .{ .attacking = 3 },
579
- }, FIXME, .false));
580
- try expectEqualSlices(u3, &.{0}, Rolls.attacking(.{
581
- .durations = .{ .attacking = 2 },
582
- }, FIXME, .true));
583
- try expectEqualSlices(u3, &.{1}, Rolls.attacking(.{
584
- .durations = .{ .attacking = 1 },
585
- }, FIXME, .false));
586
- try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.attacking(.{
587
- .durations = .{ .attacking = 2 },
588
- }, FIXME, .false));
674
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
675
+ .{ .durations = .{ .attacking = 0 } },
676
+ .{ .durations = .{ .attacking = 0 } },
677
+ .false,
678
+ ));
679
+ try expectEqualSlices(u3, &.{1}, Rolls.attacking(
680
+ .{ .durations = .{ .attacking = 1 } },
681
+ .{ .durations = .{ .attacking = 0 } },
682
+ .false,
683
+ ));
684
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
685
+ .{ .durations = .{ .attacking = 0 } },
686
+ .{ .durations = .{ .attacking = 1 } },
687
+ .false,
688
+ ));
689
+ try expectEqualSlices(u3, &.{1}, Rolls.attacking(
690
+ .{ .durations = .{ .attacking = 1 } },
691
+ .{ .durations = .{ .attacking = 1 } },
692
+ .false,
693
+ ));
694
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
695
+ .{ .durations = .{ .attacking = 0 } },
696
+ .{ .durations = .{ .attacking = 2 } },
697
+ .false,
698
+ ));
699
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
700
+ .{ .durations = .{ .attacking = 0 } },
701
+ .{ .durations = .{ .attacking = 2 } },
702
+ .true,
703
+ ));
704
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
705
+ .{ .durations = .{ .attacking = 3 } },
706
+ .{ .durations = .{ .attacking = 2 } },
707
+ .false,
708
+ ));
709
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
710
+ .{ .durations = .{ .attacking = 0 } },
711
+ .{ .durations = .{ .attacking = 3 } },
712
+ .false,
713
+ ));
589
714
  }
590
715
 
591
716
  test "Rolls.binding" {
592
- try expectEqualSlices(u3, &.{0}, Rolls.binding(.{
593
- .durations = .{ .binding = 0 },
594
- }, FIXME, .false));
595
- try expectEqualSlices(u3, &.{0}, Rolls.binding(.{
596
- .durations = .{ .binding = 4 },
597
- }, FIXME, .false));
598
- try expectEqualSlices(u3, &.{0}, Rolls.binding(.{
599
- .durations = .{ .binding = 2 },
600
- }, FIXME, .true));
601
- try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(.{
602
- .durations = .{ .binding = 2 },
603
- }, FIXME, .false));
717
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(
718
+ .{ .durations = .{ .binding = 0 } },
719
+ .{ .durations = .{ .binding = 0 } },
720
+ .false,
721
+ ));
722
+ try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.binding(
723
+ .{ .durations = .{ .binding = 1 } },
724
+ .{ .durations = .{ .binding = 0 } },
725
+ .false,
726
+ ));
727
+ try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(
728
+ .{ .durations = .{ .binding = 0 } },
729
+ .{ .durations = .{ .binding = 1 } },
730
+ .false,
731
+ ));
732
+ try expectEqualSlices(u3, &.{1}, Rolls.binding(
733
+ .{ .durations = .{ .binding = 1 } },
734
+ .{ .durations = .{ .binding = 1 } },
735
+ .false,
736
+ ));
737
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.binding(
738
+ .{ .durations = .{ .binding = 0 } },
739
+ .{ .durations = .{ .binding = 2 } },
740
+ .false,
741
+ ));
742
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(
743
+ .{ .durations = .{ .binding = 0 } },
744
+ .{ .durations = .{ .binding = 2 } },
745
+ .true,
746
+ ));
747
+ try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.binding(
748
+ .{ .durations = .{ .binding = 4 } },
749
+ .{ .durations = .{ .binding = 3 } },
750
+ .false,
751
+ ));
752
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(
753
+ .{ .durations = .{ .binding = 0 } },
754
+ .{ .durations = .{ .binding = 4 } },
755
+ .false,
756
+ ));
604
757
  }
605
758
 
606
759
  test "Rolls.moveSlot" {
@@ -1332,7 +1332,7 @@ fn mirrorMove(
1332
1332
  auto: bool,
1333
1333
  residual: *bool,
1334
1334
  options: anytype,
1335
- ) !?Result {
1335
+ ) anyerror!?Result {
1336
1336
  var side = battle.side(player);
1337
1337
  const foe = battle.foe(player);
1338
1338
 
@@ -1359,7 +1359,7 @@ fn metronome(
1359
1359
  auto: bool,
1360
1360
  residual: *bool,
1361
1361
  options: anytype,
1362
- ) !?Result {
1362
+ ) anyerror!?Result {
1363
1363
  var side = battle.side(player);
1364
1364
 
1365
1365
  side.last_selected_move = try Rolls.metronome(battle, player, options);
@@ -2680,7 +2680,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
2680
2680
  }
2681
2681
  }
2682
2682
 
2683
- inline fn isForced(active: ActivePokemon) bool {
2683
+ inline fn isForced(active: anytype) bool {
2684
2684
  return active.volatiles.Recharging or active.volatiles.Rage or
2685
2685
  active.volatiles.Thrashing or active.volatiles.Charging;
2686
2686
  }
@@ -3072,7 +3072,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3072
3072
  const side = battle.side(player);
3073
3073
  const foe = battle.foe(player);
3074
3074
 
3075
- var active = side.active;
3075
+ var active = &side.active;
3076
3076
  const stored = side.stored();
3077
3077
 
3078
3078
  // While players are not given any input options on the cartridge in these cases,