@pkmn/engine 0.1.0-dev.6ef582f1 → 0.1.0-dev.736f7a8b

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
@@ -49,9 +57,9 @@ $ zig build --prefix /usr/local -Doptimize=ReleaseFast
49
57
  ```
50
58
 
51
59
  The Zig website has [installation instructions](https://ziglang.org/learn/getting-started/) which
52
- 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
54
- breaking language changes are introduced:
60
+ walk through how to install Zig on each platform - the engine code should work on Zig v0.11.0 or
61
+ greater, though tracks Zig's master branch so this may change in the future if breaking language
62
+ changes are introduced.
55
63
 
56
64
  `libpkmn` can be built with `-Dshowdown` to instead produce the Pokémon Showdown compatible
57
65
  `libpkmn-showdown` library. Furthermore, protocol message logging can be enabled through `-Dlog`.
package/build.zig CHANGED
@@ -66,16 +66,16 @@ pub fn build(b: *std.Build) !void {
66
66
  const addon = b.fmt("{s}.node", .{name});
67
67
  const lib = b.addSharedLibrary(.{
68
68
  .name = addon,
69
+ .main_pkg_path = .{ .path = "." },
69
70
  .root_source_file = .{ .path = "src/lib/binding/node.zig" },
70
71
  .optimize = optimize,
71
72
  .target = target,
72
73
  });
73
74
  lib.addOptions("build_options", options);
74
- lib.setMainPkgPath("./");
75
- lib.addSystemIncludePath(headers);
75
+ lib.addSystemIncludePath(.{ .path = headers });
76
76
  lib.linkLibC();
77
77
  if (node_import_lib) |il| {
78
- lib.addObjectFile(il);
78
+ lib.addObjectFile(.{ .path = il });
79
79
  } else if ((try std.zig.system.NativeTargetInfo.detect(target)).target.os.tag == .windows) {
80
80
  try std.io.getStdErr().writeAll("Must provide --node-import-library path on Windows\n");
81
81
  std.process.exit(1);
@@ -95,6 +95,7 @@ pub fn build(b: *std.Build) !void {
95
95
  } else if (wasm) {
96
96
  const lib = b.addSharedLibrary(.{
97
97
  .name = name,
98
+ .main_pkg_path = .{ .path = "." },
98
99
  .root_source_file = .{ .path = "src/lib/binding/wasm.zig" },
99
100
  .optimize = switch (optimize) {
100
101
  .ReleaseFast, .ReleaseSafe => .ReleaseSmall,
@@ -103,7 +104,6 @@ pub fn build(b: *std.Build) !void {
103
104
  .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
104
105
  });
105
106
  lib.addOptions("build_options", options);
106
- lib.setMainPkgPath("./");
107
107
  lib.stack_size = wasm_stack_size;
108
108
  lib.rdynamic = true;
109
109
  lib.strip = strip;
@@ -121,14 +121,14 @@ pub fn build(b: *std.Build) !void {
121
121
  } else if (dynamic) {
122
122
  const lib = b.addSharedLibrary(.{
123
123
  .name = name,
124
+ .main_pkg_path = .{ .path = "." },
124
125
  .root_source_file = .{ .path = "src/lib/binding/c.zig" },
125
126
  .version = try std.SemanticVersion.parse(version),
126
127
  .optimize = optimize,
127
128
  .target = target,
128
129
  });
129
130
  lib.addOptions("build_options", options);
130
- lib.setMainPkgPath("./");
131
- lib.addIncludePath("src/include");
131
+ lib.addIncludePath(.{ .path = "src/include" });
132
132
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
133
133
  if (pic) lib.force_pic = pic;
134
134
  b.installArtifact(lib);
@@ -136,18 +136,30 @@ pub fn build(b: *std.Build) !void {
136
136
  } else {
137
137
  const lib = b.addStaticLibrary(.{
138
138
  .name = name,
139
+ .main_pkg_path = .{ .path = "." },
139
140
  .root_source_file = .{ .path = "src/lib/binding/c.zig" },
140
141
  .optimize = optimize,
141
142
  .target = target,
142
143
  });
143
144
  lib.addOptions("build_options", options);
144
- lib.setMainPkgPath("./");
145
- lib.addIncludePath("src/include");
145
+ lib.addIncludePath(.{ .path = "src/include" });
146
146
  lib.bundle_compiler_rt = true;
147
147
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
148
148
  if (pic) lib.force_pic = pic;
149
- if (emit_asm) lib.emit_asm = .emit;
150
- if (emit_ll) lib.emit_llvm_ir = .emit;
149
+ if (emit_asm) {
150
+ b.getInstallStep().dependOn(&b.addInstallFileWithDir(
151
+ lib.getEmittedAsm(),
152
+ .prefix,
153
+ b.fmt("{s}.s", .{name}),
154
+ ).step);
155
+ }
156
+ if (emit_ll) {
157
+ b.getInstallStep().dependOn(&b.addInstallFileWithDir(
158
+ lib.getEmittedLlvmIr(),
159
+ .prefix,
160
+ b.fmt("{s}.ll", .{name}),
161
+ ).step);
162
+ }
151
163
  b.installArtifact(lib);
152
164
  c = true;
153
165
  }
@@ -219,12 +231,16 @@ pub fn build(b: *std.Build) !void {
219
231
  fuzz_config.tool.name = "fuzz";
220
232
  const fuzz = tool(b, "src/test/fuzz.zig", fuzz_config);
221
233
 
222
- const copies = tool(b, "src/tools/copies.zig", tools);
234
+ const analyze = tool(b, "src/tools/analyze.zig", tools);
223
235
  const dump = tool(b, "src/tools/dump.zig", tools);
224
- const serde = tool(b, "src/tools/serde.zig", tools);
225
236
  const transitions = tool(b, "src/tools/transitions.zig", tools);
226
237
 
227
- b.step("copies", "Run copy detector").dependOn(&copies.step);
238
+ // FIXME: serde randomly fails to build in some release configurations
239
+ var hack = tools;
240
+ if (optimize != .Debug) hack.tool.tests = null;
241
+ const serde = tool(b, "src/tools/serde.zig", hack);
242
+
243
+ b.step("analyze", "Run LLVM analysis tool").dependOn(&analyze.step);
228
244
  b.step("benchmark", "Run benchmark code").dependOn(&benchmark.step);
229
245
  b.step("dump", "Run protocol dump tool").dependOn(&dump.step);
230
246
  b.step("fuzz", "Run fuzz tester").dependOn(&fuzz.step);
@@ -281,12 +297,12 @@ const TestStep = struct {
281
297
  std.fs.path.basename(std.fs.path.dirname(path).?),
282
298
  std.fs.path.stem(std.fs.path.basename(path)),
283
299
  }),
300
+ .main_pkg_path = .{ .path = "." },
284
301
  .root_source_file = .{ .path = path },
285
302
  .optimize = config.optimize,
286
303
  .target = config.target,
287
304
  .filter = test_filter,
288
305
  });
289
- tests.setMainPkgPath("./");
290
306
  tests.addOptions("build_options", options);
291
307
  tests.single_threaded = true;
292
308
  maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
@@ -343,7 +359,7 @@ const ToolsStep = struct {
343
359
  const self = b.allocator.create(ToolsStep) catch @panic("OOM");
344
360
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Install tools", .owner = b });
345
361
  self.* = ToolsStep{ .step = step };
346
- for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t).step);
362
+ for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t, .{}).step);
347
363
  return self;
348
364
  }
349
365
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.6ef582f1",
3
+ "version": "0.1.0-dev.736f7a8b",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -73,30 +73,30 @@
73
73
  "access": "public"
74
74
  },
75
75
  "dependencies": {
76
- "@pkmn/data": "^0.7.46",
77
- "@pkmn/protocol": "^0.6.12"
76
+ "@pkmn/data": "^0.7.50",
77
+ "@pkmn/protocol": "^0.6.13"
78
78
  },
79
79
  "devDependencies": {
80
- "@pkmn/eslint-config": "^5.0.0",
81
- "@pkmn/img": "^0.2.13",
80
+ "@pkmn/eslint-config": "^5.2.0",
81
+ "@pkmn/img": "^0.2.15",
82
82
  "@pkmn/sets": "^5.0.4",
83
- "@pkmn/sim": "0.7.46",
83
+ "@pkmn/sim": "0.7.50",
84
84
  "@types/html-minifier": "^4.0.2",
85
- "@types/jest": "^29.5.3",
85
+ "@types/jest": "^29.5.4",
86
86
  "@types/minimist": "^1.2.2",
87
87
  "@types/mustache": "^4.2.2",
88
- "@types/node": "^20.4.2",
88
+ "@types/node": "^20.5.9",
89
89
  "@vitest/coverage-c8": "^0.33.0",
90
90
  "binaryen": "^114.0.0",
91
- "eslint": "^8.44.0",
91
+ "eslint": "^8.48.0",
92
92
  "html-minifier": "^4.0.0",
93
93
  "json-stringify-pretty-compact": "3.0.0",
94
94
  "minimist": "^1.2.8",
95
95
  "mustache": "^4.2.0",
96
96
  "source-map-support": "^0.5.21",
97
97
  "trakr": "^0.2.0",
98
- "typescript": "^5.1.6",
99
- "vitest": "^0.33.0"
98
+ "typescript": "^5.2.2",
99
+ "vitest": "^0.34.4"
100
100
  },
101
101
  "scripts": {
102
102
  "lint": "eslint --cache src --ext ts,js && eslint src/bin/*",
@@ -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};
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';
@@ -136,6 +136,8 @@ pub const Damage = enum(u8) {
136
136
  Confusion,
137
137
  LeechSeed,
138
138
  RecoilOf,
139
+
140
+ Spikes,
139
141
  };
140
142
 
141
143
  pub const Status = enum(u8) {
@@ -264,7 +266,15 @@ pub fn Log(comptime Writer: type) type {
264
266
  if (!enabled) return;
265
267
 
266
268
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
267
- try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
269
+ if (@hasField(@TypeOf(pokemon.*), "dvs")) {
270
+ try self.writer.writeAll(&.{
271
+ @intFromEnum(pokemon.species),
272
+ @intFromEnum(pokemon.dvs.gender),
273
+ pokemon.level,
274
+ });
275
+ } else {
276
+ try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
277
+ }
268
278
  try self.writer.writeIntNative(u16, pokemon.hp);
269
279
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
270
280
  try self.writer.writeAll(&.{pokemon.status});
@@ -646,9 +656,13 @@ pub const ByteStream = struct {
646
656
  };
647
657
 
648
658
  pub const Kind = enum { Move, Species, Type, Status };
649
- pub const Formatter = fn (Kind, u8) []const u8;
650
659
 
651
- pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, color: bool) void {
660
+ pub fn format(
661
+ comptime gen: anytype,
662
+ a: []const u8,
663
+ b: ?[]const u8,
664
+ color: bool,
665
+ ) void {
652
666
  print("\n", .{});
653
667
 
654
668
  var i: usize = 0;
@@ -701,17 +715,27 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
701
715
  .Move => {
702
716
  const source = ID.from(@intCast(a[i]));
703
717
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
704
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
718
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
705
719
  const target = ID.from(@intCast(a[i]));
706
720
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
707
721
  const reason: Move = @enumFromInt(a[i]);
708
722
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
709
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
723
+ if (reason == .From) {
724
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
725
+ }
710
726
  },
711
727
  .Switch => {
712
728
  const id = ID.from(@intCast(a[i]));
713
729
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
714
- printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
730
+ printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
731
+ if (@hasDecl(gen, "Gender")) {
732
+ if (a[i] == N(gen.Gender.Unknown)) {
733
+ i += 1;
734
+ } else {
735
+ var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
736
+ printc(", {s}", .{gender}, a, b, &i, 1, color);
737
+ }
738
+ }
715
739
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
716
740
  switch (endian) {
717
741
  .Big => {
@@ -727,7 +751,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
727
751
  printc("/{d}", .{hp}, a, b, &i, 2, color);
728
752
  },
729
753
  }
730
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
754
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
731
755
  },
732
756
  .Cant => {
733
757
  const id = ID.from(@intCast(a[i]));
@@ -735,7 +759,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
735
759
  const reason: Cant = @enumFromInt(a[i]);
736
760
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
737
761
  if (reason == .Disable) {
738
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
762
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
739
763
  }
740
764
  },
741
765
  .Faint,
@@ -775,7 +799,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
775
799
  printc("/{d}", .{hp}, a, b, &i, 2, color);
776
800
  },
777
801
  }
778
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
802
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
779
803
  if (arg == .Damage) {
780
804
  const reason: Damage = @enumFromInt(a[i]);
781
805
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
@@ -795,15 +819,17 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
795
819
  .Status => {
796
820
  const id = ID.from(@intCast(a[i]));
797
821
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
798
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
822
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
799
823
  const reason: Status = @enumFromInt(a[i]);
800
824
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
801
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
825
+ if (reason == .From) {
826
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
827
+ }
802
828
  },
803
829
  .CureStatus => {
804
830
  const id = ID.from(@intCast(a[i]));
805
831
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
806
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
832
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
807
833
  const reason: CureStatus = @enumFromInt(a[i]);
808
834
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
809
835
  },
@@ -826,7 +852,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
826
852
  .Prepare => {
827
853
  const id = ID.from(@intCast(a[i]));
828
854
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
829
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
855
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
830
856
  },
831
857
  .Activate => {
832
858
  const id = ID.from(@intCast(a[i]));
@@ -841,12 +867,12 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
841
867
  if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
842
868
  const types = @as(gen1.Types, @bitCast(a[i]));
843
869
  const args = .{
844
- formatter(.Type, @intFromEnum(types.type1)),
845
- formatter(.Type, @intFromEnum(types.type2)),
870
+ formatter(gen, .Type, @intFromEnum(types.type1)),
871
+ formatter(gen, .Type, @intFromEnum(types.type2)),
846
872
  };
847
873
  printc(" {s}/{s}", args, a, b, &i, 1, color);
848
874
  } else if (reason >= @intFromEnum(Start.Disable)) {
849
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
875
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
850
876
  }
851
877
  },
852
878
  .End => {
@@ -896,7 +922,7 @@ fn printc(
896
922
  }
897
923
 
898
924
  pub fn expectLog(
899
- comptime formatter: Formatter,
925
+ comptime gen: anytype,
900
926
  expected: []const u8,
901
927
  actual: []const u8,
902
928
  offset: usize,
@@ -915,8 +941,8 @@ pub fn expectLog(
915
941
 
916
942
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
917
943
  error.TestExpectedEqual => {
918
- format(formatter, expected, null, color);
919
- format(formatter, actual, expected, color);
944
+ format(gen, expected, null, color);
945
+ format(gen, actual, expected, color);
920
946
  return err;
921
947
  },
922
948
  else => return err,
@@ -956,6 +982,11 @@ const gen1 = struct {
956
982
  pub const helpers = @import("../gen1/helpers.zig");
957
983
  };
958
984
 
985
+ const gen2 = struct {
986
+ pub usingnamespace @import("../gen2/data.zig");
987
+ pub const helpers = @import("../gen2/helpers.zig");
988
+ };
989
+
959
990
  var buf: [gen1.LOGS_SIZE]u8 = undefined;
960
991
  var stream = ByteStream{ .buffer = &buf };
961
992
  var log: FixedLog = .{ .writer = stream.writer() };
@@ -964,15 +995,19 @@ const M = gen1.Move;
964
995
  const S = gen1.Species;
965
996
 
966
997
  fn expectLog1(expected: []const u8, actual: []const u8) !void {
967
- return expectLog(gen1Formatter, expected, actual, 0);
998
+ return expectLog(gen1, expected, actual, 0);
968
999
  }
969
1000
 
970
- fn gen1Formatter(kind: Kind, byte: u8) []const u8 {
1001
+ fn expectLog2(expected: []const u8, actual: []const u8) !void {
1002
+ return expectLog(gen2, expected, actual, 0);
1003
+ }
1004
+
1005
+ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
971
1006
  return switch (kind) {
972
- .Move => @tagName(@as(M, @enumFromInt(byte))),
973
- .Species => @tagName(@as(S, @enumFromInt(byte))),
974
- .Type => @tagName(@as(gen1.Type, @enumFromInt(byte))),
975
- .Status => gen1.Status.name(byte),
1007
+ .Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
1008
+ .Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
1009
+ .Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
1010
+ .Status => gen.Status.name(byte),
976
1011
  };
977
1012
  }
978
1013
 
@@ -1057,6 +1092,25 @@ test "|switch|" {
1057
1092
  };
1058
1093
  try expectLog1(expected, buf[0..9]);
1059
1094
  stream.reset();
1095
+
1096
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1097
+ blissey.level = 91;
1098
+ blissey.hp = 200;
1099
+ blissey.stats.hp = 400;
1100
+ blissey.status = gen2.Status.init(.PAR);
1101
+ try log.switched(p2.ident(3), &blissey);
1102
+ expected = &(.{
1103
+ N(ArgType.Switch),
1104
+ 0b1011,
1105
+ N(gen2.Species.Blissey),
1106
+ N(gen2.Gender.Female),
1107
+ 91,
1108
+ } ++ switch (endian) {
1109
+ .Big => .{ 0, 200, 1, 144, par },
1110
+ .Little => .{ 200, 0, 144, 1, par },
1111
+ });
1112
+ try expectLog2(expected, buf[0..10]);
1113
+ stream.reset();
1060
1114
  }
1061
1115
 
1062
1116
  test "|cant|" {
@@ -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
@@ -29,7 +29,6 @@ const Optional = optional.Optional;
29
29
  const Rational = rational.Rational;
30
30
 
31
31
  const PointerType = util.PointerType;
32
- const FieldType = util.FieldType;
33
32
 
34
33
  const Actions = chance.Actions;
35
34
  const Action = chance.Action;
@@ -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
  }