mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
7d6566fa51
A capture the party cannot hold silently falls through to the box; the new partyFullDestination seam lets a mode claim custody at that moment instead, and pokemon.caught reports destination "mod" so the mode can find the mon again. Guarded call site, file-local vanilla, docs and a public-API modkit case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1DVpYXGabigHqMwviDoKV
114 lines
5.0 KiB
Markdown
114 lines
5.0 KiB
Markdown
# RFC 0018: `catch.party_full` — custody of a catch the party cannot hold
|
|
|
|
## Status
|
|
|
|
Proposed.
|
|
|
|
## Motivation
|
|
|
|
When a capture lands and the party already holds six Pokémon, the cart does
|
|
not ask the player anything: `AddPartyMon` fails and the mon goes to
|
|
`SendNewMonToBox` without a stop. The engine mirrors that —
|
|
`BattleState:storeCaughtMon` falls through to `Boxes.deposit` — and a game
|
|
mode has no way to stand in front of it.
|
|
|
|
That silence is wrong for any mode where the box is not a thing. A battle
|
|
royale locks the Pokémon Center PC for the whole match (the box is a second
|
|
health bar: deposit the healthy ones, fight with one, withdraw fresh ones),
|
|
so a seventh catch is deposited into storage the player cannot reach — the
|
|
mon is gone and everyone saw the fanfare. A Nuzlocke that counts a boxed mon
|
|
as lost, a randomizer that wants to hand out a replacement instead, a
|
|
challenge run that makes the player release someone for the catch — all want
|
|
the decision, and today there is no seam to catch it at.
|
|
|
|
The immediate consumer is a battle-royale mode, where the fix is the game's
|
|
own rule: at 6/6 you choose who makes room, and whoever leaves hits the
|
|
ground as a ball. Neither the decision nor the spill is specific to it.
|
|
|
|
## The decision it extends
|
|
|
|
This extends the **additive, guarded seam convention** Route B in
|
|
`CONTRIBUTING-mods.md` documents, and is gated by the parity guarantee
|
|
`tests/engine/gate_meta_coverage.lua` enforces. It sits next to
|
|
`catch.nickname` (RFC 0015) on the same capture path: that hook answers
|
|
*what the mon is called* once its home is decided; this one decides the home
|
|
when the party cannot hold it.
|
|
|
|
There is no in-repo D-number registry to amend.
|
|
|
|
## Exact API delta
|
|
|
|
### New hook: `catch.party_full`
|
|
|
|
```lua
|
|
mod.hooks:wrap("catch.party_full", function(next, ctx)
|
|
-- ctx = { battle = <BattleState>, mon = <Pokemon>, name = <display name>,
|
|
-- game = <Game> }
|
|
if myMode.active then
|
|
takeCustody(ctx.mon) -- the mon is the mod's problem now
|
|
return true -- nothing is deposited
|
|
end
|
|
return next(ctx) -- false: deposit, as today
|
|
end)
|
|
```
|
|
|
|
Call site: `BattleState:partyFullDestination(mon)`, called from the capture
|
|
path at the moment `AddPartyMon` has failed and `SendNewMonToBox` would run.
|
|
The vanilla link returns `false`. A truthy return skips the box entirely —
|
|
the mon is neither in the party nor in any box, and `pokemon.caught` reports
|
|
`destination = "mod"` so the mode can find its own custody again. Anything
|
|
falsy deposits as always, "But every BOX is full!" included.
|
|
|
|
The method returns `"box"` or `"mod"`, and is a *method* on purpose: the
|
|
compatibility seam for engines that predate this RFC needs a name to ask for
|
|
(see below), and `battleStyle`/`offerNickname` set the precedent.
|
|
|
|
### One event value, already emitted
|
|
|
|
`pokemon.caught`'s `destination` gains a third value, `"mod"`, next to
|
|
`"party"` and `"box"`. Nothing that reads the event today matches on it.
|
|
|
|
### No other surface changes
|
|
|
|
The call site is guarded by `Runtime.wantsHook`, and the vanilla link is a
|
|
file-local, so a build with nothing wrapped runs the branch exactly as
|
|
before and allocates nothing it did not allocate before.
|
|
|
|
## Migration
|
|
|
|
Nothing changes for existing mods. A mode that was fighting the box after
|
|
the fact — withdrawing the deposit it could not prevent, or eating the loss —
|
|
should wrap `catch.party_full` and take the mon at the moment of the catch.
|
|
|
|
## Verification
|
|
|
|
- `tests/modkit/cases/catch_party_full.lua` — through the public mod API:
|
|
with no mod a 6/6 catch lands in the box with the transfer text; a wrapped
|
|
mod that claims custody leaves the mon out of both party and boxes; a
|
|
fall-through deposits; the hook's ctx carries the battle, the mon, the
|
|
display name and the game.
|
|
- `tests/engine/gate_hooks.lua` — the name is in the live catalog and passes
|
|
the no-mod parity gate (vanilla called exactly once, result unchanged,
|
|
nothing allocated).
|
|
- `tests/engine/gate_meta_coverage.lua` — the name is covered by the unit
|
|
corpus.
|
|
|
|
## Backward compatibility
|
|
|
|
Additive. No existing hook, event, registry or manifest field changes shape.
|
|
`storeCaughtMon`'s box branch keeps its text, its nickname offer and its
|
|
`pokemon.caught` emit; the only reader-visible difference with no subscriber
|
|
is one extra method on `BattleState`.
|
|
|
|
## Compatibility seam for older engines
|
|
|
|
The call site is mid-function, so a mod on a stock engine cannot see it —
|
|
the same problem `world.talk` has. The battle-royale mod's shim covers the
|
|
gap by wrapping `Boxes.deposit` (the one call the old branch makes that a
|
|
patch can reach): during a match it raises `catch.party_full` first, and a
|
|
claim refuses the deposit, so nothing reaches a box even there. The mod then
|
|
takes custody from the `pokemon.caught` emit, which carries the mon. What
|
|
the shim cannot repair is the text: the old branch answers a refused deposit
|
|
with "But every BOX is full!" before the mode's own prompt opens — the wrong
|
|
reason for the right decision, and the argument for the seam.
|