2
Guide Link Compatibility
bryanthaboi edited this page 2026-08-15 23:44:59 -04:00

Link compatibility

How two connected games decide whether they can battle and trade, and how to keep a mod link-safe. Implementation: src/link/Fingerprint.lua, src/link/Handshake.lua.

The fingerprint

On entering link play, each side digests its link surface: the merged pokemon, moves, type chart, statuses, and move_effects records (over stable allowlisted fields), the link-relevant constants, any registered link_fields, and the { id, version } of every mod whose affects_link is true. Identical surfaces produce identical digests — regardless of how mods produced them.

The digest is generation-aware. A Gold dataset hashes a Gen 2 surface over Gold's own records: species carry baseStats with the split specials, genderRatio and evolutions; moves add effectChance; the type chart brings its FORESIGHT matchups; and the Gen 2 registries statuses, move_effects, held_items and growth_rates are hashed in place of their Gen 1 counterparts. The two surfaces can never collide, and the link.fingerprint hook keeps its (data, mods) shape on both.

The verdict

Verdict When Battle Trade
full fingerprints match yes yes
vanilla_peer old (v1) build on the far side, this side unmodified yes yes
engine_skew both v2 on the same major, different engine releases no yes, negotiated
subset both v2, fingerprints differ no strict-vanilla subset, negotiated
refused v1 peer that our mods would corrupt, an engine mismatch, or a peer running the other generation no no

engine_skew exists because the fingerprint only covers the data and mod surface: battle logic moves between releases, so two honest vanilla installs one release apart would pair as full and then drift mid-battle. They trade; they do not battle until both sides run the same release.

The subset trade path admits only mons both sides rebuild identically — per-record digests agree species by species — so a modded game and a lightly-modded friend can still trade the vanilla 151 without risk. A mid-battle divergence (the last line of defense) raises link.desync with the failing component and ends the battle as a draw instead of letting the two screens drift apart. Never desync silently is the rule.

The manifest switch

affects_link defaults by profile: false for content, true for overhaul and total_conversion. Set it explicitly when the default is wrong:

  • A "content" mod that rebalances moves or adds species does move battle outcomes — declare "affects_link": true.
  • An overhaul that only touches UI, music, and text can declare "affects_link": false to keep vanilla-compatible link play.

Declare false while writing into a link-relevant registry (pokemon, moves, type_chart, statuses, move_effects — src/mods/Manifest.lua LINK_REGISTRIES) and the loader logs an attributed warning: your claim and your writes disagree.

  • Prefer additions over changes: a new species you never trade does not block the vanilla subset.
  • Keep battle-relevant records untouched if you want vanilla_peer compat with unmodified builds.
  • Extra per-mon data that must survive a trade belongs in the link_fields registry — { rev, pack?, unpack? } per field, where only rev enters the digest; bump it when the codec's meaning changes.
  • Two peers must run the same ordering rules: battle.turn_order wraps inside link battles too, so a mod that changes it must be installed on both sides (the fingerprint enforces this when affects_link is true).
  • On Gold, held-item attributes and growth curves are link surface too: what LEFTOVERS heals rides on a traded mon, and a curve decides what level a traded mon's experience buys. A write into held_items or growth_rates marks the install link-modified the way the five registries above always have, even though the loader's claim-vs-writes warning currently checks only those five.

Gold

Gold does not link yet: a Gold boot offers no LINK menu, and the trade and battle sessions are not built. What is built is the fence around them. Each hello carries a generation field (absent means 1, which every pre-Gold build is), and a cross-generation pairing is refused as generation_mismatch before any digest is compared. The cart's own answer to that cable was the Time Capsule's party conversion and its validators; until those exist, refusal is the honest verdict, named as the wrong game rather than an older version. The link_fields registry is likewise Gen 1 only: on a Gold boot it has no home and a write is a reported drop.

Events: link.connected ({ role, remote = { name, mode, mods, fingerprint } }), link.ended, link.desync, trade.completed, pokemon.received. The link.fingerprint hook wraps digest computation itself — for tooling, not for lying to your peer.

Player-facing link setup: Link Play.