@pkmn/engine 0.1.0-dev.7679dad9 → 0.1.0-dev.7963b917
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 +43 -40
- package/build/pkg/addon.js +3 -2
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +24 -2
- package/build/pkg/gen1.js +13 -16
- package/build/pkg/gen1.js.map +1 -1
- package/build.zig +59 -51
- package/package.json +24 -15
- package/src/bin/install-pkmn-engine +10 -5
- package/src/lib/binding/node.zig +45 -140
- package/src/lib/binding/wasm.zig +4 -2
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/data.zig +11 -11
- package/src/lib/common/debug.zig +20 -11
- package/src/lib/common/js.zig +118 -0
- package/src/lib/common/optional.zig +60 -0
- package/src/lib/common/options.zig +9 -0
- package/src/lib/common/protocol.zig +204 -140
- package/src/lib/common/rational.zig +353 -0
- package/src/lib/common/rng.zig +10 -10
- package/src/lib/common/util.zig +12 -0
- package/src/lib/gen1/README.md +173 -173
- package/src/lib/gen1/calc.zig +427 -0
- package/src/lib/gen1/chance.zig +1044 -0
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +3 -3
- package/src/lib/gen1/data.zig +61 -37
- package/src/lib/gen1/helpers.zig +435 -17
- package/src/lib/gen1/mechanics.zig +754 -481
- package/src/lib/gen2/README.md +12 -13
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +1 -1
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +3 -3
- package/src/lib/gen2/data.zig +33 -22
- package/src/lib/gen2/helpers.zig +9 -11
- package/src/lib/gen2/mechanics.zig +5 -4
- package/src/lib/pkmn.zig +61 -2
package/src/lib/gen1/README.md
CHANGED
|
@@ -9,8 +9,8 @@ following files:
|
|
|
9
9
|
by the engine, described in detail [below](#data-structures)
|
|
10
10
|
- [`helpers.zig`](helpers.zig): helpers used to construct complex data types with
|
|
11
11
|
sensible defaults (internally used by tests and tools)
|
|
12
|
-
- [`mechanics.zig`](mechanics.zig): code which manipulates the data structures
|
|
13
|
-
|
|
12
|
+
- [`mechanics.zig`](mechanics.zig): code which manipulates the data structures to implement the
|
|
13
|
+
mechanics of the game
|
|
14
14
|
- [`test.zig`](test.zig): unit tests for `mechanics.zig` (the code is also tested by [integration
|
|
15
15
|
tests](../../../docs/TESTING.md) at a higher level)
|
|
16
16
|
|
|
@@ -28,70 +28,68 @@ list of additional reference [**resources**](#resources).
|
|
|
28
28
|
|
|
29
29
|
The following information is required to simulate a Generation I Pokémon battle:
|
|
30
30
|
|
|
31
|
-
| pkmn
|
|
32
|
-
|
|
|
33
|
-
| `battle.seed`
|
|
34
|
-
| `battle.turn`
|
|
35
|
-
| `battle.last_damage`
|
|
36
|
-
| `side.{active,pokemon}`
|
|
37
|
-
| `side.team`
|
|
38
|
-
| `side.last_used_move`
|
|
39
|
-
| `side.last_selected_move`
|
|
40
|
-
| `battle.last_moves.
|
|
41
|
-
| `battle.last_moves.
|
|
42
|
-
| `side.order`
|
|
43
|
-
| `{pokemon,active}.moves`
|
|
44
|
-
| `pokemon.hp`
|
|
45
|
-
| `{pokemon,active}.status`
|
|
46
|
-
| `{pokemon,active}.level`
|
|
47
|
-
| `pokemon.species`
|
|
48
|
-
| `pokemon.stats`
|
|
49
|
-
| `active.stats`
|
|
50
|
-
| `volatiles.transform`
|
|
51
|
-
| `active.species`
|
|
52
|
-
| `{pokemon,active}.types`
|
|
53
|
-
| `active.boosts`
|
|
54
|
-
| `active.volatiles`
|
|
55
|
-
| `volatiles.state`
|
|
56
|
-
| `volatiles.attacks`
|
|
57
|
-
| `volatiles.confusion`
|
|
58
|
-
| `volatiles.toxic`
|
|
59
|
-
| `volatiles.substitute`
|
|
60
|
-
| `volatiles.disabled_{move,duration}`
|
|
61
|
-
|
|
62
|
-
- Pokémon Showdown [
|
|
31
|
+
| pkmn | Pokémon Red (pret) | Pokémon Showdown |
|
|
32
|
+
| ------------------------------------ | ---------------------------------- | -------------------------------------- |
|
|
33
|
+
| `battle.seed` | `Random{Add,Sub}` | `battle.seed` |
|
|
34
|
+
| `battle.turn` | - | `battle.turn` |
|
|
35
|
+
| `battle.last_damage` | `Damage` | `battle.lastDamage` |
|
|
36
|
+
| `side.{active,pokemon}` | `PlayerMonNumber`/`BattleMon` | `side.active` |
|
|
37
|
+
| `side.team` | `PartyMons` | `side.pokemon` |
|
|
38
|
+
| `side.last_used_move` | `PlayerUsedMove` | `pokemon.lastMove` |
|
|
39
|
+
| `side.last_selected_move` | `PlayerSelectedMove` | `side.lastSelectedMove` |
|
|
40
|
+
| `battle.last_moves.index` | `PlayerMoveListIndex` | - |
|
|
41
|
+
| `battle.last_moves.counterable` | - | `side.lastMove` |
|
|
42
|
+
| `side.order` | - | `pokemon.position` |
|
|
43
|
+
| `{pokemon,active}.moves` | `{party,battle}_struct.{Moves,PP}` | `pokemon.{baseMoveSlots,moveSlots}` |
|
|
44
|
+
| `pokemon.hp` | `{party,battle}_struct.HP` | `pokemon.hp` |
|
|
45
|
+
| `{pokemon,active}.status` | `{party,battle}_struct.Status` | `pokemon.status` |
|
|
46
|
+
| `{pokemon,active}.level` | `PlayerMonUnmodifiedLevel` | `pokemon.level` |
|
|
47
|
+
| `pokemon.species` | `party_struct.Species` | `pokemon.baseSpecies` |
|
|
48
|
+
| `pokemon.stats` | `box_struct.Stats` | `pokemon.baseStoredStats` |
|
|
49
|
+
| `active.stats` | `battle_struct.Stats` | `pokemon.modifiedStats` |
|
|
50
|
+
| `volatiles.transform` | `PlayerMonUnmodified*` | `pokemon.storedStats` |
|
|
51
|
+
| `active.species` | `battle_struct.Species` | `pokemon.species` |
|
|
52
|
+
| `{pokemon,active}.types` | `{party,battle}_struct.Type` | `pokemon.types` |
|
|
53
|
+
| `active.boosts` | `PlayerMon*Mod` | `pokemon.boosts` |
|
|
54
|
+
| `active.volatiles` | `PlayerBattleStatus{1,2,3}` | `pokemon.volatiles` |
|
|
55
|
+
| `volatiles.state` | `PlayerBideAccumulatedDamage` | `volatiles.bide.totalDamage` |
|
|
56
|
+
| `volatiles.attacks` | `PlayerNumAttacksLeft` | `volatiles.{bide,lockedmove}.duration` |
|
|
57
|
+
| `volatiles.confusion` | `PlayerConfusedCounter` | `volatiles.confusion.duration` |
|
|
58
|
+
| `volatiles.toxic` | `PlayerToxicCounter` | `volatiles.residualdmg.counter` |
|
|
59
|
+
| `volatiles.substitute` | `PlayerSubstituteHP` | `volatiles.substitute.hp` |
|
|
60
|
+
| `volatiles.disabled_{move,duration}` | `PlayerDisabledMove{,Number}` | `volatiles.disable.{move,time}` |
|
|
61
|
+
|
|
62
|
+
- Pokémon Showdown [doesn't implement the correct Generation I RNG](#rng) and as such its `seed` is
|
|
63
63
|
different
|
|
64
|
-
- `battle.turn` only needs to be tracked
|
|
65
|
-
protocol
|
|
64
|
+
- `battle.turn` only needs to be tracked to be compatible with the Pokémon Showdown protocol
|
|
66
65
|
- Pokémon Showdown tracks several last used move variables (`Battle.lastMove`, `Side.lastMove`, and
|
|
67
66
|
`Pokemon.lastMove`), none of which accurately match the `PlayerUsedMove` variable from the
|
|
68
|
-
cartridge (`Side.lastMove` should be what
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- Pokémon Showdown does not implement the [partial-trapping move Mirror Move
|
|
67
|
+
cartridge (`Side.lastMove` should be what's used, but is used by Pokémon Showdown only for Counter
|
|
68
|
+
and isn't set and cleared in the correct locations. `Pokemon.lastMove` matches `PlayerUsedMove`
|
|
69
|
+
more often so is what the engine attempts to model, despite the implications for Counter)
|
|
70
|
+
- `battle.last_moves` doesn't map precisely to cartridge concepts because it's used to track desyncs
|
|
71
|
+
which instead are represented on the cartridge as discrepancies between the two separate battle
|
|
72
|
+
states that exist in the link battle.
|
|
73
|
+
- Pokémon Showdown doesn't implement the [partial-trapping move Mirror Move
|
|
76
74
|
glitch](https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch) and as such
|
|
77
|
-
|
|
75
|
+
doesn't need to keep track of a player's last selected move index (`PlayerMoveListIndex`)
|
|
78
76
|
- Battle results (win, lose, draw) and request states are communicated via the return value of
|
|
79
77
|
`Battle.update`
|
|
80
|
-
- Nicknames (`BattleMonNick`/`pokemon.name`)
|
|
78
|
+
- Nicknames (`BattleMonNick`/`pokemon.name`) aren't handled by the pkmn engine as they're expected
|
|
81
79
|
to be handled by driver code if required
|
|
82
80
|
- Pokémon Red's `LastSwitchInEnemyMonHP`, `InHandlePlayerMonFainted`, `PlayerNumHits`,
|
|
83
81
|
`PlayerMonMinimized`, and `Move{DidntMiss,Missed}` are relevant for messaging/UI only
|
|
84
82
|
- Pokémon Red's `TransformedEnemyMonOriginalDVs` is only relevant for the [Transform DV manipulation
|
|
85
83
|
glitch](https://pkmn.cc/bulba/Transform_glitches#Transform_DV_manipulation_glitch)
|
|
86
|
-
- Pokémon Red's `PlayerMove*` tracks information that
|
|
84
|
+
- Pokémon Red's `PlayerMove*` tracks information that's stored in the `Move` class
|
|
87
85
|
- Pokémon Red's `PlayerStatsToDouble`/`PlayerStatsToHalve` are constants which are always 0
|
|
88
|
-
- pkmn
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
- pkmn doesn't store the DVs/stat experience of Pokémon as they're expected to already be accounted
|
|
87
|
+
for in the `Pokemon` `stats` and never need to be referenced in-battle (though a `DVs` struct
|
|
88
|
+
exists to simplify generating legal test data)
|
|
91
89
|
- Instead of storing unmodified stats like Pokémon Red or Pokémon Showdown, pkmn simply tracks the
|
|
92
90
|
identity of the Pokémon that has been transformed into in the `active.volatiles.transform` field
|
|
93
91
|
- pkmn uses `volatiles.state` for total accumulated damage for Bide but also for implementing
|
|
94
|
-
accuracy overwrite mechanics for certain moves (this glitch is present on the device but
|
|
92
|
+
accuracy overwrite mechanics for certain moves (this glitch is present on the device but isn't
|
|
95
93
|
correctly implemented by Pokémon Showdown currently)
|
|
96
94
|
|
|
97
95
|
### `Battle` / `Side`
|
|
@@ -103,13 +101,13 @@ store general information about the battle. Unlike in Pokémon Showdown there is
|
|
|
103
101
|
between the data structure for the "active" Pokémon and its party members (see below).
|
|
104
102
|
|
|
105
103
|
Due to layout constraints, details about the last moves for a side are stored in what would
|
|
106
|
-
otherwise be the padding bytes of`Battle` (the `last_moves` field) instead of in `Side`.
|
|
104
|
+
otherwise be the padding bytes of `Battle` (the `last_moves` field) instead of in `Side`.
|
|
107
105
|
|
|
108
106
|
### `Pokemon` / `ActivePokemon`
|
|
109
107
|
|
|
110
|
-
Similar to the cartridge,
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
Similar to the cartridge, to save space different information is stored depending on whether a
|
|
109
|
+
[Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_I%29) is actively
|
|
110
|
+
participating in battle vs. is switched out (pret's [`battle_struct` vs.
|
|
113
111
|
`party_struct`](https://pkmn.cc/pokered/macros/ram.asm)). In Pokémon Showdown, all the Pokémon in a
|
|
114
112
|
battle are represented by the same
|
|
115
113
|
[`Pokemon`](https://github.com/smogon/pokemon-showdown/blob/master/sim/pokemon.ts) class, and static
|
|
@@ -121,14 +119,14 @@ A `MoveSlot` is a data-type for a `(move, current pp)` pair. A pared down versio
|
|
|
121
119
|
Showdown's `Pokemon.moveSlot`, it also stores data from the cartridge's `battle_struct::Move` macro
|
|
122
120
|
and can be used to replace the `PlayerMove*` data. Move PP is stored as a full byte instead of how
|
|
123
121
|
the cartridge (`battle_struct::PP`) stores it (6 bits for current PP and the remaining 2 bits used
|
|
124
|
-
to store the number of applied PP Ups). PP Up bits
|
|
122
|
+
to store the number of applied PP Ups). PP Up bits don't actually need to be stored on move slot
|
|
125
123
|
as max PP is never relevant in Generation I.
|
|
126
124
|
|
|
127
125
|
#### `Status`
|
|
128
126
|
|
|
129
127
|
Bitfield representation of a Pokémon's major [status
|
|
130
|
-
condition](https://pkmn.cc/bulba/Status_condition), mirroring how it
|
|
131
|
-
value of `0x00` means that the Pokémon
|
|
128
|
+
condition](https://pkmn.cc/bulba/Status_condition), mirroring how it's stored on the cartridge. A
|
|
129
|
+
value of `0x00` means that the Pokémon isn't affected by any major status, otherwise the lower 3
|
|
132
130
|
bits represent the remaining duration for Sleep. Other status are denoted by the presence of
|
|
133
131
|
individual bits - at most one status should be set at any given time.
|
|
134
132
|
|
|
@@ -137,10 +135,10 @@ below), so the upper most bit of `Status` is instead used to track state require
|
|
|
137
135
|
quirks:
|
|
138
136
|
|
|
139
137
|
- when combined with a valid sleep duration (or 0), it indicates that the `SLP` status was
|
|
140
|
-
self-inflicted (required
|
|
138
|
+
self-inflicted (required to implement Pokémon Showdown's "Sleep Clause Mod")
|
|
141
139
|
- when combined with `PSN` it indicates that the Pokémon is actually badly poisoned (required in
|
|
142
140
|
order to send the same incorrect protocol messages as Pokémon Showdown - the Toxic volatile
|
|
143
|
-
alone
|
|
141
|
+
alone isn't sufficient in compatibility mode because it gets lost on switch)
|
|
144
142
|
|
|
145
143
|
#### `Volatiles`
|
|
146
144
|
|
|
@@ -176,17 +174,16 @@ boolean flags that are cleared when the Pokémon faints or switches out:
|
|
|
176
174
|
[Toxic](https://pkmn.cc/bulba/Toxic_(move)) (counter),
|
|
177
175
|
[Transform](https://pkmn.cc/bulba/Transform_(move)) (identity), and
|
|
178
176
|
[Disable](https://pkmn.cc/bulba/Disable_(move)) (move and duration), and multi-hit attacks all
|
|
179
|
-
require additional information that
|
|
180
|
-
strictly required to be stored as it should always be 0 after updates barring errors, however it
|
|
177
|
+
require additional information that's also stored in the `Volatiles` structure. `MultiHit` isn't
|
|
178
|
+
strictly required to be stored as it should always be 0 after updates barring errors, however it's
|
|
181
179
|
convenient to maintain as it maps neatly to the cartridge and is effectively "free" space-wise due
|
|
182
180
|
to padding.
|
|
183
181
|
|
|
184
182
|
The `state` field of `Volatiles` is effectively treated as a union:
|
|
185
183
|
|
|
186
184
|
- if `volatiles.Bide` is set, `volatiles.data.state` reflects the total accumulated Bide damage
|
|
187
|
-
- otherwise, `volatiles.data.state` reflects the last computed move accuracy (required
|
|
188
|
-
|
|
189
|
-
bug](https://www.youtube.com/watch?v=NC5gbJeExbs))
|
|
185
|
+
- otherwise, `volatiles.data.state` reflects the last computed move accuracy (required to implement
|
|
186
|
+
the [Rage and Thrash / Petal Dance accuracy bug](https://www.youtube.com/watch?v=NC5gbJeExbs))
|
|
190
187
|
|
|
191
188
|
#### `Stats` / `Boosts`
|
|
192
189
|
|
|
@@ -198,8 +195,8 @@ boosts should always range from `-6`...`6` instead of `1`...`13` as on the cartr
|
|
|
198
195
|
|
|
199
196
|
`Move` serves as an identifier for a unique [Pokémon move](https://pkmn.cc/bulba/Move) that can be
|
|
200
197
|
used to retrieve `Move.Data` with information regarding base power, accuracy, and type. As covered
|
|
201
|
-
|
|
202
|
-
a special sentinel value to indicate `null`. Move PP data is only included for testing and
|
|
198
|
+
earlier, PP information isn't strictly necessary in Generation I so is dropped. `Move.None` exists
|
|
199
|
+
as a special sentinel value to indicate `null`. Move PP data is only included for testing and isn't
|
|
203
200
|
necessary for the actual engine implementation.
|
|
204
201
|
|
|
205
202
|
In order to workaround various [Pokémon Showdown bugs](#bugs) and to support its protocol in logs,
|
|
@@ -221,11 +218,11 @@ places to determine which protocol messages to print.
|
|
|
221
218
|
### `Species` / `Species.Data`
|
|
222
219
|
|
|
223
220
|
`Species` just serves as an identifier for a unique [Pokémon
|
|
224
|
-
species](https://pkmn.cc/bulba/Pokémon_species_data_structure_(Generation_I)) as the base stats
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
species](https://pkmn.cc/bulba/Pokémon_species_data_structure_(Generation_I)) as the base stats of a
|
|
222
|
+
species are already accounted for in the computed stats in the `Pokemon` structure and nothing in
|
|
223
|
+
battle requires these to be recomputed. Similarly, Type is unnecessary to include as it's also
|
|
227
224
|
already present in the `Pokemon` struct. `Species.None` exists as a special sentinel value to
|
|
228
|
-
indicate `null`. `Species.Data` is only included for testing and
|
|
225
|
+
indicate `null`. `Species.Data` is only included for testing and isn't necessary for the actual
|
|
229
226
|
engine implementation, outside of `Species.CHANCES` which is required as the base speed / 2 of the
|
|
230
227
|
species is necessary for computing critical hit probability.
|
|
231
228
|
|
|
@@ -238,15 +235,15 @@ effectiveness - like the cartridge, effectiveness is stored as `0`, `5`, `10`, a
|
|
|
238
235
|
(technically only a 2-bit value is required, but as with `Types` Zig only allows a minimum of a byte
|
|
239
236
|
to be stored at each address of an array).
|
|
240
237
|
|
|
241
|
-
The 'precedence' of the various type
|
|
238
|
+
The 'precedence' of the various type match-ups matters beyond just the [dual-type damage
|
|
242
239
|
misinformation
|
|
243
240
|
glitch](https://pkmn.cc/bulba/List_of_glitches_(Generation_I)#Dual-type_damage_misinformation) -
|
|
244
241
|
type effectiveness modifiers are applied based on the (haphazard) ordering of the effectiveness
|
|
245
242
|
table as opposed to first applying modifiers for a species's first type and then second type. The
|
|
246
|
-
engine maintains a `Type.PRECEDENCE` table with just the
|
|
247
|
-
type combinations
|
|
248
|
-
for efficiency) and only looks up precedence when necessary to minimize expensive searches.
|
|
249
|
-
Showdown
|
|
243
|
+
engine maintains a `Type.PRECEDENCE` table with just the match-ups that are relevant in game
|
|
244
|
+
(certain type combinations can't crop up with the limited Generation I species pool and as such are
|
|
245
|
+
pruned for efficiency) and only looks up precedence when necessary to minimize expensive searches.
|
|
246
|
+
Pokémon Showdown doesn't implement type precedence.
|
|
250
247
|
|
|
251
248
|
## Information
|
|
252
249
|
|
|
@@ -272,8 +269,8 @@ entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as foll
|
|
|
272
269
|
| **crit chance** | 7..65 | 6 | | **transform** | 0..15 | 4 |
|
|
273
270
|
| **target** | 0..4 | 3 | | | | |
|
|
274
271
|
|
|
275
|
-
From this
|
|
276
|
-
how much overhead the representations
|
|
272
|
+
From this one can determine the minimum bits[^1] required to store each data structure to determine
|
|
273
|
+
how much overhead the preceding representations have after taking into consideration [alignment &
|
|
277
274
|
padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
|
|
278
275
|
[denormalization](https://en.wikipedia.org/wiki/Denormalization):
|
|
279
276
|
|
|
@@ -286,9 +283,9 @@ padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
|
|
|
286
283
|
- the active Pokémon's level and current and max HP can always be referred to the `Pokemon`
|
|
287
284
|
struct
|
|
288
285
|
- **`Side`**: `ActivePokemon` + 6× `Pokemon` + active (`3`) + last used (`8`) + last selected
|
|
289
|
-
(`8`)
|
|
290
|
-
- `order`
|
|
291
|
-
- **`Battle`**: 6× `Side` + seed (
|
|
286
|
+
(`8`) + counterable (`1`) + last move index (`3`)
|
|
287
|
+
- `order` doesn't need to be stored as the party can always be rearranged as switches occur
|
|
288
|
+
- **`Battle`**: 6× `Side` + seed (9× `8` + `4`) + turn (`10`) + last damage (`10`)
|
|
292
289
|
- **`Type.CHART`**: attacking types (`15`) × defending types (`15`) × effectiveness (`2`)[^2]
|
|
293
290
|
- **`Type.PRECEDENCE`**: 29× attacking type (`4`) + defending type (`4`) pairs
|
|
294
291
|
- **`Moves.DATA`**: 165× base power (`6`) + effect (`7`) + accuracy (`4`) + type (`4`) + target
|
|
@@ -299,8 +296,8 @@ padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
|
|
|
299
296
|
| ----------------- | ----------- | ------------ | -------- |
|
|
300
297
|
| `Pokemon` | 192 | 147 | 30.6% |
|
|
301
298
|
| `ActivePokemon` | 256 | 202 | 26.7% |
|
|
302
|
-
| `Side` | 1472 |
|
|
303
|
-
| `Battle` | 3088 |
|
|
299
|
+
| `Side` | 1472 | 1107 | 33.0% |
|
|
300
|
+
| `Battle` | 3088 | 2310 | 33.7% |
|
|
304
301
|
| `Type.CHART` | 1800 | 450 | 300.0% |
|
|
305
302
|
| `Type.PRECEDENCE` | 232 | 232 | 0.0% |
|
|
306
303
|
| `Moves.DATA` | 5280 | 3960 | 33.3% |
|
|
@@ -313,25 +310,25 @@ consideration the worst case all Pokémon types are required and 48 moves. The `
|
|
|
313
310
|
could be eliminated and instead the `Move` data actually required by each `Pokemon` could be placed
|
|
314
311
|
beside the `MoveSlot`, though this is both less general and adds unnecessary complexity.
|
|
315
312
|
|
|
316
|
-
[^1]: For data with lower cardinality it
|
|
313
|
+
[^1]: For data with lower cardinality it's possible to save memory by ordinalizing the values and
|
|
317
314
|
turning them into indices into a lookup table (e.g. encode all possible move base powers in a lookup
|
|
318
315
|
table and store the index into that table instead of the actual base power). While this approach may
|
|
319
316
|
minimize the absolute memory used, performance is more likely to suffer as the goal is to minimize
|
|
320
|
-
|
|
317
|
+
un-cached memory lookups, not total memory usage.
|
|
321
318
|
|
|
322
319
|
[^2]: Instead of storing as a sparse multi-dimensional array the type chart could instead only
|
|
323
320
|
[store values which are not normal
|
|
324
321
|
effectiveness](https://github.com/pret/pokered/blob/master/data/types/type_matchups.asm) in 820 bits
|
|
325
322
|
as 82× attacking type (`4`) + defending type (`4`) + non-Normal effectiveness (`2`). This would
|
|
326
323
|
avoid having to do two memory lookups at the cost of a longer time scanning, but the second lookup
|
|
327
|
-
should already be fast due to locality of reference meaning it
|
|
324
|
+
should already be fast due to locality of reference meaning it's likely to already be in the cache.
|
|
328
325
|
This approach would also render `Type.PRECEDENCE` unnecessary.
|
|
329
326
|
|
|
330
327
|
## Layout
|
|
331
328
|
|
|
332
329
|
The precise layout of the engine's data structures is important to those implementing driver code,
|
|
333
330
|
as clients must directly probe the engine's state through these structures (i.e. the pkmn engine
|
|
334
|
-
|
|
331
|
+
doesn't produce an equivalent to Pokémon Showdown's `|request|` protocol message, this information
|
|
335
332
|
must be gleaned through the `Battle` state). Useful size and offset information can be found in
|
|
336
333
|
the [`layout.json`](../../data/layout.json) which exists to simplify writing driver code.
|
|
337
334
|
|
|
@@ -342,14 +339,14 @@ data in the browser for ease of debugging.
|
|
|
342
339
|
|
|
343
340
|
### `Battle`
|
|
344
341
|
|
|
345
|
-
| Start | End | Data | Description
|
|
346
|
-
| ------- | ------- | ------------------- |
|
|
347
|
-
| 0 | 184 | [`sides[0]`](#side) | Player 1's side
|
|
348
|
-
| 184 | 368 | [`sides[1]`](#side) | Player 2's side
|
|
349
|
-
| 368 | 370 | `turn` | The current turn number
|
|
350
|
-
| 370 | 372 | `last_damage` | The last damage dealt by either side
|
|
351
|
-
| 372 |
|
|
352
|
-
|
|
|
342
|
+
| Start | End | Data | Description |
|
|
343
|
+
| ------- | ------- | ------------------- | ------------------------------------------------------ |
|
|
344
|
+
| 0 | 184 | [`sides[0]`](#side) | Player 1's side |
|
|
345
|
+
| 184 | 368 | [`sides[1]`](#side) | Player 2's side |
|
|
346
|
+
| 368 | 370 | `turn` | The current turn number |
|
|
347
|
+
| 370 | 372 | `last_damage` | The last damage dealt by either side |
|
|
348
|
+
| 372 | 374-376 | `last_moves` | Details about the last move selected/used by each side |
|
|
349
|
+
| 374-376 | 384 | `rng` | The RNG state |
|
|
353
350
|
|
|
354
351
|
- the current `turn` is 2 bytes, written in native-endianness
|
|
355
352
|
- `last_moves` layout depends on whether or not Pokémon Showdown compatibility mode is enabled
|
|
@@ -359,13 +356,13 @@ data in the browser for ease of debugging.
|
|
|
359
356
|
- if `showdown` is enabled byte 372 is used to store the last selected move index of `side[0]` and
|
|
360
357
|
byte 373 is used to store whether or not the side's last executed move would have been considered
|
|
361
358
|
"counterable"; and bytes 374 and 375 store the same information for `side[1]`
|
|
362
|
-
- otherwise the first
|
|
363
|
-
next
|
|
364
|
-
with the same information for `side[1]`'s being stored in the second
|
|
359
|
+
- otherwise the first 4 bits of byte 372 stores the last selected move index of `side[0]` and the
|
|
360
|
+
next 4 bits store whether that side's last executed move would have been considered
|
|
361
|
+
"counterable", with the same information for `side[1]`'s being stored in the second byte
|
|
365
362
|
- the `rng` depends on whether or not Pokémon Showdown compatibility mode is enabled (`-Dshowdown`):
|
|
366
363
|
- if `showdown` is enabled, the RNG state begins on byte 376 and consists of a 64-bit seed,
|
|
367
364
|
written in native-endianness
|
|
368
|
-
- otherwise the RNG state begins on byte
|
|
365
|
+
- otherwise the RNG state begins on byte 374 and consists of the 9 bytes of the seed followed by
|
|
369
366
|
the index pointing to which byte of the seed is currently being used
|
|
370
367
|
|
|
371
368
|
### `Side`
|
|
@@ -384,7 +381,7 @@ data in the browser for ease of debugging.
|
|
|
384
381
|
| 183 | 184 | `last_used_move` | The last move the player used |
|
|
385
382
|
|
|
386
383
|
- `order` is a 6 byte array where `order[i]` represents the "slot" of `pokemon[i]`, where the slot
|
|
387
|
-
is usually a unique value from 1
|
|
384
|
+
is usually a unique value from 1 to 6 but can be 0 in situations where a player brings less than six
|
|
388
385
|
Pokémon to battle (note however that if `order[i]` is 0 than for all j > 0 `order[i+j]` **must**
|
|
389
386
|
equal 0).
|
|
390
387
|
|
|
@@ -411,7 +408,7 @@ data in the browser for ease of debugging.
|
|
|
411
408
|
| 28 | 29 | `moves[2].id` | The active Pokémon's third move |
|
|
412
409
|
| 29 | 30 | `moves[2].pp` | The PP of the active Pokémon's third move |
|
|
413
410
|
| 30 | 31 | `moves[3].id` | The active Pokémon's fourth move |
|
|
414
|
-
| 31 | 32 | `moves[3].pp` | The PP of the active Pokémon's fourth
|
|
411
|
+
| 31 | 32 | `moves[3].pp` | The PP of the active Pokémon's fourth move |
|
|
415
412
|
|
|
416
413
|
- the active Pokémon's `stats.hp` is always identical to the corresponding stored Pokémon's `stats.hp`
|
|
417
414
|
- `boosts` and `types` includes bytes which store two 4-bit fields each
|
|
@@ -446,7 +443,7 @@ data in the browser for ease of debugging.
|
|
|
446
443
|
| 40 | 48 | `substitute` | The remaining HP of the Substitute |
|
|
447
444
|
| 48 | 52 | `transform` | The identity of whom the active Pokémon is transformed into |
|
|
448
445
|
| 52 | 56 | `disabled_duration` | The remaining turns the move is disabled |
|
|
449
|
-
| 56 | 59 | `disabled_move` | The move slot (1-4) that
|
|
446
|
+
| 56 | 59 | `disabled_move` | The move slot (1-4) that's disabled |
|
|
450
447
|
| 59 | 64 | `toxic` | The number of turns toxic damage has been accumulating |
|
|
451
448
|
|
|
452
449
|
### `Pokemon`
|
|
@@ -480,29 +477,31 @@ Smogon](https://www.smogon.com/forums/posts/5933177/show):
|
|
|
480
477
|
|
|
481
478
|
- moves on Pokémon Showdown can do 0 damage instead of failing or causing a [division-by-zero
|
|
482
479
|
freeze](https://pkmn.cc/bulba-glitch-1#Division_by_0).
|
|
483
|
-
- Pokémon Showdown
|
|
480
|
+
- Pokémon Showdown doesn't implement type effectiveness precedence correctly.
|
|
484
481
|
- Pokémon Showdown checks for type and OHKO immunity before accuracy.
|
|
485
482
|
- Confusion self-hits use the wrong damage formula resulting in off-by-one errors (and also fail to
|
|
486
|
-
account for an opponent's Reflect). Furthermore, the confusion self-hit damage
|
|
487
|
-
inflicted
|
|
483
|
+
account for an opponent's Reflect). Furthermore, the confusion self-hit damage erroneously gets
|
|
484
|
+
inflicted on the confused user's substitute if the confused user had been attempting to use a
|
|
488
485
|
self-targeting move. Finally, Pokémon Showdown erroneously considers the *uncapped* self-hit
|
|
489
486
|
damage for the purposes of tracking the battle's last damage.
|
|
490
487
|
|
|
491
|
-
Beyond
|
|
492
|
-
|
|
493
|
-
|
|
488
|
+
Beyond these general bugs, several move effects are implemented incorrectly by Pokémon Showdown.
|
|
489
|
+
Some of these moves are [too fundamentally broken to be implemented](#unimplementable) by the pkmn
|
|
490
|
+
engine, but the following moves have their broken behavior preserved in `-Dshowdown` mode:
|
|
494
491
|
|
|
495
492
|
- **Bide**: Bide's damage can overflow if OHKO moves are involved because OHKO moves work by setting
|
|
496
493
|
the damage to 65535 - Pokémon Showdown doesn't implement this overflow and instead lets Bide's
|
|
497
494
|
damage grow unbounded. Additionally, if the opponent faints after Bide inflicts damage on Pokémon
|
|
498
495
|
Showdown residual damage incorrectly still gets applied to Bide's user.
|
|
499
|
-
- **Counter**: On Pokémon Showdown choices made while sleeping (which
|
|
500
|
-
registered) can erroneously cause Counter to trigger Desync Clause Mod behavior.
|
|
496
|
+
- **Counter**: On Pokémon Showdown choices made while sleeping (which shouldn't have been
|
|
497
|
+
registered) can erroneously cause Counter to trigger Desync Clause Mod behavior. Additionally,
|
|
498
|
+
because Pokémon Showdown neglects to zero the last battle damage if a move misses due to immunity
|
|
499
|
+
or invulnerability Counter occasionally works on Pokémon Showdown when it should fail.
|
|
501
500
|
- **Leech Seed**: Leech Seed fails to heal its source side if a seeded target faints due to
|
|
502
501
|
recoil/crash damage on Pokémon Showdown.
|
|
503
502
|
- **Pay Day**: Pay Day should still scatter coins if it hits (but doesn't break) and opponent's
|
|
504
503
|
Substitute but doesn't on Pokémon Showdown.
|
|
505
|
-
- **Flinch**: Flinching
|
|
504
|
+
- **Flinch**: Flinching doesn't get cleared during move selection on Pokémon Showdown and is
|
|
506
505
|
instead cleared in Pokémon Showdown's "residual" phase, meaning the flinch status gets erroneously
|
|
507
506
|
preserved across fainting (as fainting triggers Pokémon Showdown's "instaswitch" behavior which
|
|
508
507
|
skips end-of-turn residuals).
|
|
@@ -513,45 +512,45 @@ the pkmn engine, but the following moves have their broken behavior preserved in
|
|
|
513
512
|
Petal Dance accuracy bug]( https://www.youtube.com/watch?v=NC5gbJeExbs) incorrectly, as the
|
|
514
513
|
accuracy only gets written when the volatile is present which can lead to incorrect accuracy
|
|
515
514
|
values on turns where Rage is used on the same turn as a move which modifies accuracy or evasion.
|
|
516
|
-
- **Thrash** / **Petal Dance**: once a Pokémon is locked into a thrashing move they
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
515
|
+
- **Thrash** / **Petal Dance**: once a Pokémon is locked into a thrashing move they lose all speed
|
|
516
|
+
ties on Pokémon Showdown (due to the fact that if they win the speed tie their action then gets
|
|
517
|
+
"changed" and inserted back into the queue after their opponent's action). Pokémon Showdown also
|
|
518
|
+
implements accuracy incorrectly, as covered earlier.
|
|
520
519
|
- **Freeze** / **Sleep**: Pokémon Showdown requires a move to be selected when a Pokémon is frozen
|
|
521
520
|
or sleeping and uses that in the event that the status is removed while on the cartridge no
|
|
522
521
|
selection is possible and no turn exists for the thawed/woken Pokémon to act except in the case of
|
|
523
522
|
a Fire-type move thawing a slower Pokémon (which should result in the [Freeze top move selection
|
|
524
|
-
glitch](https://glitchcity.wiki/Freeze_top_move_selection_glitch), which
|
|
523
|
+
glitch](https://glitchcity.wiki/Freeze_top_move_selection_glitch), which isn't implemented and
|
|
525
524
|
would also likely be incorrect if it were to be implemented due to how Pokémon Showdown
|
|
526
525
|
incorrectly saves arbitrary moves with its choice selection semantics). Furthermore, thrashing
|
|
527
|
-
volatiles
|
|
526
|
+
volatiles shouldn't be cleared if the user misses a turn due to freeze / sleep.
|
|
528
527
|
- **Hyper Beam**: due to improperly implemented selection mechanics, the [Hyper Beam
|
|
529
528
|
automatic-selection glitch](https://glitchcity.wiki/Hyper_Beam_automatic_selection_glitch) does
|
|
530
529
|
not exist on Pokémon Showdown. Additionally, Hyper Beam being able to cause [Freeze permanent
|
|
531
|
-
helplessness](https://pkmn.cc/bulba-glitch-1#Hyper_Beam_.2B_Freeze_permanent_helplessness)
|
|
530
|
+
helplessness](https://pkmn.cc/bulba-glitch-1#Hyper_Beam_.2B_Freeze_permanent_helplessness) isn't
|
|
532
531
|
implement by Pokémon Showdown.
|
|
533
532
|
- **Roar** / **Whirlwind**: these moves can miss on Pokémon Showdown (and advance the RNG when
|
|
534
|
-
checking) which is incorrect (these moves should always fail, but
|
|
533
|
+
checking) which is incorrect (these moves should always fail, but don't check accuracy or advance
|
|
535
534
|
the RNG). More importantly, these moves should *not* cause the tracked last battle damage to be
|
|
536
535
|
zeroed, but on Pokémon Showdown they do.
|
|
537
536
|
- **Substitute**: in addition to the [Substitute + Confusion
|
|
538
537
|
glitch](https://pkmn.cc/bulba-glitch-1#Substitute_.2B_Confusion_glitch) not being implemented
|
|
539
|
-
correctly (covered
|
|
538
|
+
correctly (covered earlier), the [Substitute 1/4
|
|
540
539
|
glitch](https://glitchcity.wiki/Substitute_%C2%BC_HP_glitch) also fails in many cases due to
|
|
541
540
|
Pokémon Showdown implementing the health check based on floating point division instead of integer
|
|
542
|
-
|
|
541
|
+
division like on the cartridge (meaning the Substitute 1/4 glitch only occurs if the Pokémon's
|
|
543
542
|
maximum HP is evenly divisible by 4). Substitute also incorrectly blocks Dream Eater on
|
|
544
|
-
Pokémon Showdown and
|
|
543
|
+
Pokémon Showdown and incorrectly still heals 1 HP for any draining moves if the attack does 0
|
|
545
544
|
damage. Finally, Pokémon Showdown uses a `subFainted` field to track whether a Substitute was
|
|
546
|
-
broken
|
|
545
|
+
broken to know when to nullify a move's effect, only it doesn't get cleared at the end of
|
|
547
546
|
the turn and can result in incorrect behavior on subsequent turns with the moves Mirror Move and
|
|
548
547
|
Metronome that invoke `runMove` (which is where `subFainted` gets cleared) on the user but skips
|
|
549
548
|
calling it for the eventual true target.
|
|
550
549
|
|
|
551
550
|
In addition to numerous cases where Pokémon Showdown uses the wrong type of message (e.g. `|-fail`
|
|
552
|
-
vs. `|-miss|` vs. `|-immune|`, e.g. in the case of Leech Seed) which
|
|
553
|
-
Showdown sometimes gets message ordering incorrect (which
|
|
554
|
-
|
|
551
|
+
vs. `|-miss|` vs. `|-immune|`, e.g. in the case of Leech Seed) which aren't documented here, Pokémon
|
|
552
|
+
Showdown sometimes gets message ordering incorrect (which doesn't effect the outcome of the battle,
|
|
553
|
+
only pedantic UI correctness):
|
|
555
554
|
|
|
556
555
|
- **Rage**: Rage should report the `|-boost|` before the Disable `|-miss|`, not after.
|
|
557
556
|
- **Haze**: Haze clears the status/volatiles in an incorrect order.
|
|
@@ -564,21 +563,21 @@ Pokémon Showdown also implements a number of modifications to the cartridge (us
|
|
|
564
563
|
called out in the `|rule|` section at the beginning of a battle's log):
|
|
565
564
|
|
|
566
565
|
- **Sleep Clause Mod**: players are prevented from putting more than one of their opponent's Pokémon
|
|
567
|
-
to sleep at a time (usage of the move
|
|
566
|
+
to sleep at a time (usage of the move fails).
|
|
568
567
|
- **Freeze Clause Mod**: players are prevented from freezing more than one of their opponent's
|
|
569
|
-
Pokémon at a time (usage of the move
|
|
570
|
-
- **Desync Clause Mod**: If the usage of a move would cause a desync it instead causes a
|
|
571
|
-
|
|
568
|
+
Pokémon at a time (usage of the move fails).
|
|
569
|
+
- **Desync Clause Mod**: If the usage of a move would cause a desync it instead causes a failure.
|
|
570
|
+
However, this mod doesn't trigger for [**division by
|
|
572
571
|
zero**](https://pkmn.cc/bulba/List_of_glitches_(Generation_I)#Division_by_0) - instead of failing,
|
|
573
572
|
Pokémon Showdown silently patches the damage calculation to divide by 1 instead of 0. The
|
|
574
573
|
definition of the Desync Clause Mod should be extended or the code should be changed to fail
|
|
575
574
|
instead of succeeding in these cases.
|
|
576
575
|
- **Endless Battle Clause**: Prematurely ends the battle in a tie after 1000 turns or in certain
|
|
577
|
-
situations where it
|
|
576
|
+
situations where it's trivially detectable that no progress can be made.
|
|
578
577
|
- **Switch Priority Clause Mod**: When both player switch out their Pokémon at the same time the
|
|
579
578
|
faster Pokémon switches first. This mod is **not broadcast at the start of the battle in the
|
|
580
|
-
`|rule|` section** in Generation I (or II) as the actual order of switches here
|
|
581
|
-
competitive implications like it does in Generation III, but it
|
|
579
|
+
`|rule|` section** in Generation I (or II) as the actual order of switches here doesn't have
|
|
580
|
+
competitive implications like it does in Generation III, but it's still contrary to how the games
|
|
582
581
|
work where the [host (Player 1)'s Pokemon would switch
|
|
583
582
|
first](https://www.smogon.com/forums/posts/5989318/show).
|
|
584
583
|
|
|
@@ -620,35 +619,35 @@ the correct control flow):
|
|
|
620
619
|
Pokémon using Rage/Bide or Thrashing/Charging moves being incorrectly forced to skip a turn when
|
|
621
620
|
the Pokémon using the Binding move switches.
|
|
622
621
|
- **Mimic**: Pokémon Showdown checks that the user of Mimic has Mimic in one of their move slots,
|
|
623
|
-
which means Mimic legally called via Metronome or Mirror Move
|
|
624
|
-
Mimic (and the moved mimicked by Mimic called via Metronome / Mirror Move
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
- **Mirror Move**: As covered
|
|
632
|
-
Mirror Move (though Pokémon Showdown has its own weird behavior and
|
|
633
|
-
|
|
622
|
+
which means Mimic legally called via Metronome or Mirror Move only works if the user also has
|
|
623
|
+
Mimic (and the moved mimicked by Mimic called via Metronome / Mirror Move erroneously overrides
|
|
624
|
+
the Mimic move slot instead of the Metronome / Mirror Move move slot). Furthermore, because
|
|
625
|
+
Pokémon Showdown deducts PP based on a move's name instead of slot, if Mimic copies a move the
|
|
626
|
+
Pokémon already knows, PP deduction for using either the original move of the mimicked move
|
|
627
|
+
instead deducts PP for whichever appears at the lower move slot index and the PP is allowed to go
|
|
628
|
+
negative (effectively allowing for infinite PP use). Pokémon Showdown also doesn't deduct PP from
|
|
629
|
+
using Transform if it was copied by Mimic.
|
|
630
|
+
- **Mirror Move**: As covered earlier, both Substitute and Binding moves misbehave when used via
|
|
631
|
+
Mirror Move (though Pokémon Showdown has its own weird behavior and doesn't implement the [partial
|
|
632
|
+
trapping move Mirror Move
|
|
634
633
|
glitch](https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch) that exists
|
|
635
634
|
on the cartridge). Additionally, Pokémon Showdown sets the last used move every turn a Pokémon is
|
|
636
|
-
Thrashing instead of just on the turn it
|
|
637
|
-
successfully
|
|
638
|
-
|
|
635
|
+
Thrashing instead of just on the turn it's actually selected meaning Mirror Move sometimes
|
|
636
|
+
successfully mirrors a Thrashing move when it should fail. Finally, if Mirror Move copies Struggle
|
|
637
|
+
it shouldn't deduct PP but on Pokémon Showdown it does.
|
|
639
638
|
- **Metronome**: In addition to the issues with binding moves and Substitute, Metronome and Mirror
|
|
640
|
-
Move
|
|
641
|
-
Showdown simulator to crash due to defensive safety checks that
|
|
639
|
+
Move can't mutually call each other more than 3 times in a row without causing the Pokémon
|
|
640
|
+
Showdown simulator to crash due to defensive safety checks that don't exist on the cartridge.
|
|
642
641
|
- **Transform**: Transform screws up the effect of Disable, because on Pokémon Showdown, Disable
|
|
643
642
|
prevents moves of a given *name* from being used (e.g. "Water Gun") as opposed to moves in a
|
|
644
|
-
specific *slot* (e.g. the
|
|
645
|
-
|
|
646
|
-
also has a bad interaction with Pokémon Showdown's buggy Haze implementation, as Haze on
|
|
647
|
-
Showdown
|
|
648
|
-
Furthermore, transforming and then using Mirror Move / Metronome can result in [glitchy
|
|
649
|
-
and
|
|
643
|
+
specific *slot* (e.g. the second move slot), and a Pokémon's moves can change after Transform
|
|
644
|
+
(this isn't an issue with Disable + Mimic because Mimic happens to replace the same slot).
|
|
645
|
+
Transform also has a bad interaction with Pokémon Showdown's buggy Haze implementation, as Haze on
|
|
646
|
+
Pokémon Showdown doesn't copy unmodified stats, leaving the Transformed Pokémon with incorrect
|
|
647
|
+
stats. Furthermore, transforming and then using Mirror Move / Metronome can result in [glitchy
|
|
648
|
+
behavior and
|
|
650
649
|
softlocks](https://pkmn.cc/bulba/Transform_glitches#Transform_.2B_Mirror_Move.2FMetronome_PP_error)
|
|
651
|
-
which Pokémon Showdown
|
|
650
|
+
which Pokémon Showdown doesn't implement.
|
|
652
651
|
|
|
653
652
|
Importantly, Pokémon Showdown's **speed tie RNG mechanics are unimplementable** in the pkmn engine
|
|
654
653
|
as they rely on internal implementation decisions made by Pokémon Showdown that are impossible to
|
|
@@ -659,13 +658,13 @@ below).
|
|
|
659
658
|
|
|
660
659
|
**The pkmn engine aims to match the cartridge's RNG frame-accurately**, in that provided with the
|
|
661
660
|
same initial seed and inputs it should produce the same battle playout as the Pokémon Red cartridge.
|
|
662
|
-
**Pokémon Showdown
|
|
663
|
-
with the [bugs](#bugs) discussed
|
|
661
|
+
**Pokémon Showdown doesn't correctly implement frame-accurate RNG in any generation**, and along
|
|
662
|
+
with the [bugs](#bugs) discussed earlier this results in large differences in the codebase. Because
|
|
664
663
|
the pkmn engine aims to be as compatible with Pokémon Showdown as possible when in `-Dshowdown`
|
|
665
664
|
compatibility mode, the implications of these differences are outlined below:
|
|
666
665
|
|
|
667
666
|
- **RNG**: Pokémon Showdown uses the RNG from Generation V & VI in every generation, despite
|
|
668
|
-
the seeds and algorithm being different. Pokémon Red uses a simple 8-bit RNG with
|
|
667
|
+
the seeds and algorithm being different. Pokémon Red uses a simple 8-bit RNG with 9 distinct
|
|
669
668
|
seeds generated when the link connection is established, whereas Pokémon Showdown uses a 64-bit
|
|
670
669
|
RNG with a 32-bit output.
|
|
671
670
|
- **Algorithm**: As detailed in the table below, the algorithm used by Pokémon Showdown in the
|
|
@@ -678,15 +677,15 @@ compatibility mode, the implications of these differences are outlined below:
|
|
|
678
677
|
sampling to ensure uniformity as is done on the cartridge. This means that certain values are
|
|
679
678
|
fractionally more likely to be chosen than others, though this bias is usually quite small (e.g.
|
|
680
679
|
in the case of Metronome instead of selecting moves with an equal $1\over163$ chance, Pokémon
|
|
681
|
-
Showdown
|
|
680
|
+
Showdown selects some with a $1\over2^{32}$ greater chance than others).
|
|
682
681
|
- **Order of operations**: RNG calls effectively introduce something similar to a ["memory
|
|
683
682
|
barrier"](https://en.wikipedia.org/wiki/Memory_barrier) in that they must be sequenced correctly
|
|
684
683
|
(though operations which occur between them may happen in any order). Pokémon Showdown violates
|
|
685
684
|
this by introducing additional operations (see below) and changing up the order of existing
|
|
686
685
|
operations (e.g. choosing to check for hit/miss, determine the number of hits for moves with
|
|
687
686
|
multiple hits, determine if a move hit critically and then the damage instead of checking for
|
|
688
|
-
hit/miss and determining number of hits *after* the other two). While it
|
|
689
|
-
rearrange code for performance or to improve readability this can only be done if it
|
|
687
|
+
hit/miss and determining number of hits *after* the other two). While it's often desirable to
|
|
688
|
+
rearrange code for performance or to improve readability this can only be done if it doesn't
|
|
690
689
|
affect accuracy.
|
|
691
690
|
- **Speed-ties**: In addition to [breaking switch-in speed ties with an RNG
|
|
692
691
|
call](https://www.smogon.com/forums/threads/adv-switch-priority.3622189/), speed ties in Pokémon
|
|
@@ -697,16 +696,16 @@ compatibility mode, the implications of these differences are outlined below:
|
|
|
697
696
|
ties proves impossible, and thus the reference Pokémon Showdown implementation pkmn engine aims to
|
|
698
697
|
match has been [patched](../../../docs/TESTING.md#patches) to change this behavior.
|
|
699
698
|
- **Effects**: Pokémon Showdown occasionally incorrectly inserts RNG calls in move effect handlers
|
|
700
|
-
when they
|
|
699
|
+
when they're not relevant:
|
|
701
700
|
- Roar / Whirlwind roll to hit and can "miss" as opposed to simply failing
|
|
702
701
|
|
|
703
|
-
Finally, the **initial
|
|
702
|
+
Finally, the **initial 9-byte seed for link battles on Pokémon Red can't include bytes larger than
|
|
704
703
|
the `SERIAL_PREAMBLE_BYTE`**, so must be in the range $\left[0, 252\right]$. This has implications
|
|
705
|
-
on the first
|
|
704
|
+
on the first 9 random numbers generated during the battle and has [**non-trivial competitive
|
|
706
705
|
implications**](https://www.smogon.com/forums/posts/9068411/show) (at the start of the battle move
|
|
707
706
|
effects become more likely, the ["1/256-miss" glitch](https://glitchcity.wiki/1/256_miss_glitch)
|
|
708
|
-
|
|
709
|
-
|
|
707
|
+
can't happen, Player 1 is more likely to win speed ties, etc) that Pokémon Showdown can't replicate
|
|
708
|
+
due to everything described earlier.
|
|
710
709
|
|
|
711
710
|
All of places in the link battle code where randomness is required are outlined below:
|
|
712
711
|
|
|
@@ -726,19 +725,19 @@ All of places in the link battle code where randomness is required are outlined
|
|
|
726
725
|
| **Paralysis** (full) | `beforeMove` | Trigger if $X < 63$ |
|
|
727
726
|
| **Poison** (chance) | `Effects.poison` | Trigger if $X < 52$ ($103$ for Smog / Sludge) |
|
|
728
727
|
| **Sleep** (duration) | `Effects.sleep` | <dl><dt>Pokémon Red</dt><dd>Continue generating until $X \land 7 \neq 0$</dd><dt>Pokémon Showdown</dt><dd>Generate $X \in \left[1, 8\right)$</dd></dl> |
|
|
729
|
-
| **Bide** (duration) | `Effects.bide` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 1\right) + 2$ turns</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \left[2, 4\right)$ turns</dd></dl>
|
|
728
|
+
| **Bide** (duration) | `Effects.bide` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 1\right) + 2$ turns</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \left[2, 4\right)$ turns</dd></dl> |
|
|
730
729
|
| **Disable** (move) | `Effects.disable` | <dl><dt>Pokémon Red</dt><dd>Continue generating until $X \land 3$ is the index of a non-empty move slot</dd><dt>Pokémon Showdown</dt><dd>Generate $X \in \left[0, \|movesSlots\|\right)$</dd></dl> |
|
|
731
730
|
| **Disable** (duration) | `Effects.disable` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 7\right) + 1$ turns</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \left[1, 9\right)$ turns</dd></dl> |
|
|
732
|
-
| **Metronome** (move) | `metronome` | <dl><dt>Pokémon Red</dt><dd>Continue generating until $X$ matches the index of a move which
|
|
731
|
+
| **Metronome** (move) | `metronome` | <dl><dt>Pokémon Red</dt><dd>Continue generating until $X$ matches the index of a move which isn't Struggle or Metronome</dd><dt>Pokémon Showdown</dt><dd>Generate $X \in \left[0, \|validMoves\|\right)$, where $validMoves$ is ordered set of moves with Struggle and Metronome removed <i>(indexes of moves > Metronome are shifted down)</i></dd></dl> |
|
|
733
732
|
| **Mimic** (move) | `Effects.mimic` | <dl><dt>Pokémon Red</dt><dd>Continue generating until $X \land 3$ is the index of a non-empty move slot</dd><dt>Pokémon Showdown</dt><dd>Generate $X \in \left[0, \|movesSlots\|\right)$</dd></dl> |
|
|
734
733
|
| **Psywave** (power) | `specialDamage` | <dl><dt>Pokémon Red</dt><dd>Continue generating until $X < {3\over2} \cdot level$</dd><dt>Pokémon Showdown</dt><dd>Generate $X \in \left[0, {3\over2} \cdot level\right)$</dd></dl> |
|
|
735
734
|
| **Thrash** (rampage) | `Effects.thrash` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 1\right) + 2$ turns</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \left[2, 4\right)$ turns</dd></dl> |
|
|
736
735
|
| **Thrash** (confusion) | `beforeMove` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 3\right) + 2$ turns</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \left[2, 6\right)$ turns</dd></dl> |
|
|
737
|
-
| **Binding** (duration) | `Effects.binding` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 3\right) + 1$ further turns if $\left(X \land 3\right) < 2$<br /> otherwise last for $\left(Y \land 3\right) + 1$ further turns (reroll)</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \{2, 2, 3, 3, 4, 5\} - 1$ further turns</dd></dl> |
|
|
736
|
+
| **Binding** (duration) | `Effects.binding` | <dl><dt>Pokémon Red</dt><dd>Last for $\left(X \land 3\right) + 1$ further turns if $\left(X \land 3\right) < 2$<br /> otherwise last for $\left(Y \land 3\right) + 1$ further turns (reroll)</dd><dt>Pokémon Showdown</dt><dd>Last for $X \in \{2, 2, 2, 3, 3, 3, 4, 5\} - 1$ further turns</dd></dl> |
|
|
738
737
|
| **Multi-Hit** (hits) | `Effects.multiHit` | *Ibid.* |
|
|
739
738
|
| **Unboost** (chance) | `Effects.unboost` | Trigger if $X < 85$ |
|
|
740
739
|
|
|
741
|
-
*In the table
|
|
740
|
+
*In the preceding table,* $X$ *is always in the range* $\left[0, 256\right)$ *for Pokémon Red and for
|
|
742
741
|
Pokémon Showdown is always **scaled** from the 32-bit output range of the RNG to either be in that
|
|
743
742
|
range or to whichever range is specified by the description.*
|
|
744
743
|
|
|
@@ -781,7 +780,8 @@ table provides a rough mapping between pkmn and Pokémon Red methods:
|
|
|
781
780
|
| `handleResidual` | `HandlePoisonBurnLeechSeed` |
|
|
782
781
|
| `endTurn` / `checkEBC` | - |
|
|
783
782
|
|
|
784
|
-
Pokémon Red groups move effect handlers together into several groups, these have also been renamed
|
|
783
|
+
Pokémon Red groups move effect handlers together into several groups, these have also been renamed
|
|
784
|
+
for clarification in the pkmn engine:
|
|
785
785
|
|
|
786
786
|
| pkmn | Pokémon Red (pret) |
|
|
787
787
|
| --------------- | ------------------------- |
|
|
@@ -797,7 +797,7 @@ The pkmn engine attempts to adhere to certain naming conventions:
|
|
|
797
797
|
- **`p1`** & **`p2`** always correspond to the `Side` of `Player.P1` and `Player.P2` respectively
|
|
798
798
|
- **`player`** is the executing turn `Player` (`hWhoseTurn`) and **`player.foe()`** is their
|
|
799
799
|
opponent
|
|
800
|
-
- if `player` (executing turn player) is present then **`side`**
|
|
800
|
+
- if `player` (executing turn player) is present then **`side`** always corresponds to their
|
|
801
801
|
`Side` and **`foe`** the opposing `Side`, however `side` can refer to an arbitrary `Side` object
|
|
802
802
|
if there is no `player` in scope (e.g. in helper functions)
|
|
803
803
|
- **`target_player`** is a target `Player` whose corresponding `Side` is **`target`**
|