mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
Merge remote-tracking branch 'origin/dev' into feat/battle-menu-auxiliary
# Conflicts: # src/core/Checkpoint.lua
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
# RFC 0001 — Port Yellow's `IsSurfingPikachuInParty` surf sprite
|
||||
|
||||
## Status
|
||||
|
||||
Proposed. Engine: `Player.lua`, `FieldDefaults.lua`,
|
||||
`OverworldController.lua`, `RomExtractor.lua`, `PaletteFX.lua`. Tools:
|
||||
`build_rom_data.py`, `extract/sprites.py`, `make_rom_manifest.py`,
|
||||
`make_yellow_manifest.py`. Tests: `parity_surfing_pikachu_sprite.lua`,
|
||||
`mod_world_tests.lua`.
|
||||
|
||||
**Regeneration required.** The manifest and sprite sheet update by
|
||||
re-running `make_yellow_manifest.py` against a `pret/pokeyellow`
|
||||
checkout, then re-importing the Yellow ROM.
|
||||
|
||||
## Motivation
|
||||
|
||||
Yellow's `IsSurfingPikachuInParty` + `LoadSurfingPlayerSpriteGraphics2`
|
||||
(`home/map_objects.asm`, `home/overworld.asm`) swap the player's
|
||||
overworld sheet to `SurfingPikachuSprite` (`gfx/sprites/
|
||||
surfing_pikachu.2bpp`, a 16×96 walk sheet — not the minigame sheets)
|
||||
when the party mon that knows SURF is a Pikachu. The recomp misses this
|
||||
in two places:
|
||||
|
||||
1. **Extraction.** `SurfingPikachuSprite` is not in
|
||||
`SpriteSheetPointerTable` — loaded by its own `ld de,` like
|
||||
`RedBikeSprite`. The extractor never sees it, and the symbol is not
|
||||
in the Yellow manifest.
|
||||
2. **Engine rule.** `field.playerSprites.surf` is one static
|
||||
(`SPRITE_SEEL`), cached at boot. No seam for "swap when the SURF-mon
|
||||
is a Pikachu."
|
||||
|
||||
## The decision it extends
|
||||
|
||||
No prior D-number. Extends the surf-field-move port in
|
||||
`docs/behavior-porting-notes.md` (the `IsSurfingAllowed` exact port)
|
||||
with the player-sprite swap vanilla runs alongside it.
|
||||
|
||||
## The exact API delta
|
||||
|
||||
Backward-compatible, additive-only.
|
||||
|
||||
### `field.playerSprites.surfPikachu`
|
||||
|
||||
New optional key alongside `walk`/`surf`/`bike`/`fly`, defaults to
|
||||
`SPRITE_SURFING_PIKACHU`. Guarded in `Player.new` so before extraction
|
||||
lands the ride keeps the Seel — no plain on-water Pikachu.
|
||||
|
||||
### `Player.surfPikachuSprite`
|
||||
|
||||
`Player.new` builds a second `SpriteRenderer` when the field resolves.
|
||||
`pose()` picks it when `surfing and surfingPikachu`.
|
||||
|
||||
### `Player.surfingPikachu` (runtime)
|
||||
|
||||
Runtime-only boolean (not persisted); re-derived so a party change
|
||||
between save and load is honored.
|
||||
|
||||
### `OverworldState:syncSurfingPikachu()`
|
||||
|
||||
Sets `player.surfingPikachu` from `partyKnows("SURF")`. Called at every
|
||||
surf-state toggle: trySurf, dismount, flyTo, beginTeleportOut,
|
||||
warpToHealPoint, forced-surf tile, setMap boot-restore.
|
||||
|
||||
### Importer — `SPRITE_SURFING_PIKACHU`
|
||||
|
||||
`make_yellow_manifest.py` adds `SurfingPikachuSprite` to
|
||||
`YELLOW_EXTRA_SYMBOLS`. `make_rom_manifest.py`'s `sprite_metadata()`
|
||||
gains a `surfPikachu` entry (guarded, so Red/Blue unchanged).
|
||||
`RomExtractor.extractSprites` + `build_rom_data.py` + `extract/sprites.py`
|
||||
each gain a parallel extract mirroring `RedBikeSprite`.
|
||||
|
||||
### `PaletteFX.spriteObp`
|
||||
|
||||
`SurfingPikachuSprite` joins `RedBikeSprite` in the no-bracket-index
|
||||
special case, wearing the player's OBP palette so it colors in GBC mode.
|
||||
|
||||
## Migration note for existing mods
|
||||
|
||||
**Nothing.** `surf` still defaults to `SPRITE_SEEL`; `surfPikachu`
|
||||
only resolves on a Yellow import after regeneration. No manifest or
|
||||
`mod.save` shape changes. An eligibility hook that swaps a rental
|
||||
SURF-mon still drives the sprite pick via `partyKnows`.
|
||||
|
||||
## Parity tests
|
||||
|
||||
- **No-mod** (`mod_world_tests.lua`): `surf == "SPRITE_SEEL"`,
|
||||
`surfPikachu == "SPRITE_SURFING_PIKACHU"` seeded at boot. The 19229-check
|
||||
`world & maps v2` suite stays green.
|
||||
- **Mod-API** (`parity_surfing_pikachu_sprite.lua`): `syncSurfingPikachu`
|
||||
+ `Player:pose` across four party shapes (12/12). The existing
|
||||
`parity_cinnabar_east_surf.lua` (24/24) stays green.
|
||||
|
||||
## Deprecation etiquette
|
||||
|
||||
Nothing deprecated. Additive: a new `field.playerSprites` key, a new
|
||||
runtime flag, a new engine method, a new sprite id.
|
||||
@@ -1,54 +0,0 @@
|
||||
# RFC 0002 — Let mods hide an active screen state from the main render
|
||||
|
||||
## Status
|
||||
|
||||
Proposed. Engine: `StateStack.lua`, `Game.lua`. Tests:
|
||||
`screen_render_visible.lua`.
|
||||
|
||||
## Motivation
|
||||
|
||||
A mod can render a native menu on a companion display through
|
||||
`render.compose`, but it cannot remove that menu from the main display without
|
||||
also popping it. Popping transfers update and input ownership and forces the
|
||||
mod to reimplement native menu behavior.
|
||||
|
||||
## The decision it extends
|
||||
|
||||
No prior D-number. Extends the render-hook plan in `docs/modding.md` and the
|
||||
state-stack rendering contract in `docs/architecture.md`.
|
||||
|
||||
## The exact API delta
|
||||
|
||||
Backward-compatible, additive-only.
|
||||
|
||||
### `screen.render_visible`
|
||||
|
||||
New hook called with `(state) -> boolean` through the public wrapper signature
|
||||
`(next, state)`. Its vanilla result is `true`.
|
||||
|
||||
Returning `false` excludes the state from the main draw, from opaque-base
|
||||
selection and from palette-zone ownership. It does not remove the state or
|
||||
change update, input, push or pop behavior. The call sites are
|
||||
`StateStack:visibleBase`, `StateStack:draw` and the equivalent draw and palette
|
||||
walks in `Game:draw`.
|
||||
|
||||
The hook is guarded by `Runtime.wantsHook`, so the no-subscriber path allocates
|
||||
nothing. It is a pure render predicate and may be evaluated more than once per
|
||||
frame.
|
||||
|
||||
## Migration note for existing mods
|
||||
|
||||
**Nothing.** With no subscriber every state remains visible, and the existing
|
||||
state-stack, event and hook behavior is unchanged.
|
||||
|
||||
## Parity tests
|
||||
|
||||
- **No-mod:** the topmost opaque state still owns drawing and palette zones,
|
||||
and `Runtime.wantsHook("screen.render_visible")` stays false.
|
||||
- **Mod-API:** a fixture mod registers through `mod.hooks:wrap`, hides one
|
||||
opaque state and proves the state beneath draws and owns the palette while
|
||||
the hidden state remains topmost and continues updating.
|
||||
|
||||
## Deprecation etiquette
|
||||
|
||||
Nothing deprecated. This is one additive hook with a `true` vanilla default.
|
||||
@@ -15,9 +15,12 @@ references, completion is currently an `onFinish` closure, and scripted battles
|
||||
resume a suspended `ScriptRunner` coroutine. Copying the controller would create
|
||||
a record that is neither data-only nor process-independent.
|
||||
|
||||
The engine can instead expose a narrow semantic safe point. This gives all mods
|
||||
the strongest persistent battle checkpoint the current architecture can prove,
|
||||
without claiming mid-animation or suspended-script support.
|
||||
The engine can instead expose a narrow semantic safe point. Ordinary encounters
|
||||
use fixed engine-owned completion descriptors. A scripted story encounter may
|
||||
also participate when its active command row and remaining row-list are
|
||||
detached data, its NPC can be rebound by stable object id, and its completion is
|
||||
one of the engine-declared semantic forms. The suspended coroutine itself is
|
||||
never captured.
|
||||
|
||||
## API delta
|
||||
|
||||
@@ -26,7 +29,7 @@ second format-1 runtime kind.
|
||||
|
||||
### Capability
|
||||
|
||||
`mod.checkpoints:inspect(game)` returns this only when an ordinary single-player
|
||||
`mod.checkpoints:inspect(game)` returns this only when a supported single-player
|
||||
wild or trainer battle is settled at the player command menu:
|
||||
|
||||
```lua
|
||||
@@ -35,13 +38,15 @@ wild or trainer battle is settled at the player command menu:
|
||||
|
||||
The action/message queue, waits, UI, animations, HP/status presentation, and
|
||||
faint processing must be settled. The player must actually control the menu.
|
||||
The underlying overworld must have no running/queued script or scripted move,
|
||||
and the battle must carry an engine-owned semantic continuation descriptor.
|
||||
The battle must carry an engine-owned semantic continuation descriptor. An
|
||||
ordinary encounter requires an idle overworld. A scripted story encounter may
|
||||
have exactly its originating foreground runner suspended at the battle command;
|
||||
queued/parallel scripts and scripted movement remain unsafe.
|
||||
|
||||
Additional refusal codes are `battle_phase_busy`, `battle_origin_unsupported`,
|
||||
`battle_variant_unsupported`, and `link_battle_unsupported`. Link, Safari,
|
||||
ghost, old-man/demo, fishing, static-object, script-suspended, and mod-created
|
||||
closure continuations remain rejected.
|
||||
ghost, old-man/demo, fishing, opaque callback continuations, non-data-only
|
||||
scripts, and unsupported concurrent script work remain rejected.
|
||||
|
||||
### Capture
|
||||
|
||||
@@ -107,10 +112,20 @@ trainer class/party, and optional header event; a win reapplies the same defeate
|
||||
flag, event, reward, and `afterBattle` path. Reconstructed overworld input and
|
||||
NPC freeze state are normalized instead of reviving the old closure.
|
||||
|
||||
`Commands.start_battle` is deliberately unsupported: its completion closure
|
||||
mutates script context and resumes a coroutine whose program counter and Lua
|
||||
stack cannot be serialized. Existing script rejection remains the correct safe
|
||||
contract until a separate semantic ScriptRunner checkpoint RFC exists.
|
||||
For `start_battle`, `rival_battle`, and `static_battle`, the runner records the
|
||||
detached row list and current command program counter plus stable source/NPC
|
||||
identity where present. On restore, battle completion starts a fresh runner at
|
||||
that command with a one-use semantic battle result. Replaying the current
|
||||
command (rather than skipping to the next row) preserves wrapper behavior such
|
||||
as rival-party consequences, static-object removal, `lastCheck`, and deferred
|
||||
`afterBattle` evolution ordering. The reconstructed overworld starts with
|
||||
normalized input/NPC freeze state, so an engine-marked `release_npc` callback
|
||||
needs no closure revival.
|
||||
|
||||
Arbitrary `onDone` callbacks, function-bearing rows, unknown commands, missing
|
||||
NPC identities, old-man/demo flows, and concurrent scripts fail closed. This is
|
||||
not a general ScriptRunner snapshot: no coroutine, Lua stack, local variable,
|
||||
function, or runtime object enters the checkpoint.
|
||||
|
||||
## Migration note
|
||||
|
||||
@@ -138,3 +153,6 @@ No-mod behavior is unchanged when checkpoints are unused.
|
||||
- exactly one post-verification `checkpoint.restored` event and none on failure;
|
||||
- legacy overworld checkpoint compatibility;
|
||||
- complete ROM-free engine and public mod-API suites.
|
||||
- scripted trainer and static/wild story continuation, including wrapper-row
|
||||
replay, cold reconstruction, malformed row rejection, and opaque callback
|
||||
refusal.
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# RFC 0006 — Selected title playthrough storage and checkpoint resume
|
||||
|
||||
## Status
|
||||
|
||||
Proposed. Engine: `SaveData.lua`, `Storage.lua`, `Checkpoint.lua`, and
|
||||
`Loader.lua`. Tests: `title_playthrough_context.lua`, existing storage,
|
||||
checkpoint, title, save-slot, and no-mod parity suites.
|
||||
|
||||
## Motivation
|
||||
|
||||
A tool checkpoint may be the first durable record of a new playthrough. The
|
||||
engine intentionally keeps normal Pokémon SAVE independent: before the first
|
||||
normal write, identity is retained by the engine-owned selected-slot mapping,
|
||||
while title starts with a fresh New Game skeleton. A durable checkpoint tool may
|
||||
explicitly create one ordinary progress anchor after its first checkpoint has
|
||||
committed; later tool writes must remain independent. Calling ordinary active
|
||||
`mod.storage` there would allocate/adopt an identity, and live
|
||||
`mod.checkpoints:restore` correctly refuses title because it has no gameplay
|
||||
rollback state. Generic public capabilities are required; a tool must not use
|
||||
private storage paths, slot ids, or simulate the player's SAVE menu flow.
|
||||
|
||||
## Additive public API
|
||||
|
||||
### `mod.storage:selected(game)`
|
||||
|
||||
Available only while the engine is in a title session. Returns an opaque bound
|
||||
facade or `nil, code, message`:
|
||||
|
||||
```lua
|
||||
local selected = mod.storage:selected(game)
|
||||
local context = selected:context()
|
||||
local history = selected:read("history/index")
|
||||
```
|
||||
|
||||
The facade exposes `context()`, `read(key)`, `write(key, value)`,
|
||||
`list(prefix)`, and `delete(key)`. It is bound internally to the launcher-
|
||||
selected existing game-version/playthrough and the calling mod id. It neither
|
||||
accepts an arbitrary playthrough id nor reveals a slot id, filesystem path, or
|
||||
another mod namespace. Resolution is read-only; no selected mapping means
|
||||
`no_selected_playthrough`, and opening a title browser never mints an identity.
|
||||
Its detached context can include only `normalSavedAt` from a matching ordinary
|
||||
save, so title tools can apply their own resume policy without receiving the
|
||||
canonical normal-save record.
|
||||
|
||||
### `mod.checkpoints:ensureNormalSave(game, checkpoint)`
|
||||
|
||||
Available only at a live checkpoint-safe boundary. After a tool has durably
|
||||
committed the supplied current checkpoint, it may request an ordinary progress
|
||||
anchor for a playthrough that has never had one. The engine validates the
|
||||
checkpoint, proves it exactly matches a fresh capture of the live runtime, and
|
||||
uses the normal atomic save path including `save.write` lifecycle/veto hooks.
|
||||
|
||||
The operation is idempotent. It returns `true, "already_exists"` without writing
|
||||
when matching normal progress already exists, so later checkpoints never move
|
||||
the vanilla CONTINUE target. A stale/non-current checkpoint, unsafe runtime,
|
||||
write veto/failure, or failed readback returns a structured failure. A tool
|
||||
should call it only after its own checkpoint and index are durable and must not
|
||||
report that first checkpoint as successful if the required anchor fails.
|
||||
|
||||
### `mod.checkpoints:resume(game, checkpoint)`
|
||||
|
||||
Available only from title. It validates format, data-only structure, selected
|
||||
game/playthrough identity, canonical save/content, overworld/battle runtime, and
|
||||
RNG exactly as `restore` does. It then reconstructs semantic overworld or a
|
||||
supported battle continuation, preserves current options, and differentially
|
||||
recaptures before committing. On success it emits `checkpoint.restored` once.
|
||||
|
||||
Title has no live runtime rollback. A reconstruction or verification failure
|
||||
therefore rebuilds a clean title session from the pre-operation title save and
|
||||
RNG; it emits no success event and never rewrites normal progress. Validation
|
||||
failure leaves the existing title session untouched. Stable errors include
|
||||
`not_at_title`, `no_selected_playthrough`, normal checkpoint validation codes,
|
||||
`resume_failed`, and `title_recovery_failed`.
|
||||
|
||||
## Isolation and migration
|
||||
|
||||
Explicit NEW GAME retains its existing fresh-identity rule. It does not reuse a
|
||||
previous selected mapping and cannot see old tool history. Existing mods change
|
||||
nothing: no identity, storage, title reconstruction, or event is created unless
|
||||
the new methods are called. `mod.storage` remains independent durable data and
|
||||
does not rewind with a checkpoint; canonical `game.save` / `mod.save` does.
|
||||
|
||||
## Verification
|
||||
|
||||
The public SDK test starts a fresh playthrough, stores tool history, creates and
|
||||
readback-verifies exactly one normal anchor, proves subsequent calls do not
|
||||
rewrite it, simulates title/restart, reads the selected binding without
|
||||
allocating title identity, resumes an overworld checkpoint, preserves options,
|
||||
differentially recaptures, and confirms a later explicit NEW GAME receives
|
||||
another identity. A separate two-process disk test proves cold-start routing and
|
||||
reconstruction. Existing no-mod, storage, checkpoint, battle, and title suites
|
||||
prove additive parity.
|
||||
Reference in New Issue
Block a user