DramaticShapeVoxelMod archival mirror · read-only
2907aba6ff07b0b099090ef615ff481c5b703ff9

new menus

DramaticShape · 2026-08-08 · parent 66da6cbc4b

new menus

Changed files

CHANGELOG.md                  |  7 +++++--
 lib/SettingsMenu.lua          | 27 ++++++++++++++++-----------
 tests/dramatic_shape_test.lua | 16 ++++++++++++----
 3 files changed, 33 insertions(+), 17 deletions(-)

Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73c81ca..1d60060 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -92,8 +92,11 @@
   rows it has quietly taken away. It opens VOXEL and T-SHIFT, which came off
   the engine's list with it, and four categories: **3D WORLD** (V-GRID,
   V-CURVE, RENDER DIST, WATER, DAYTIME), **BATTLES** (3D-BTL, BACK SPRITES,
-  LET'S GO, STADIUM ROM), **PERFORMANCE** (FOREST FX, SHADOWS, AA) and
-  **VR** (VR, SMOOTH TURN).
+  LET'S GO), **PERFORMANCE** (FOREST FX, SHADOWS, AA) and **VR** (VR,
+  SMOOTH TURN). STADIUM ROM stays on that top-level menu, last: it is
+  one-time setup rather than a setting, and somebody who has been told to
+  import a cartridge should find the row where the mod begins, not two
+  levels down a category they have no reason to open until it has worked.
 
   The split is not a new opinion: it is the `full` flag each row already
   carried. `full` marks a row the FULL preset does not take away, and the
diff --git a/lib/SettingsMenu.lua b/lib/SettingsMenu.lua
index 0b19d67..9984da8 100644
--- a/lib/SettingsMenu.lua
+++ b/lib/SettingsMenu.lua
@@ -166,6 +166,22 @@ function SettingsMenu.rows(catId, game)
         }
       end
     end
+    -- ------- and the ROM import, last, on the top-level menu
+    --
+    -- An ACTION and not a setting: there is no rung to store, nothing for the
+    -- mod manager's page to persist and nothing to restore on the next boot,
+    -- so it is appended rather than living in SETTINGS.
+    --
+    -- On the ROOT menu rather than under the battles whose STADIUM rungs it
+    -- unlocks. It is a piece of one-time SETUP -- point the mod at a cartridge
+    -- and wait while it builds -- and a player who has been told to import a
+    -- ROM should find the row where the mod begins, not two levels down a
+    -- category they have no reason to open until it has worked. Last, because
+    -- the categories are what the menu is FOR.
+    local ok, importRow = pcall(function()
+      return V.require("StadiumRomPick").row()
+    end)
+    if ok and importRow then out[#out + 1] = importRow end
     return out
   end
   for _, entry in ipairs(settings) do
@@ -173,17 +189,6 @@ function SettingsMenu.rows(catId, game)
       out[#out + 1] = entry[1]:row()
     end
   end
-  -- and the ROM import, which is an ACTION and not a setting: there is no
-  -- rung to store, nothing for the mod manager's page to persist and nothing
-  -- to restore on the next boot, so it is appended here rather than living in
-  -- SETTINGS. It sits under the battles because the STADIUM rungs of 3D-BTL
-  -- are what it unlocks.
-  if catId == "battles" then
-    local ok, importRow = pcall(function()
-      return V.require("StadiumRomPick").row()
-    end)
-    if ok and importRow then out[#out + 1] = importRow end
-  end
   return out
 end
 
diff --git a/tests/dramatic_shape_test.lua b/tests/dramatic_shape_test.lua
index d50fc97..ede5de7 100644
--- a/tests/dramatic_shape_test.lua
+++ b/tests/dramatic_shape_test.lua
@@ -434,10 +434,18 @@ for key, cat in pairs(WHERE) do
       ("%s is on the %s menu"):format(key, cat))
   end
 end
--- the ROM import is an action, not a setting, and it sits with the rungs of
--- 3D-BTL it unlocks
-T.check(Menus.rows("battles")["DRAMATIC_SHAPE:stadiumRom"],
-  "and STADIUM ROM is under the battles, which is what it unlocks")
+-- the ROM import is an action, not a setting, and it is one-time SETUP, so it
+-- sits on the top-level menu where the mod begins rather than two levels down
+-- the category it unlocks
+T.check(Menus.rows(Menus.lib.ROOT)["DRAMATIC_SHAPE:stadiumRom"],
+  "and STADIUM ROM is on the top-level menu, not inside a category")
+T.check(not Menus.rows("battles")["DRAMATIC_SHAPE:stadiumRom"],
+  "and only there")
+do
+  local rootRows = Menus.lib.rows(Menus.lib.ROOT, { data = Data })
+  T.eq(rootRows[#rootRows].id, "DRAMATIC_SHAPE:stadiumRom",
+    "last on it, because the categories are what the menu is for")
+end
 end
 
 -- ------- the open menu notices when FULL is stepped onto or off