Repository: zaksabeast/PokemonRNGGuides
Pull request #281: TS for transporter generator
Author: login: sorendog
Created: 2025-05-04T22:25:53Z | Merged: 2025-05-08T18:17:46Z

add files for typescript connections to add transporter 


Diff:
diff --git a/guides/Transporter/Transporter.mdx b/guides/Transporter/Transporter.mdx
index fde35e41d8..3bd2568ec6 100644
--- a/guides/Transporter/Transporter.mdx
+++ b/guides/Transporter/Transporter.mdx
@@ -10,7 +10,6 @@ tag: "cfw"
 ## Tools
 
 - [A 3DS with PokeReader](/install-pokereader)
-- [3DSRNGTool](https://github.com/wwwwwwzx/3DSRNGTool/releases)
 
 ## Useful note
 
@@ -24,63 +23,51 @@ RNGing with Transporter has a delay between pressing `A` and Pokémon generation
 
 1. Make sure you have at least one Pokémon in Box 1, Slot 1 of your Gen 1/2 game.
 1. Load Pokémon Transporter with PokeReader on your 3DS.
-1. Open 3DSRNGTool on your computer.
-1. Change 3DSRNGTool's "Game Version" to "Transporter."
-1. Change 3DSRNGTool's "Seed" to the Initial Seed PokeReader shows.
-1. Change 3DSRNGTool's tab to "Stationary RNG."
+1. Enter your Initial seed in the tool below.
 1. In Transporter, press `Start` to select the game for Transporter.
 1. Press `Start + Select` to pause the game.
 1. Note your current frame; this is your starting frame.
 1. Press `A` to unpause and let Transporter generate the Pokémon.
-1. Search for the exact IVs PokeReader indicates using 3DSRNGTool to find your hit frame.
+1. Search for the exact IVs PokeReader indicates using the tool below to find your hit frame.
 1. Subtract your hit frame from your starting frame to get your delay.
    - Example: If you started on frame 1000 and hit frame 1197, your delay is 197.
 1. When asked to transfer the Pokémon, click "No."
 1. Repeat the process 5-10 times to get 5-10 delays.
-1. Input the most frequent delay or an average into 3DSRNGTool's "Delay" box.
+1. Input the most frequent delay or an average into the "Delay" box.
 
 ## Step 2: Find a spread to RNG
 
-Follow these steps in 3DSRNGTool to find a desired spread:
+Follow these steps with the tool below to find a desired spread:
 
-1. Change the "Gender Ratio" to match the Pokémon you want to RNG.
-1. Change the "Index of Target Pkm" to "1."
-1. Change only the gender and IVs to find a spread you want.
+1. Change the "Transporter Gender" to match the Pokémon you want to RNG.
+1. Change IVs to find a spread you want.
    - Nature, ability, and shininess are determined in the Gen 1/2 game before Transporter.
-1. Change the first "Frame Range" box to your current frame.
-1. Change the second "Frame Range" box to the maximum frame you want to search.
-1. Click "Search."
+1. Change the "Initial Advance" box to your current frame.
+1. Change the "Max Advances" box to the maximum frame you want to search.
+1. Click "Generate."
 
-All results are potential wanted frames. Pick your favorite!
+All results are potential wanted advances. Pick your favorite!
 
 ## Step 3: Hit the target
 
 1. Wait at the game selection screen until you get close to your wanted frame.
 1. Once close, press `Start + Select` to pause the game.
-1. Press `Select` to advance the frames one by one until you reach your wanted frame.
+1. Press `Select` to move the advances one by one until you reach your wanted frame.
 1. Once at your wanted frame, press and hold `A`.
 1. Check if you got the Pokémon you wanted; if not, don’t transfer your Pokémon and try again.
 
 This may take several attempts.
 
-## Alternative method with increased chances
-
 Transporter generates 20 Pokémon at once. If you have 20 of the same Pokémon or cloned, you can have 20 chances to RNG the same Pokémon.
 
 1. Catch 20 of the same Pokémon (or clone them).
 1. Make sure all 20 are in Box 1 of your Gen 1/2 game.
 
-In 3DSRNGTool:
+In the tool below:
 
-1. Change the "Index of Target Pkm" to "1."
 1. Change the "Filters" box to have the IVs you want to RNG.
 1. Search for spreads using the initial seed PokeReader shows.
-1. Repeat this, adding 1 to the "Index of Target Pkm" to check the spreads for all 20 Pokémon.
-1. Note the index with the most consecutive frames with spreads you want; this is your target index.
-1. Ensure the number of consecutive frames is at least 10.
-1. Set the "Index of Target Pkm" to your target index.
-1. Get the list of delays from the calibration step.
-1. Look for the highest delay within 10 frames of your most common delay.
-1. In the "RNG Info" box of 3DSRNGTool, change "delay" to this new delay.
-1. Press "Calculate" to find your potential frames with this new delay for your target index.
-1. Find the highest frame among the 10+ consecutive frames from earlier and use it as your target frame.
+1. Ensure the number of consecutive advances is at least 10.
+1. Aim for the middle advance as your target advance.
+
+<Transporter />
diff --git a/rng_tools/src/generators/gen6/stationary.rs b/rng_tools/src/generators/gen6/stationary.rs
index 1e54545c64..0f27b4ad1a 100644
--- a/rng_tools/src/generators/gen6/stationary.rs
+++ b/rng_tools/src/generators/gen6/stationary.rs
@@ -8,6 +8,7 @@ use wasm_bindgen::prelude::*;
 
 #[derive(Debug, Clone, Copy, PartialEq, Tsify, Serialize, Deserialize)]
 #[tsify(into_wasm_abi, from_wasm_abi)]
+#[repr(u8)]
 pub enum TransporterGenderType {
     NoGender = 0,
     RandomGender = 1,
@@ -293,7 +294,7 @@ pub struct TransporterOpts {
 pub fn generate_transporter(opts: TransporterOpts) -> Vec<Stationary6State> {
     let target_gender = opts
         .transporter_genders
-        .get(opts.target.saturating_sub(1))
+        .get(opts.target)
         .map(|target| *target)
         .unwrap_or(TransporterGenderType::RandomGender);
     let ability = match target_gender {
@@ -1417,7 +1418,7 @@ mod test {
                 initial_advances: 0,
                 max_advances: 3,
                 delay: 0,
-                target: 1,
+                target: 0,
                 transporter_genders: vec![TransporterGenderType::Mythical],
                 tsv: 123,
                 filter: Default::default(),
@@ -1498,7 +1499,7 @@ mod test {
 
         #[test]
         fn preceding_fixed_natures() {
-            let opts = TransporterOpts {
+            let opts: TransporterOpts = TransporterOpts {
                 seed: 0xAABBCCDD,
                 initial_advances: 0,
                 max_advances: 3,
diff --git a/src/markdownExports/index.ts b/src/markdownExports/index.ts
index 942414037f..bdbf5d1a9e 100644
--- a/src/markdownExports/index.ts
+++ b/src/markdownExports/index.ts
@@ -41,6 +41,7 @@ import { Gen3MirageIsland } from "~/rngToolsUi/gen3/mirageIsland";
 import { OrAsMirageSpot } from "~/rngToolsUi/gen6/orasMirageSpot";
 import { RngTimer } from "~/rngToolsUi/timer";
 import { OrasId } from "~/rngToolsUi/gen6/orasId";
+import { Transporter } from "~/rngToolsUi/gen6/transporter/transporter";
 import { DpptSeed } from "~/rngToolsUi/gen4/dpptSeed";
 import { DpptId } from "~/rngToolsUi/gen4/dpptId";
 import { XyPokeRadar } from "~/rngToolsUi/gen6/xyPokeRadar";
@@ -96,6 +97,7 @@ export const markdownComponents = {
   Gen4Timer,
   RngTimer,
   OrasId,
+  Transporter,
   DpptSeed,
   DpptId,
   XyPokeRadar,
diff --git a/src/rngToolsUi/gen6/transporter/transporter.tsx b/src/rngToolsUi/gen6/transporter/transporter.tsx
new file mode 100644
index 0000000000..fd6d103bdd
--- /dev/null
+++ b/src/rngToolsUi/gen6/transporter/transporter.tsx
@@ -0,0 +1,172 @@
+import React from "react";
+import {
+  FormikNumberInput,
+  ResultColumn,
+  RngToolForm,
+  RngToolSubmit,
+  Field,
+  FormikSelect,
+} from "~/components";
+import { rngTools, Stationary6State } from "~/rngTools";
+import {
+  flattenIvs,
+  FlattenIvs,
+  ivColumns,
+} from "~/rngToolsUi/shared/ivColumns";
+import { maxIvs, minIvs } from "~/types/ivs";
+import { getPkmFilterFields, pkmFilterSchema } from "~/components/pkmFilter";
+import { z } from "zod";
+import { HexSchema } from "~/utils/number";
+import { toOptions } from "~/utils/options";
+
+const transporterGenders = ["NoGender", "RandomGender", "Mythical"] as const;
+const transporterGenderOptions = toOptions(transporterGenders);
+type Result = FlattenIvs<Stationary6State>;
+
+const columns: ResultColumn<Result>[] = [
+  {
+    title: "Advance",
+    dataIndex: "advance",
+  },
+  {
+    title: "RNG State",
+    dataIndex: "rng_state",
+    monospace: true,
+    render: (seed) => seed.toString(16).toUpperCase().padStart(8, "0"),
+  },
+  {
+    title: "PSV",
+    dataIndex: "psv",
+  },
+  {
+    title: "Shiny",
+    dataIndex: "shiny",
+    render: (shiny) => (shiny ? "Yes" : "No"),
+  },
+  ...ivColumns,
+  {
+    title: "Ability",
+    dataIndex: "ability",
+  },
+  {
+    title: "Nature",
+    dataIndex: "nature",
+  },
+  {
+    title: "Gender",
+    dataIndex: "gender",
+  },
+];
+
+const Validator = z
+  .object({
+    seed: HexSchema(0xffffffff),
+    initial_advances: z.number().int().min(0),
+    max_advances: z.number().int().min(0),
+    delay: z.number().int(),
+    tsv: z.number().int(),
+    only_current_seed: z.boolean(),
+    transporter_genders: z.enum(transporterGenders),
+  })
+  .merge(pkmFilterSchema);
+
+export type FormState = z.infer<typeof Validator>;
+
+const initialValues: FormState = {
+  seed: 0,
+  initial_advances: 20,
+  max_advances: 50,
+  delay: 100,
+  tsv: 0,
+  only_current_seed: false,
+  transporter_genders: "NoGender",
+  filter_shiny: false,
+  filter_min_ivs: minIvs,
+  filter_max_ivs: maxIvs,
+  filter_nature: null,
+  filter_gender: null,
+  filter_ability: null,
+};
+
+const fields: Field[] = [
+  {
+    label: "Seed",
+    input: <FormikNumberInput<FormState> name="seed" numType="hex" />,
+  },
+  {
+    label: "Initial Advance",
+    input: (
+      <FormikNumberInput<FormState> name="initial_advances" numType="decimal" />
+    ),
+  },
+  {
+    label: "Max Advances",
+    input: (
+      <FormikNumberInput<FormState> name="max_advances" numType="decimal" />
+    ),
+  },
+  {
+    label: "Delay",
+    input: <FormikNumberInput<FormState> name="delay" numType="decimal" />,
+  },
+  {
+    label: "TSV",
+    input: <FormikNumberInput<FormState> name="tsv" numType="decimal" />,
+  },
+  {
+    label: "Transporter Gender",
+    input: (
+      <FormikSelect<FormState, "transporter_genders">
+        name="transporter_genders"
+        options={transporterGenderOptions}
+      />
+    ),
+  },
+  ...getPkmFilterFields({
+    ivs: true,
+    ability: false,
+    gender: false,
+    nature: false,
+    shiny: false,
+  }),
+];
+
+export const Transporter = () => {
+  const [results, setResults] = React.useState<Result[]>([]);
+
+  const onSubmit = React.useCallback<RngToolSubmit<FormState>>(async (opts) => {
+    const results = await rngTools.generate_transporter({
+      seed: opts.seed,
+      initial_advances: opts.initial_advances,
+      max_advances: opts.max_advances,
+      delay: opts.delay,
+      target: 0,
+      transporter_genders: [opts.transporter_genders],
+      tsv: opts.tsv,
+      filter: {
+        gender: null,
+        ability: null,
+        nature: null,
+        ivs: {
+          min_ivs: opts.filter_min_ivs,
+          max_ivs: opts.filter_max_ivs,
+        },
+        shiny: false,
+      },
+    });
+
+    setResults(results.map(flattenIvs));
+  }, []);
+
+  return (
+    <RngToolForm<FormState, Result>
+      fields={fields}
+      columns={columns}
+      results={results}
+      validationSchema={Validator}
+      initialValues={initialValues}
+      onSubmit={onSubmit}
+      submitTrackerId="generate_transporter"
+    />
+  );
+};


Reviews:
user: login: sorendog
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T22:27:21Z
user: login: zaksabeast
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-05T00:52:55Z
user: login: sorendog
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T02:38:20Z
user: login: zaksabeast
body: Super close!  Just a few small tweaks.
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-05-05T15:41:39Z
user: login: zaksabeast
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-05T15:41:53Z
user: login: sorendog
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T01:12:53Z
user: login: sorendog
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T01:51:57Z
user: login: zaksabeast
body: One tiny tweak, but otherwise this is ready from a code perspective.  I'll let @ShinySylveon04 take a look at the guide.
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-05-06T15:15:22Z
user: login: ShinySylveon04
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T15:18:55Z
user: login: zaksabeast
body: Thank you!  This is great!

Looks good from a code perspective - I'll wait for @ShinySylveon04's guide approval before merging.
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-08T13:58:20Z
user: login: ShinySylveon04
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-08T16:02:54Z

Review comments:
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: sorendog
body: on line 120-ish is where the error i get comes from when i run bun run lint
created_at: 2025-05-04T22:27:21Z
author_association: CONTRIBUTOR
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: zaksabeast
body: You're mostly there!

**Here's the issue:**

`transporter_genders` is an array of genders, but `FormikSelect` is a single dropdown that chooses a single gender.  The type mismatch is because `TransporterGender` doesn't equal `TransporterGender[]`.

---

**Why do we need a list of genders?**

3DSRNGTool let's people select multiple genders, because different genders take a different number of advances to generate:
<img width="369" alt="Screenshot 2025-05-04 at 7 35 13 PM" src="https://github.com/user-attachments/assets/f7b05dba-2c97-4750-bfb6-84af83e83cce" />

So if the user has a Pokemon in slot 3, we need to know the genders leading up to that Pokemon.

---

**What's the solution?**

For this web tool, we have two options:
- Use a `FormikInput` and allow people to type a list of characters like what 3DSRNGTool has
- Assume people will only transfer one Pokemon (the one they want to RNG) and only accept a single gender

I lean towards the second option.  

Transporter has network lag that causes an unpredictable delay.  We recommend people duplicate the same Pokemon into a few slots starting at 0 before transferring in order to increase the odds one of the slots will hit the target.  This means it's safe to assume the user wants to target slot 0.  It's also the better user experience.

We can do this by:
- Converting `transporter_genders` in the validator to not be an array
- Updating the initial values to not use an array
- Updating `transporter_genders: opts.transporter_genders` to `transporter_genders: [opts.transporter_genders]` since rust still expects an array
- Removing the `Target` field, `target` in the validator, and hardcode `0` as the target in the `generate_transporter` opts

---

**One more note:**

We have a convenient `toOptions` util that allows us to convert a list into options for `FormikSelect`.  This allows us to reuse the same array used in the validator to make sure we don't have typos or duplicate code.

```tsx
// Pull this out to be reused and prevent typos
const transporterGenders = [
  "NoGender",
  "RandomGender",
  "Mythical",
] as const;

// Use this in FormikSelect
const transporterGenderOptions = toOptions(transporterGenders);

// Use this in the validator (you're aleady doing this)
const TransporterGenderEnum = z.enum(transporterGenders);
```
created_at: 2025-05-05T00:52:55Z
author_association: OWNER
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: sorendog
body: Thank you! This was exactly the information I needed to get this finished out, i hard coded to zero and so have it set up with the assumption people would be doing 1 at a time. I really appreciate your thorough response.
created_at: 2025-05-05T02:38:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -311,7 +312,7 @@ pub fn generate_transporter(opts: TransporterOpts) -> Vec<Stationary6State> {
         delay: opts.delay,
         perfect_iv_count,
         is_transporter: true,
-        target: opts.target,
+        target: 0,
path: rng_tools/src/generators/gen6/stationary.rs
user: login: zaksabeast
body: Whoops sorry, let's keep `opts.target` here, and only hardcode to `0` in TS.

That way if we ever want to support transferring multiple Pokemon we'll only need to edit TS.
created_at: 2025-05-05T14:53:46Z
author_association: OWNER
diff_hunk: @@ -293,7 +294,7 @@ pub struct TransporterOpts {
 pub fn generate_transporter(opts: TransporterOpts) -> Vec<Stationary6State> {
     let target_gender = opts
         .transporter_genders
-        .get(opts.target.saturating_sub(1))
+        .get(opts.target)
path: rng_tools/src/generators/gen6/stationary.rs
user: login: zaksabeast
body: Ah, I forgot about this.

3DSRNGTool started with an index of 1 for users.  It makes sense to use 0 index going forward.

However this means we have failing tests when we run `cargo test`.  We need to update of our tests at the bottom of this file to have a `target` value 1 less than they currently have.
created_at: 2025-05-05T14:55:49Z
author_association: OWNER
diff_hunk: @@ -41,6 +41,7 @@ import { Gen3MirageIsland } from "~/rngToolsUi/gen3/mirageIsland";
 import { OrAsMirageSpot } from "~/rngToolsUi/gen6/orasMirageSpot";
 import { RngTimer } from "~/rngToolsUi/timer";
 import { OrasId } from "~/rngToolsUi/gen6/orasId";
+import { Transporter } from "~/rngToolsUi/gen6/transporter/Transporter.tsx";
path: src/markdownExports/index.ts
user: login: zaksabeast
body: Let's update this to be case sensitive since it can cause issues on some operating systems when the casing doesn't match.  We can also remove `.tsx`.
```suggestion
import { Transporter } from "~/rngToolsUi/gen6/transporter/transporter";
```
created_at: 2025-05-05T14:59:02Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,152 @@
+import React from "react";
+import {
+  FormikNumberInput,
+  ResultColumn,
+  RngToolForm,
+  RngToolSubmit,
+  Field,
+  FormikSelect,
+} from "~/components";
+import { rngTools, Stationary6State } from "~/rngTools";
+import { z } from "zod";
+import { HexSchema } from "~/utils/number";
+import { toOptions } from "~/utils/options";
+
+const transporterGenders = ["NoGender", "RandomGender", "Mythical"] as const;
+const transporterGenderOptions = toOptions(transporterGenders);
+
+const columns: ResultColumn<Stationary6State>[] = [
+  {
+    title: "Advance",
+    dataIndex: "advance",
+  },
+  {
+    title: "RNG State",
+    dataIndex: "rng_state",
+    monospace: true,
+    render: (seed) => seed.toString(16).toUpperCase().padStart(8, "0"),
+  },
+  {
+    title: "PSV",
+    dataIndex: "psv",
+  },
+  {
+    title: "Shiny",
+    dataIndex: "shiny",
+    render: (shiny) => (shiny ? "Yes" : "No"),
+  },
+  {
+    title: "IVs",
+    dataIndex: "ivs",
+    monospace: true,
+    render: (ivs) =>
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: zaksabeast
body: IV results are a bit of a special case.  When we had a poll in the Discord, people preferred them to be in separate columns.

We have some easy utils to do this:
- `flattenIvs` function
- `FlattenIvs` type
- `ivColumns` pre-built, shared columns

---

**What do the utils do?**

The `flattenIvs` takes an object and merges the ivs to the top level:
```ts
const test = flattenIvs({
  species: "Torchic",
  ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 },
});
console.log(test);
// { species: 'Torchic', ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 } }
```

`FlattenIvs` is the type equivalent:
```ts
type PretendState = {
  species: Species;
  ivs: Ivs;
}

type Test = FlattenIvs<PretendState>;
// Test's type is now this:
// type Test = {
//   species: Species;
//   hp: number;
//   atk: number;
//   def: number;
//   spa: number;
//   spd: number;
//   spe: number;
// }
```

---

**How can we use these tools?**


At the top of the file we can import the utils:
```tsx
import {
  flattenIvs,
  FlattenIvs,
  ivColumns,
} from "~/rngToolsUi/shared/ivColumns";
```

Then we can create a new result type:
```tsx
type Result = FlattenIvs<Stationary6State>;
```

`Result` should be used everywhere `Stationary6State` is currently used.

Next we transform the result before setting it:
```tsx
// Instead of this
// setResults(results);

// Do this
setResults(results.map(flattenIvs));
```

And lastly we replace the IV columns from this:
```tsx
  {
    title: "Shiny",
    dataIndex: "shiny",
    render: (shiny) => (shiny ? "Yes" : "No"),
  },
  {
    title: "IVs",
    dataIndex: "ivs",
    monospace: true,
    render: (ivs) =>
      [ivs.hp, ivs.atk, ivs.def, ivs.spa, ivs.spd, ivs.spe].join(" / "),
  },
```

To this:
```tsx
{
    title: "Shiny",
    dataIndex: "shiny",
    render: (shiny) => (shiny ? "Yes" : "No"),
  },
  ...ivColumns,
```

---

**What's the final result?**

| Current PR | After changes |
| - | - |
| <img width="611" alt="Screenshot 2025-05-05 at 10 17 01 AM" src="https://github.com/user-attachments/assets/570013e5-33e2-4a55-acac-5ce3360b5f98" /> | <img width="636" alt="Screenshot 2025-05-05 at 10 16 28 AM" src="https://github.com/user-attachments/assets/122bc625-c62b-4a61-ae59-8d7f9ed492a4" /> | 
created_at: 2025-05-05T15:18:28Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,152 @@
+import React from "react";
+import {
+  FormikNumberInput,
+  ResultColumn,
+  RngToolForm,
+  RngToolSubmit,
+  Field,
+  FormikSelect,
+} from "~/components";
+import { rngTools, Stationary6State } from "~/rngTools";
+import { z } from "zod";
+import { HexSchema } from "~/utils/number";
+import { toOptions } from "~/utils/options";
+
+const transporterGenders = ["NoGender", "RandomGender", "Mythical"] as const;
+const transporterGenderOptions = toOptions(transporterGenders);
+
+const columns: ResultColumn<Stationary6State>[] = [
+  {
+    title: "Advance",
+    dataIndex: "advance",
+  },
+  {
+    title: "RNG State",
+    dataIndex: "rng_state",
+    monospace: true,
+    render: (seed) => seed.toString(16).toUpperCase().padStart(8, "0"),
+  },
+  {
+    title: "PSV",
+    dataIndex: "psv",
+  },
+  {
+    title: "Shiny",
+    dataIndex: "shiny",
+    render: (shiny) => (shiny ? "Yes" : "No"),
+  },
+  {
+    title: "IVs",
+    dataIndex: "ivs",
+    monospace: true,
+    render: (ivs) =>
+      [ivs.hp, ivs.atk, ivs.def, ivs.spa, ivs.spd, ivs.spe].join(" / "),
+  },
+  {
+    title: "Ability",
+    dataIndex: "ability",
+  },
+  {
+    title: "Nature",
+    dataIndex: "nature",
+  },
+  {
+    title: "Gender",
+    dataIndex: "gender",
+  },
+];
+
+const Validator = z.object({
+  seed: HexSchema(0xffffffff),
+  initial_advances: z.number().int().min(0),
+  max_advances: z.number().int().min(0),
+  delay: z.number().int(),
+  tsv: z.number().int(),
+  only_current_seed: z.boolean(),
+  transporter_genders: z.enum(transporterGenders),
+});
+
+export type FormState = z.infer<typeof Validator>;
+
+const initialValues: FormState = {
+  seed: 0,
+  initial_advances: 20,
+  max_advances: 50,
+  delay: 100,
+  tsv: 0,
+  only_current_seed: false,
+  transporter_genders: "NoGender",
+};
+
+const fields: Field[] = [
+  {
+    label: "Seed",
+    input: <FormikNumberInput<FormState> name="seed" numType="hex" />,
+  },
+  {
+    label: "Initial Advance",
+    input: (
+      <FormikNumberInput<FormState> name="initial_advances" numType="decimal" />
+    ),
+  },
+  {
+    label: "Max Advances",
+    input: (
+      <FormikNumberInput<FormState> name="max_advances" numType="decimal" />
+    ),
+  },
+  {
+    label: "Delay",
+    input: <FormikNumberInput<FormState> name="delay" numType="decimal" />,
+  },
+  {
+    label: "TSV",
+    input: <FormikNumberInput<FormState> name="tsv" numType="decimal" />,
+  },
+  {
+    label: "Transporter Gender",
+    input: (
+      <FormikSelect<FormState, "transporter_genders">
+        name="transporter_genders"
+        options={transporterGenderOptions}
+      />
+    ),
+  },
+];
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: zaksabeast
body: Let's add IV filter fields in case users wants specific IVs.

In rust we have a shared `PkmFilter` struct that several of our generators use, including this one.

In TS we have shared fields that let us take advantage of this with minimal code: `getPkmFilterFields`

It's a function so we can opt out of fields.  In this case we only want to filter IVs, because shininess, gender, ability, and nature are predetermined.

```tsx
const fields: Field[] = [
  // other fields
  ...getPkmFilterFields({
    ivs: true,
    ability: false,
    gender: false,
    nature: false,
    shiny: false,
  }),
];
```

A few more changes are needed:
- Validator
- initial values
- Passing the filter to `generate_transporter`

But I'll leave those as an exercise for you to explore how we do this elsewhere.  If you get stuck, let me know!
created_at: 2025-05-05T15:30:00Z
author_association: OWNER
diff_hunk: @@ -84,3 +84,5 @@ In 3DSRNGTool:
 1. In the "RNG Info" box of 3DSRNGTool, change "delay" to this new delay.
 1. Press "Calculate" to find your potential frames with this new delay for your target index.
 1. Find the highest frame among the 10+ consecutive frames from earlier and use it as your target frame.
+
+<Transporter />
path: guides/Transporter/Transporter.mdx
user: login: zaksabeast
body: We should 100% add this to the guide, although as-is the guide references 3DSRNGTool.  We need to update this for the embedded tool.

If you want to update the guide, go for it!  If not, Shiny or I can do it.  Let us know which you'd prefer.
created_at: 2025-05-05T15:31:20Z
author_association: OWNER
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: zaksabeast
body: Sure thing!  I'm happy to help.
created_at: 2025-05-05T15:41:53Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,152 @@
+import React from "react";
+import {
+  FormikNumberInput,
+  ResultColumn,
+  RngToolForm,
+  RngToolSubmit,
+  Field,
+  FormikSelect,
+} from "~/components";
+import { rngTools, Stationary6State } from "~/rngTools";
+import { z } from "zod";
+import { HexSchema } from "~/utils/number";
+import { toOptions } from "~/utils/options";
+
+const transporterGenders = ["NoGender", "RandomGender", "Mythical"] as const;
+const transporterGenderOptions = toOptions(transporterGenders);
+
+const columns: ResultColumn<Stationary6State>[] = [
+  {
+    title: "Advance",
+    dataIndex: "advance",
+  },
+  {
+    title: "RNG State",
+    dataIndex: "rng_state",
+    monospace: true,
+    render: (seed) => seed.toString(16).toUpperCase().padStart(8, "0"),
+  },
+  {
+    title: "PSV",
+    dataIndex: "psv",
+  },
+  {
+    title: "Shiny",
+    dataIndex: "shiny",
+    render: (shiny) => (shiny ? "Yes" : "No"),
+  },
+  {
+    title: "IVs",
+    dataIndex: "ivs",
+    monospace: true,
+    render: (ivs) =>
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: sorendog
body: That is so helpful, having it be a callable function basically for the result column. 
created_at: 2025-05-06T01:12:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -84,3 +84,5 @@ In 3DSRNGTool:
 1. In the "RNG Info" box of 3DSRNGTool, change "delay" to this new delay.
 1. Press "Calculate" to find your potential frames with this new delay for your target index.
 1. Find the highest frame among the 10+ consecutive frames from earlier and use it as your target frame.
+
+<Transporter />
path: guides/Transporter/Transporter.mdx
user: login: sorendog
body: I updated the guide to reference the tool. For now I removed the section with regards to doing 20 pokemon at once. but maybe that section would do good as it's own page now that still links to 3dsrng tool since doing 20 at once right now would be best set up for in 3dsrng tool?
created_at: 2025-05-06T01:51:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,169 @@
+import React from "react";
+import {
+  FormikNumberInput,
+  ResultColumn,
+  RngToolForm,
+  RngToolSubmit,
+  Field,
+  FormikSelect,
+} from "~/components";
+import { rngTools, Stationary6State } from "~/rngTools";
+import {
+  flattenIvs,
+  FlattenIvs,
+  ivColumns,
+} from "~/rngToolsUi/shared/ivColumns";
+import { maxIvs, minIvs } from "~/types/ivs";
+import { getPkmFilterFields, pkmFilterSchema } from "~/components/pkmFilter";
+import { z } from "zod";
+import { HexSchema } from "~/utils/number";
+import { toOptions } from "~/utils/options";
+
+const transporterGenders = ["NoGender", "RandomGender", "Mythical"] as const;
+const transporterGenderOptions = toOptions(transporterGenders);
+type Result = FlattenIvs<Stationary6State>;
+
+const columns: ResultColumn<Result>[] = [
+  {
+    title: "Advance",
+    dataIndex: "advance",
+  },
+  {
+    title: "RNG State",
+    dataIndex: "rng_state",
+    monospace: true,
+    render: (seed) => seed.toString(16).toUpperCase().padStart(8, "0"),
+  },
+  {
+    title: "PSV",
+    dataIndex: "psv",
+  },
+  {
+    title: "Shiny",
+    dataIndex: "shiny",
+    render: (shiny) => (shiny ? "Yes" : "No"),
+  },
+  ...ivColumns,
+  {
+    title: "Ability",
+    dataIndex: "ability",
+  },
+  {
+    title: "Nature",
+    dataIndex: "nature",
+  },
+  {
+    title: "Gender",
+    dataIndex: "gender",
+  },
+];
+
+const Validator = z
+  .object({
+    seed: HexSchema(0xffffffff),
+    initial_advances: z.number().int().min(0),
+    max_advances: z.number().int().min(0),
+    delay: z.number().int(),
+    tsv: z.number().int(),
+    only_current_seed: z.boolean(),
+    transporter_genders: z.enum(transporterGenders),
+  })
+  .merge(pkmFilterSchema);
+
+export type FormState = z.infer<typeof Validator>;
+
+const initialValues: FormState = {
+  seed: 0,
+  initial_advances: 20,
+  max_advances: 50,
+  delay: 100,
+  tsv: 0,
+  only_current_seed: false,
+  transporter_genders: "NoGender",
+  filter_shiny: false,
+  filter_min_ivs: minIvs,
+  filter_max_ivs: maxIvs,
+  filter_nature: null,
+  filter_gender: null,
+  filter_ability: null,
+};
+
+const fields: Field[] = [
+  {
+    label: "Seed",
+    input: <FormikNumberInput<FormState> name="seed" numType="hex" />,
+  },
+  {
+    label: "Initial Advance",
+    input: (
+      <FormikNumberInput<FormState> name="initial_advances" numType="decimal" />
+    ),
+  },
+  {
+    label: "Max Advances",
+    input: (
+      <FormikNumberInput<FormState> name="max_advances" numType="decimal" />
+    ),
+  },
+  {
+    label: "Delay",
+    input: <FormikNumberInput<FormState> name="delay" numType="decimal" />,
+  },
+  {
+    label: "TSV",
+    input: <FormikNumberInput<FormState> name="tsv" numType="decimal" />,
+  },
+  {
+    label: "Transporter Gender",
+    input: (
+      <FormikSelect<FormState, "transporter_genders">
+        name="transporter_genders"
+        options={transporterGenderOptions}
+      />
+    ),
+  },
+  ...getPkmFilterFields({
+    ivs: true,
+    ability: false,
+    gender: false,
+    nature: false,
+    shiny: false,
+  }),
+];
+
+export const Transporter = () => {
+  const [results, setResults] = React.useState<Result[]>([]);
+
+  const onSubmit = React.useCallback<RngToolSubmit<FormState>>(async (opts) => {
+    const results = await rngTools.generate_transporter({
+      seed: opts.seed,
+      initial_advances: opts.initial_advances,
+      max_advances: opts.max_advances,
+      delay: opts.delay,
+      target: 0,
+      transporter_genders: [opts.transporter_genders],
+      tsv: opts.tsv,
+      filter: {
+        gender: null,
+        ability: null,
+        nature: null,
+        ivs: null,
path: src/rngToolsUi/gen6/transporter/transporter.tsx
user: login: zaksabeast
body: We need to hook up the iv filters - they don't work at the moment:
```suggestion
        ivs: {
          min_ivs: opts.filter_min_ivs,
          max_ivs: opts.filter_max_ivs,
        },
```
created_at: 2025-05-06T15:00:36Z
author_association: OWNER
path: guides/Transporter/Transporter.mdx
user: login: ShinySylveon04
body: I think it would be good to change “frames” to “advances.” We’re trying to phase out the term “frames” and use “advances” instead. Both the tool and PokeReader use “advances,” so keeping the wording the same will help avoid confusion.
created_at: 2025-05-06T15:05:34Z
author_association: COLLABORATOR
diff_hunk: @@ -41,15 +37,14 @@ RNGing with Transporter has a delay between pressing `A` and Pokémon generation
 
 ## Step 2: Find a spread to RNG
 
-Follow these steps in 3DSRNGTool to find a desired spread:
+Follow these steps in with the tool below to find a desired spread:
path: guides/Transporter/Transporter.mdx
user: login: ShinySylveon04
body: A small typo here.
```suggestion
Follow these steps with the tool below to find a desired spread:
```
created_at: 2025-05-06T15:07:19Z
author_association: COLLABORATOR
diff_hunk: @@ -24,15 +23,12 @@ RNGing with Transporter has a delay between pressing `A` and Pokémon generation
 
 1. Make sure you have at least one Pokémon in Box 1, Slot 1 of your Gen 1/2 game.
 1. Load Pokémon Transporter with PokeReader on your 3DS.
-1. Open 3DSRNGTool on your computer.
-1. Change 3DSRNGTool's "Game Version" to "Transporter."
-1. Change 3DSRNGTool's "Seed" to the Initial Seed PokeReader shows.
-1. Change 3DSRNGTool's tab to "Stationary RNG."
+1. Enter your Initial seed in the tool below.
 1. In Transporter, press `Start` to select the game for Transporter.
 1. Press `Start + Select` to pause the game.
 1. Note your current frame; this is your starting frame.
 1. Press `A` to unpause and let Transporter generate the Pokémon.
-1. Search for the exact IVs PokeReader indicates using 3DSRNGTool to find your hit frame.
+1. Search for the exact IVs PokeReader indicates using the tool below to find your hit frame.
 1. Subtract your hit frame from your starting frame to get your delay.
    - Example: If you started on frame 1000 and hit frame 1197, your delay is 197.
 1. When asked to transfer the Pokémon, click "No."
path: guides/Transporter/Transporter.mdx
user: login: ShinySylveon04
body: There's a 3DSRNGTool reference two lines below this (but I can't comment on that line for some reason).
created_at: 2025-05-06T15:14:02Z
author_association: COLLABORATOR
diff_hunk: @@ -63,24 +58,4 @@ All results are potential wanted frames. Pick your favorite!
 
 This may take several attempts.
 
-## Alternative method with increased chances
-
-Transporter generates 20 Pokémon at once. If you have 20 of the same Pokémon or cloned, you can have 20 chances to RNG the same Pokémon.
path: guides/Transporter/Transporter.mdx
user: login: ShinySylveon04
body: I think that's still a good part to keep, but we can update it to be a bit simpler and remove 3DSRNGTool references:
```
Transporter generates 20 Pokémon at once. If you have 20 of the same Pokémon or cloned, you can have 20 chances to RNG the same Pokémon.

1. Catch 20 of the same Pokémon (or clone them).
1. Make sure all 20 are in Box 1 of your Gen 1/2 game.

In the tool below:

1. Change the "Filters" box to have the IVs you want to RNG.
1. Search for spreads using the initial seed PokeReader shows.
1. Ensure the number of consecutive advances is at least 10.
1. Aim for the middle advance as your target advance.
```
created_at: 2025-05-06T15:18:44Z
author_association: COLLABORATOR

PR comments:
user: login: sorendog
created_at: 2025-05-04T23:47:40Z
body: i just realized i changed it to try to make it just work, going to do a commit in a second to change back to what i had.

author_association: CONTRIBUTOR
user: login: sorendog
created_at: 2025-05-06T01:53:45Z
body: I think I got everything, so going to request re-review. If I missed anything let me know!
author_association: CONTRIBUTOR
user: login: sorendog
created_at: 2025-05-07T02:09:43Z
body: All notes implemented!
author_association: CONTRIBUTOR