macros: the move list deals BACK only where the moves can be read

The v0.4.3 residual: BACK was 263 of 797 macro starts and every one of them was
over an open move list. A move list whose battler the seam cannot place binds no
MOVE n at all -- move_slot_bound needs wBattleMon* -- so its pad was BACK alone,
and the only thing that button does is close the list MOVE 1 on the menu
underneath had just opened. That is section 12.10's pair with MOVE 1 standing
where NEXT used to.

So BACK is dealt on the move list only while battle.own reads, and MOVE 1 keeps
the backstop it has over the top-level menu: with nothing readable the pad is
MOVE 1 alone and its script confirms wherever the cursor stands, which is the
press that ends a turn. With the moves readable the pad is unchanged.
This commit is contained in:
acamilo 2026-09-22 08:06:19 +00:00
parent 3807fff422
commit 029a3446fb
2 changed files with 50 additions and 2 deletions

View file

@ -526,7 +526,20 @@ pub fn scene_set(scene: Scene, state: &mut dyn MacroState) -> Vec<MacroKind> {
// between them. Two buttons that undo each other with nothing else changing are section // between them. Two buttons that undo each other with nothing else changing are section
// 12.2's trap spread over two sub-states of one turn. // 12.2's trap spread over two sub-states of one turn.
Scene::Battle { own_turn: true, .. } => match battle_menu(state) { Scene::Battle { own_turn: true, .. } => match battle_menu(state) {
BattleMenu::Moves { .. } => vec![Move1, Move2, Move3, Move4, Back], // The move list. `BACK` is a button here because there is a list to leave (12.9) --
// but only while the moves can be *read*: a battler the seam cannot place leaves all
// four `MOVE n` unbound, and a pad of `BACK` alone closes the list that `MOVE 1` on
// the menu underneath had just opened. That is 12.10's pair again, with `MOVE 1` in
// `NEXT`'s place. With nothing readable the pad is `MOVE 1` alone and its script
// confirms wherever the cursor stands, which is the press that ends the turn
// (section 12.11).
BattleMenu::Moves { .. } => {
if state.battle().and_then(|battle| battle.own).is_some() {
vec![Move1, Move2, Move3, Move4, Back]
} else {
vec![Move1]
}
}
BattleMenu::Party { .. } => vec![Switch, Back], BattleMenu::Party { .. } => vec![Switch, Back],
// The bag, which is the fly's turn since 12.10. Its three answers: use the thing the // The bag, which is the fly's turn since 12.10. Its three answers: use the thing the
// cursor is on (`ITEM`), throw the ball (`THROW BALL`), or leave the list (`BACK`). // cursor is on (`ITEM`), throw the ball (`THROW BALL`), or leave the list (`BACK`).
@ -1643,7 +1656,14 @@ pub fn move_slot_bound(state: &mut dyn MacroState, kind: MacroKind) -> bool {
if index == 0 && matches!(battle.menu, BattleMenu::Main { .. }) { if index == 0 && matches!(battle.menu, BattleMenu::Main { .. }) {
return true; return true;
} }
let Some(own) = battle.own else { return false }; // And the same backstop over an **open move list** whose battler the seam cannot read
// (section 12.11). That frame used to deal `BACK` alone -- the only button on it closed the
// list `MOVE 1` on the menu underneath had just opened, which is 12.10's pair with `MOVE 1` in
// `NEXT`'s place. `MOVE 1`'s script over an open list confirms wherever the cursor stands, so
// it reads no move either, and confirming a move is what ends a turn.
let Some(own) = battle.own else {
return index == 0 && matches!(battle.menu, BattleMenu::Moves { cursor: Some(_), .. });
};
let holds = |slot: usize| -> Option<&Move> { let holds = |slot: usize| -> Option<&Move> {
own.moves.get(slot).and_then(|entry| entry.as_ref()).filter(|entry| entry.id != 0) own.moves.get(slot).and_then(|entry| entry.as_ref()).filter(|entry| entry.id != 0)
}; };

View file

@ -4318,6 +4318,34 @@ fn an_overworld_pad_is_never_one_button_that_undoes_itself() {
} }
} }
/// Section 12.11: the move list deals `BACK` only where the moves can be read.
///
/// The v0.4.3 residual (`infra/docs/macros-traps.md`): `BACK` was 263 of 797 macro starts, every
/// one over an open move list, and 142 of the run's `NEXT` starts were on a move list whose cursor
/// the seam could not place. A move list the seam cannot read the battler for binds no `MOVE n` at
/// all, so its pad was `BACK` alone -- and the only thing that button does is close the list that
/// `MOVE 1` on the menu underneath had just opened. That is 12.10's pair with `MOVE 1` in `NEXT`'s
/// place. `MOVE 1` alone confirms wherever the cursor stands, which is the press that ends a turn.
#[test]
fn the_move_list_deals_back_only_where_the_moves_can_be_read() {
let mut world = World::battle();
world.list = List::Moves(3);
world.grid = false;
world.cursor_max = 2;
assert_eq!(pad_of(&mut world), ["BACK", "MOVE 1", "MOVE 2", "MOVE 3"]);
// The battler the seam cannot place: no active slot, so `battle.own` is `None`.
world.active = None;
let pad = pad_of(&mut world);
assert_eq!(pad, ["MOVE 1"], "one button, and it ends the turn: {pad:?}");
assert!(move_slot_bound(&mut world, MacroKind::Move1));
assert!(!move_slot_bound(&mut world, MacroKind::Move2));
// And it really presses: the cursor is confirmed where it stands, which is Struggle's own
// path (row 30a) and the only reading available here.
assert_eq!(run(&mut world, MacroKind::Move1).unwrap(), MacroAbort::Done);
assert_eq!(world.pulses.last(), Some(&buttons::A));
}
/// Row 37 of `infra/docs/macros-traps.md`: a tile the cartridge pushes the fly off is not a tile /// Row 37 of `infra/docs/macros-traps.md`: a tile the cartridge pushes the fly off is not a tile
/// to stand on, and the ground beside a villager is not the villager's fault. /// to stand on, and the ground beside a villager is not the villager's fault.
/// ///