Merge branch 'my-uncles-neighbor' into dev

# Conflicts:
#	src/import/RomImporter.lua
This commit is contained in:
bryanthaboi
2026-08-20 08:28:19 -04:00
68 changed files with 18617 additions and 466 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
-- everything under src/*/gen2 reaches shared services through here. Gen 1
-- Game:load cannot consume a Gen 2 cache -- different generated tables, save
-- shape and screen registry -- so main.lua's bootGame picks this owner when
-- GameVersion.isGold(), and the two never branch into each other.
-- GameVersion.generation() == 2, and the two never branch into each other.
--
-- Boot: copyright → GameFreak Presents → GS intro stub → title
-- (tilemap + Ho-Oh flap / clouds / trails) → Oak speech (Marill + shrink)
@@ -1702,7 +1702,7 @@ function Game2:hotkey(key)
self:writeSave()
return true
elseif key == "f2" then
local loaded = Save.load("gold")
local loaded = Save.load()
if loaded then self:continueGame(loaded) end
return true
elseif key == "1" then
+18 -4
View File
@@ -1,5 +1,5 @@
-- Which game this process is running: Red (the historical default), Blue,
-- Yellow, or Gold. One source of truth for everything that differs by
-- Yellow, Gold, or Silver. One source of truth for everything that differs by
-- version -- the accepted ROM hash, the import manifest, where the
-- extracted cache lives, and the save-file suffix -- so the importer,
-- cache mount, SaveData, title screen and palette all agree.
@@ -8,7 +8,8 @@
-- saves are untouched, but its extracted cache lives under red/ like Blue,
-- Yellow, and Gold (issue #899); a legacy root cache is moved into red/ once
-- by CacheFs.migrateLegacyRedCache. All supported versions can be imported
-- and selected side by side. Gold is Gen 2 (see docs/gold-phase1.md).
-- and selected side by side. Gold and Silver are Gen 2 (see
-- docs/gold-phase1.md).
--
-- Zero requires, so it loads during love.conf and under plain Lua for tools
-- and tests. The active version is a process-global set once at boot from
@@ -61,13 +62,26 @@ GameVersion.VERSIONS = {
manifest = "tools/rom_manifest_gold.json",
cachePrefix = "gold/", -- gold/data/generated, gold/assets/generated
saveSuffix = "_gold", -- save_gold.lua / .bak / .tmp
-- The only row that carries one; absent reads as 1 (GameVersion.generation)
-- Absent reads as 1 (GameVersion.generation)
generation = 2,
},
-- Gold's engine with edition-selected data; the manifest is derived from
-- Gold's by tools/make_silver_manifest.py.
silver = {
id = "silver",
label = "Silver",
displayName = "Pokemon Silver",
launcherName = "Silver (Beta)",
sha1 = "49b163f7e57702bc939d642a18f591de55d92dae",
manifest = "tools/rom_manifest_silver.json",
cachePrefix = "silver/", -- silver/data/generated, silver/assets/generated
saveSuffix = "_silver", -- save_silver.lua / .bak / .tmp
generation = 2,
},
}
-- Launcher column order.
GameVersion.ORDER = { "red", "blue", "yellow", "gold" }
GameVersion.ORDER = { "red", "blue", "yellow", "gold", "silver" }
GameVersion.current = "red"
+1
View File
@@ -39,6 +39,7 @@ local function normalizeVersion(v)
b = "blue", blue = "blue",
y = "yellow", yellow = "yellow",
g = "gold", gold = "gold",
s = "silver", silver = "silver",
}
v = alias[v] or v
if GameVersion.VERSIONS and not GameVersion.VERSIONS[v] then return nil end
+1 -1
View File
@@ -218,7 +218,7 @@ function TouchControls.defaultLayout(ww, wh, ox, oy, scale)
local ssW = dpadW * 0.30
local margin = dpadW * 0.12
local ok, GameVersion = pcall(require, "src.core.GameVersion")
if ok and GameVersion.isGold and GameVersion.isGold() then
if ok and GameVersion.generation and GameVersion.generation() == 2 then
margin = math.max(margin, math.min(ww * 0.10, 72))
end
return {
+24 -12
View File
@@ -103,7 +103,7 @@ Save.PLAYER_STATES = {
}
local function saveNames(version)
version = version or "gold"
version = version or GameVersion.get()
-- Resolve the ACTIVE SLOT the same way SaveData does, and only fall back to
-- the flat save_<version>.lua when no slot is registered.
--
@@ -133,16 +133,22 @@ local function fs()
return love.filesystem
end
-- A fresh Gold save. `opts` carries what the intro collected: player name,
-- The blank-name fallback is the first PlayerNameArray row, which differs
-- per edition -- data/player_names.asm:12-23.
function Save.defaultPlayerName(version)
return (version or GameVersion.get()) == "silver" and "SILVER" or "GOLD"
end
-- A fresh Gen 2 save. `opts` carries what the intro collected: player name,
-- rival name, and the options the OPTION screen was left on.
function Save.newGame(opts)
opts = opts or {}
local save = {
format = Save.FORMAT,
version = "gold",
version = GameVersion.get(),
generation = 2,
player = {
name = opts.playerName or "GOLD",
name = opts.playerName or Save.defaultPlayerName(),
-- _ResetWRAM rolls wPlayerID out of hRandomSub/hRandomAdd
-- (engine/menus/intro_menu.asm:41-49).
id = opts.trainerId or rand(0, 65535),
@@ -374,10 +380,13 @@ end
function Save.normalize(save)
if type(save) ~= "table" then return nil end
save.format = save.format or Save.FORMAT
save.version = "gold"
if not (GameVersion.VERSIONS[save.version]
and GameVersion.generation(save.version) == 2) then
save.version = GameVersion.get()
end
save.generation = 2
save.player = save.player or {}
save.player.name = save.player.name or "GOLD"
save.player.name = save.player.name or Save.defaultPlayerName(save.version)
save.player.id = save.player.id or rand(0, 65535)
save.player.money = math.max(0, math.min(save.player.money or 0, Save.MAX_MONEY))
save.player.coins = math.max(0, math.min(save.player.coins or 0, Save.MAX_COINS))
@@ -753,21 +762,24 @@ end
-- copy is the witness that survives a crash mid-replace.
function Save.save(save)
if type(save) ~= "table" then return false, "no save" end
if (save.version or "gold") == "gold" then
Save.normalize(save)
local version = save.version
do
local ok, SaveData = pcall(require, "src.core.SaveData")
if ok and SaveData.activeSlot and not SaveData.activeSlot("gold") then
local id = SaveData.createSlot and SaveData.createSlot("gold")
if id and SaveData.setActiveSlot then SaveData.setActiveSlot("gold", id) end
if ok and SaveData.activeSlot and not SaveData.activeSlot(version) then
local id = SaveData.createSlot and SaveData.createSlot(version)
if id and SaveData.setActiveSlot then
SaveData.setActiveSlot(version, id)
end
end
end
local main, backup, tmp = saveNames(save.version)
local main, backup, tmp = saveNames(version)
local f = fs()
if not f then return false, "no filesystem" end
-- saveNames may now return a saves/<version>/<slot>.lua path, and
-- love.filesystem.write does not create missing parent directories.
local dir = main:match("^(.*)/[^/]+$")
if dir and f.createDirectory then f.createDirectory(dir) end
Save.normalize(save)
save.savedAt = os.time()
local encoded = SaveSerializer.encode(save)
if f.getInfo(main) then