Commit Graph

1488 Commits

Author SHA1 Message Date
cecilarmitais
6174b66bdb Decomp nine more move effects and DebugRecruitingEnabled
DoMoveLowerAccuracy1, DoMoveSweetScent, DoMoveNastyPlot, DoMoveSwordsDance,
DoMoveTailGlow, DoMoveBlowback, DoMoveHiddenPower, DoMoveDecoy,
DoMovePsychoShift and DebugRecruitingEnabled.

Five more stat handlers use the StatIndex helpers from move_orb_effects.h. The
rest depart from the template:

- DoMoveBlowback passes the attacker's own action.direction, read through
  GetEntInfo.
- DoMoveHiddenPower forwards move and item_id to DealDamage with a 0x100
  multiplier, its fifth argument on the stack.
- DoMoveDecoy passes three constants, the last on the stack.
- DoMovePsychoShift guards on attacker != defender before transferring.

DebugRecruitingEnabled returns TRUE unconditionally; dungeon_recruitment.h
already declares it, so the signature is not a guess.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:48:17 -07:00
cecilarmitais
ba4d07a4b1 Decomp ten stat-boost move effects; drop a conflicting Boost*Stat extern
DoMoveTag0x1AB, DoMoveDoubleTeam, DoMoveMinimize, DoMoveAmnesia,
DoMoveBoostAttack1, DoMoveBoostDefense1, DoMoveBoostDefense2, DoMoveDefenseCurl,
DoMoveGrowth and DoMoveFamish.

Eight forward to BoostOffensiveStat / BoostDefensiveStat / BoostHitChanceStat
with ATK_STAT_IDX or SPATK_STAT_IDX and a stage count. These take
struct StatIndex by value, per move_orb_effects.h, which is why the asm loads the
global's word into r2.

src/overlay_29_0232E250.c declared those globals as s32 and BoostDefensiveStat
with an (s32, s16) tail, both contradicting move_orb_effects.h. Since the two
declarations lived in different translation units the compiler never saw the
disagreement and the ROM still matched. Replaced with an include of the canonical
header; DoMoveDefendOrder is unchanged and still matches.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate once the canonical StatIndex signature was used. Confirmed by a
matching build: build/pmdsky.us/pmdsky.us.nds: OK. None carries a region
conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:39:20 -07:00
cecilarmitais
e029b3cbd6 Decomp the dungeon RNG mode setters and nine sound helpers in overlay_29
DungeonRngSetPrimary, DungeonRngSetSecondary and DungeonRngUnsetSecondary write
prng_state::use_secondary and ::idx_secondary, using the struct dg_random.h
already defines and the extern src/dg_random.c already uses. No new types.

PlayLevelUpSound, PlayDungeonTipSound__022EB63C, PlayDungeonTipSound__022EB66C,
ov29_022EAC9C and ov29_022EACAC each tail-call sub_02017C50 with a fixed id
(1, 7, 7, 0, 5). PlaySeByIdIfNotSilence and PlayMeByIdIfNot998 guard on a
sentinel before forwarding -- 0x3F00 and 998 respectively, the latter matching
the name.

sub_02017C74 keeps the signature src/overlay_25_init.c already declares for it.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:30:05 -07:00
cecilarmitais
14d9204c54 Decomp the last ten simple move-effect wrappers in overlay_29
DoMoveHealBlock, DoMoveEmbargo, DoMoveGravity, DoMoveMagnetRise, DoMoveTrickRoom,
DoMovePowerTrick, DoMoveInvisify, DoMoveMetalBurst, DoMoveGrudge and
DoMoveDamageStealItem. This exhausts the forward-and-return-TRUE family.

Three depart from the template:

- DoMoveInvisify passes the attacker as both arguments (mov r1, r0), so
  Invisify targets the user rather than the defender.
- DoMoveGrudge and DoMoveDamageStealItem are tail calls rather than
  call-then-return-TRUE, so they are written `return f(...)`. DoMoveDamageStealItem
  forwards its own four arguments unchanged to DoMoveTakeaway, which is still asm.

DoMoveMetalBurst passes 15 to SetReflectStatus, a third distinct value for that
selector after 4 (Counter) and 10 (Rebound).

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:22:36 -07:00
cecilarmitais
80e48105c3 Decomp ten more move-effect wrappers in overlay_29
DoMoveShocker, DoMoveOneRoom, DoMoveHurl, DoMoveMobile, DoMoveSeeStairs,
DoMoveLongToss, DoMovePierce, DoMoveAquaRing, DoMoveGastroAcid and
DoMoveLuckyChant -- the same forward-and-return-TRUE template as the previous
batches. All ten helpers are undeclared in the tree and take provisional externs
written from the call site.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:16:06 -07:00
cecilarmitais
edd80366e3 Decomp ten more move-effect wrappers in overlay_29
DoMoveRebound, DoMoveStayAway, DoMovePowerEars, DoMoveSlowDown,
DoMoveSearchlight, DoMovePetrify, DoMovePounce, DoMoveTrawl, DoMoveDrought and
DoMoveHpGauge.

Two of them give a second data point for a helper argument seen earlier:
SetReflectStatus takes 10 here where DoMoveCounter passes 4, and TryWarp takes 1
where DoMoveWarp passes 0. Both third arguments are therefore selectors of some
kind; nothing here establishes what they select.

DoMoveSlowDown forwards to LowerSpeed with the same arguments as
DoMoveLowerSpeed1. The remaining helpers are undeclared in the tree and take
provisional externs written from the call site.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:03:43 -07:00
cecilarmitais
dada4f8dbb Decomp ten more move-effect wrappers in overlay_29
DoMoveWrap, DoMoveMagicCoat, DoMoveProtect, DoMoveDestinyBond, DoMoveMirrorCoat,
DoMoveSnatch, DoMoveReflect, DoMoveSeeTrap, DoMoveScan and DoMoveNoMove.

Nine forward to a two-argument helper and return TRUE; DoMoveNoMove passes a
third argument of 0. None of the ten helpers is declared in the tree, so each
takes a provisional extern written from its call site.

DoMoveSeeTrap and DoMoveScan forward to RevealTrapsNearby and RevealItems, the
first helpers in this family that are not TryInflict*Status.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:55:45 -07:00
cecilarmitais
7c02eea61b Decomp ten more move-effect wrappers in overlay_29
DoMoveLowerSpeed1, DoMoveParalyze__02328230, DoMoveParalyze__0232B434,
DoMoveConfuse, DoMovePoison, DoMoveToxic, DoMoveCurse, DoMoveWarp,
DoMoveLightScreen and DoMovePerishSong -- the same shape as the previous two
batches.

Three forward to helpers already declared in move_orb_effects.h (LowerSpeed and
TryInflictParalysisStatus), which fixes their argument meanings. The rest take
provisional externs written from the call site; TryWarp's third and fourth
parameters are left unnamed because nothing establishes what they are.

DoMoveParalyze__02328230 and DoMoveParalyze__0232B434 join
DoMoveParalyze__02326E80 from an earlier batch: three separate wrappers around
TryInflictParalysisStatus with identical arguments.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:45:54 -07:00
cecilarmitais
14f8c5116c Decomp ten more move-effect wrappers in overlay_29
DoMoveEndure, DoMoveUproar, DoMoveMist, DoMoveSafeguard, DoMoveTaunt,
DoMoveConversion2, DoMoveBlock, DoMoveWish, DoMoveIngrain and DoMoveSetDamage --
the same shape as the previous batch: forward to one status helper and return
TRUE.

All ten helpers are undeclared in the tree, so each takes a provisional extern
written from its call site. DoMoveBlock forwards to TryInflictShadowHoldStatus,
the same helper DoMoveShadowHold uses, with the same argument.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate. Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
None carries a region conditional, so US alone is sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:35:43 -07:00
cecilarmitais
f504e6ded3 Decomp ten move-effect wrappers in overlay_29
DoMoveVitalThrow, DoMoveHealStatus, DoMoveEncore, DoMoveStringShot,
DoMoveFocusEnergy, DoMoveCounter, DoMoveParalyze__02326E80, DoMoveShadowHold,
DoMoveHaze and DoMoveBoostSpeed1 -- each a thin wrapper that forwards to one
status or stat helper and returns TRUE, following DoMoveDefendOrder's shape.

Three helpers were already declared in move_orb_effects.h, which fixes the
meaning of their constant arguments; the other seven take provisional externs
written from the call site.

Authored by Claude (Opus 5) under human direction. All ten matched on the first
candidate against a context preprocessed from the real headers. Confirmed by a
matching build: build/pmdsky.us/pmdsky.us.nds: OK. None of the ten carries a
region conditional, so US alone is sufficient here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:25:09 -07:00
cecilarmitais
883be16336 Make SetLeaderAction match EU and JP
Two region-conditional calls the US build cannot see:

- SetDecoyAiTracker(GetLeader()) is US/EU only. Its declaration in
  overlay_29_022FB538.h already sits inside #ifndef JAPAN, so calling it
  unconditionally broke the JP build at compile time, not just at the checksum.
- ov29_022F2FE4() is an extra call EU makes after ov29_022E0B44() in the tail
  block, from an #ifdef EUROPE in the asm that the previous commit missed.

The remaining region differences in the replaced asm need no source change. Two
are struct-layout consequences that dungeon.h already models. The other three
looked like swapped constants but are immediate encodability: in each region the
value that is a single-instruction ARM immediate is materialized inline and the
other is derived from the pooled 0xBA3 and cached in a stack slot. 0xBA0 and
0x8E0 encode; 0xBA1 and 0x8DF do not, and the JP offset of -0x2C1 swaps which of
the pair is which.

This corrects the previous commit, which stated that JP was expected not to
match and that EU was unaffected. EU was in fact broken by it, and JP needed one
guarded call rather than a rewrite.

Authored by Claude (Opus 5) under human direction. Confirmed by matching builds
of all three targets: build/pmdsky.us/pmdsky.us.nds: OK,
build/pmdsky.eu/pmdsky.eu.nds: OK, build/pmdsky.jp/pmdsky.jp.nds: OK. EU and JP
were verified matching at the parent commit as well, so both are gains rather
than a pre-existing state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:02:31 -07:00
cecilarmitais
e8823a888e Decomp SetLeaderAction in overlay_29
Split asm/overlay_29_022F05B4.s at 0x022F2B3C; add src/overlay_29_022F0EDC.c
and its header, and both objects to main.lsf.

Type corrections the match required:
- dungeon.field_0x1d8 / field_0x1dc: paired u16 fields -> struct position
- dungeon.field_0x614: u32 -> s32, tested >= 0
- display_data.leader_target_direction_mirror: enum direction_id -> u8; the
  enum type folds the stored 0xFF to -1 and drops retail's mov r0, #0xff
- CanMonsterMoveInDirection's direction parameter: u16 -> s32; a u16 parameter
  narrows the argument at the new call site, where retail passes it unchanged

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Not verified: the JP target, which is expected not to match -- the replaced asm
carries JAPAN-specific variants beyond the message-id offset. Two volatile
stand-ins remain in the new source, and this translation unit declares
DUNGEON_PTR as a scalar where other files declare it as an array; the two are
not byte-interchangeable here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:24:58 -07:00
cecilarmitais
ac3e23890e Decomp ten more dungeon-state accessors in overlay_29
Decompile from asm:

  IsMarowakTrainingMaze       ov29_022EAEFC
  FixedRoomIsSubstituteRoom   ov29_022EAF20
  IsSecretRoom                SetUnkMusicFlag
  IsDungeonEndReasonFailure   ov29_0234969C
  ChangeDungeonMusic          ov29_023496B0

All ten reach struct dungeon through DUNGEON_PTR and every offset lands on a
named field: id at 0x748, hidden_outlaw_defeated_message at 0x2, unk_music_flag
at 0x792, dungeon_music_playing_id and field_0x2cb00 at 0x2CB06 and 0x2CB00,
gen_info.fixed_room_id at 0x40DA, and fainted_monster_dungeon_end_reason at
0x2CA66. No new types.

The constants resolve to existing enumerators that corroborate the function
names rather than being chosen to fit them: FIXED_SUBSTITUTE_ROOM is 110 and
FIXED_SECRET_ROOM is 113, which is what FixedRoomIsSubstituteRoom and
IsSecretRoom compare against; DAMAGE_SOURCE_ESCAPE is 633, the 0x279 in
IsDungeonEndReasonFailure. IsMarowakTrainingMaze tests the range
DUNGEON_NORMAL_FLY_MAZE to DUNGEON_EXPLORER_MAZE inclusive, 180 to 190.

IsDungeonEndReasonFailure reads its union member through an explicit (s16) cast.
enum damage_source_non_move declares no negative enumerator, so under -enum min
it is unsigned and compiles to ldrh with an unsigned comparison, where the target
has ldrsh and a signed one. The cast is local and reversible; adding a sentinel
to a shared enum, as was done for monster_id earlier on this branch, would not
be.

Two of the ten merged into src/run_dungeon_1.c, which already declares
DUNGEON_PTR as an array and reaches it as DUNGEON_PTR[0]. They follow that file's
convention rather than introducing a second spelling; both forms were checked on
a scratch and produce identical bytes.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 02:09:14 -07:00
cecilarmitais
bb3a61e8fe Decomp ten dungeon-state accessors in overlay_29
Decompile from asm:

  GetForcedLossReason             GetHiddenStairsField
  SetForcedLossReason             SetHiddenStairsField
  AreLateGameTrapsEnabledWrapper  GetHiddenFloorField
  GetSuccessfulExitTracker        SetHiddenFloorField
  SetDungeonEscapeFields          GetMinimapData

All ten read or write struct dungeon through DUNGEON_PTR, and every offset lands
on a field dungeon.h already names: forced_loss_reason at 0x14,
successful_exit_tracker at 0x18, end_floor_no_death_check_flag at 0x8,
minimap_display_data at 0x1A264, and three fields of gen_info at 0x40C4 --
hidden_stairs_type, hidden_floor_type and fixed_room_id. No new types.

These are verified against the preprocessed dungeon.h context built in the
previous commit rather than a hand-written struct, which is why nine of the ten
matched on the first candidate instead of appearing to and then failing the
build. The setup cost of that context is now paid, so any overlay_29 function
reaching DUNGEON_PTR is cheap to verify; a scan finds 59 more small ones.

GetMinimapData needed the null check written as a conditional expression rather
than an if with an early return. The target predicates the whole thing --
addne twice for the non-null path, then moveq for the null one -- where the
statement form branches. That is the same predicate-ordering effect recorded on
the LFO handlers in 909484fb, in the direction that wants the expression form.

The US and JP offsets differ for the gen_info and minimap fields, which the asm
carries as #ifdef JAPAN. Nothing version-specific is needed in the C: the fields
are named the same in both, so dungeon.h resolves each build to its own offset.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 01:51:59 -07:00
cecilarmitais
5c895f8a1d Decomp nine mission predicates against a faithful scratch context
Decompile from asm:

  IsOutlawMonsterHouseFloor    IsDestinationFloorWithMonster
  IsGoldenChamber              MissionTargetEnemyIsDefeated
  IsJirachiChallengeFloor      SetMissionTargetEnemyDefeated
  IsDestinationFloorWithFixedRoom
  IsDestinationFloorWithHiddenOutlaw
  GetMissionEnemyMinionGroup

These are the functions the two previous commits could not land. The obstacle was
the scratch context, not the C, and this commit is the result of replacing it.

The context is now the real headers rather than a hand-written struct: mwccarm
-E over a file including global.pch, dungeon.h and mission.h, with
LM_LICENSE_FILE set, gives 14500 lines reproducing what the build actually sees.
Two things that took finding. global.pch is required -- without it util.h fails
at typedef s32 fx32_8, because s32 comes from nitro.h, which the build supplies
through -prefix rather than through any include in dungeon.h. And -E consumes
every macro, so TRUE, FALSE and NULL have to be restored by hand afterwards;
their values are taken from lib/include/nitro/types.h, NULL being ((void *)0) on
the C branch.

Against that context the earlier verdicts change. All eight functions landed in
the two previous commits still score 0, which is the check that the new oracle
agrees with the build. Eight of the thirteen unlanded ones score 0 unchanged and
are landed here as written. GetMissionEnemyMinionGroup does not: the real headers
let the compiler fold the +1 of enemy_species[index + 1] into the load
displacement, one instruction shorter than the target. Binding index + 1 to a
local first blocks the fold and scores 0, which is what is landed.

Four remain unmatched and are genuinely unmatched rather than mis-measured:
IsCurrentMissionType and IsCurrentMissionTypeExact at 515, where the target
branches to separate return blocks and every spelling tried so far produces
predicated moves; GetItemToRetrieve at 665 and GetItemToDeliver at 1530. Their
scores are identical under both contexts, so the context was never their problem.

The mission_type arguments use the enumerators from mission.h -- ARREST_OUTLAW,
EXPLORE_WITH_CLIENT, TAKE_ITEM_FROM_OUTLAW and CHALLENGE_REQUEST -- because the
build rejects the implicit conversion from int. The values agree with the
function names rather than being chosen to fit them.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 01:38:01 -07:00
cecilarmitais
0fbdb57210 Decomp four more mission accessors; find the cause of the batch failure
Decompile from asm:

  ov29_02349688                 0x02349688
  SetTargetMonsterNotFoundFlag  0x023496C4
  GetTargetMonsterNotFoundFlag  0x023496D8
  GetMissionTargetEnemy         0x02349620

The first three read and write the two dungeon flags at 0x1 and 0x3;
GetMissionTargetEnemy returns enemy_species[0]. No new types.

The previous commit left thirteen functions unlanded because they failed four
checksums with no compile error, and recorded the elimination so far. Bisecting
found a one-function reproducer, GetMissionEnemyMinionGroup, and disassembling
the object the real build emits identifies the cause.

The scratch and the real build disagree, and the scratch was wrong. For
enemy_species[index + 1] the target computes the index first --
add r0, r0, #1, then add r0, r1, r0, lsl #1, then add #0x700 and ldrsh [r0,
#0x6e]. The real build folds the constant into the displacement instead, giving
add r0, r1, r0, lsl #1, add #0x700, ldrsh [r0, #0x70], one instruction shorter
and the same address. Same compiler, same flags, same C: the difference is the
context. The scratch used a cut-down struct dungeon carrying only the offsets
these functions touch, and against that struct the compiler emits the target's
form; against the real header it folds.

That invalidates the scratch verification for the whole cluster, not just this
function. Score 0 against an invented struct is not evidence about a build that
uses the real one, even when every offset and size matches -- and they do, which
was confirmed separately by compile-time assertions against the real headers.

So the thirteen are not near-matches to iterate on; they need re-verifying
against a faithful context before any C is judged. A preprocessed context is
obtainable -- mwccarm -E on a file including dungeon.h and mission.h, with
LM_LICENSE_FILE set, yields 9200 lines that reproduce the real build's view --
but that output does not yet compile as scratch context on its own, failing at a
typedef, so wiring it up is unfinished.

The four here were each verified by a matching build rather than by the scratch:
three of them alone, GetMissionTargetEnemy alone, and all four together.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 01:08:34 -07:00
cecilarmitais
dfbb82363f Decomp four mission-destination accessors in overlay_29
Decompile from asm:

  ov29_02349188          0x02349188
  GetMissionDestination  0x023491A4
  ov29_023491B8          0x023491B8
  IsDestinationFloor     0x02349208

All four reach DUNGEON_PTR->mission_destination, whose type is already fully
described in dungeon_mode.h. No new types.

This is the surviving part of a larger batch. Seventeen functions from this
cluster reached score 0 on a scratch; these four build matching in the tree and
the other thirteen do not, failing four checksums -- main, OVY_10, OVY_29 and
OVY_31 -- with no compile error. The thirteen are not in this commit.

What is known about that failure, so the next attempt does not repeat the work.
The struct layout is not the cause: compile-time assertions against the real
headers confirm mission_destination at 0x760, the two dungeon flags at 0x1 and
0x3, target_enemy_is_defeated at 0x1B, fixed_room_id at 0x16, enemy_species at
0xE, and sizeof(enum mission_type) == 1 and sizeof(enum monster_id) == 2 -- all
the offsets the scratch context assumed. A build of HEAD with no changes is
clean, so the regression is in the batch. Landing these four alone is clean.
Adding nine more, none of which use a mission_type enumerator, reproduces the
failure, which rules out the enumerator conversions that were the initial
suspicion. No pre-existing declaration of any of the seventeen names exists
elsewhere in the tree.

That main.sbin fails alongside the overlay points at something structural rather
than at a function body -- most likely the overlay size or the symbol exports in
the regenerated .inc files, since extract_function.py rewrites the .public list
when it splits an overlay .s. The byte-level diff of OVY_29 against a clean build
is the next step, using the cmp -l technique that located the rodata problem in
0f2e0ea8.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 00:21:33 -07:00
cecilarmitais
26611d6672 Decomp seventeen storage-selection accessors
Decompile from asm, all reaching UNK_STORAGE_STRUCT_0X14:

  sub_02043148  sub_0204329C  sub_020433E0  ClearStorageSelectedItemTable
  sub_0204316C  sub_02043380  sub_02043400  CountSelectedStorageItems
  sub_02043218  sub_02043398  sub_02043434  GetFirstSelectedStorageItemIndex
  sub_0204322C  sub_020433C0  sub_02043468
  sub_0204323C  sub_02043254

struct unk_020AFEE0 was introduced two dozen commits ago holding only the pointer
at 0x8 and filler before it. Its first eight bytes are now split into what these
functions read: halfwords at 0x0 and 0x2 and a pointer at 0x4, with a word at
0x10 added past the existing end. Nothing that already used field_0x8 moves.

The object behind that pointer is new, and its size is derived rather than
guessed. Its s16 array at 0x4 is bounded by 0x3e8 in three separate loops, which
is 1000 entries and ends at 0x7D3; the signed byte the collection-menu wrappers
read sits at 0x7D4, immediately after it. Two further fields at 0x910 and 0x18BC
come from sub_020430F4. Filling the gaps brings the struct to exactly 0x18C0,
which is the size in upstream's own name for its allocator,
InitUnkStorageStruct0x18c0. That agreement is the reason the layout is worth
trusting; the field names remain placeholders because nothing here establishes
meaning.

Fourteen of the seventeen matched on the first candidate. The other three are
loops whose index and pointer landed in swapped registers, with every
instruction otherwise correct, and all three were closed by reordering the local
declarations -- the same effect recorded in the previous two commits. All
orderings were enumerated rather than guessed: two locals for sub_02043254 and
GetFirstSelectedStorageItemIndex, three for CountSelectedStorageItems.

Eight callees remain asm and their prototypes are provisional, declared in the
shared header rather than repeated per file. None of them was declared anywhere
else in the tree. Two are typed from evidence rather than convention:
IsCollectionMenuActive and IsCollectionMenuState3 return bool8 because both
callers mask with and r0, r0, #0xff.

sub_020430F4 was attempted and is not included. It reaches the same struct and
its two extra fields are what sized it, but it stands at 495 after correcting
sub_0202C654 to the four arguments the target passes -- the fourth is a zero in
r3 that the first reading missed. Left in asm rather than landed as a near-match.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:25:55 -07:00
cecilarmitais
72b366b366 Decomp SetPokemonBattled and GetNbItemAcquired
Decompile from asm:

  SetPokemonBattled  0x0204FE58
  GetNbItemAcquired  0x020503CC

SetPokemonBattled is the battled counterpart of SetPokemonJoined, already
decompiled in src/main_0204FDFC.c, and merges into that file beside it. It
differs only in which completion flag it sets and which flag array it indexes,
so the existing function is the template rather than anything derived here.

GetNbItemAcquired counts the set bits of items_acquired_flags across 0x580
entries. The pointer is hoisted to a local because the target loads it once
before the loop, and the word and bit indices use signed division and remainder,
which is what produces the asr/lsr/ror sequences the target has.

Both matched on the first candidate.

Seven adventure-log functions remain in asm. Four are large --
ComputeSpecialCounters at 144 instructions, CopyLogTo at 122, CopyLogFrom at 120
and ClearAdventureLogStruct at 74. The other three are not large but index the
region at 0x260 as an array spanning special_challenge_flags and the five
sentry_duty_game_points that follow it, mixing signed and unsigned division of
the same index within one function; expressing that without either misdescribing
the struct or writing something the compiler will not reproduce needs more care
than this batch had left.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:11:36 -07:00
cecilarmitais
4f46fed938 Decomp thirty adventure-log accessors
Decompile from asm, all of them accessors on ADVENTURE_LOG_PTR:

  SetAdventureLogStructLocation  SetAdventureLogCompleted   IncrementNbEvolutions
  SetAdventureLogDungeonFloor    GetAdventureLogCompleted   GetNbEvolutions
  GetAdventureLogDungeonFloor    IsAdventureLogNotEmpty     IncrementNbEggsHatched
  IncrementNbDungeonsCleared     GetNbDungeonsCleared       GetNbEggsHatched
  IncrementNbFriendRescues       GetNbFriendRescues         GetNbPokemonJoined
  GetNbMovesLearned              SetVictoriesOnOneFloor     GetVictoriesOnOneFloor
  GetNbPokemonBattled            IncrementNbBigTreasureWins SetNbBigTreasureWins
  GetNbBigTreasureWins           SetNbRecycled              GetNbRecycled
  IncrementNbSkyGiftsSent        SetNbSkyGiftsSent          GetNbSkyGiftsSent
  IncrementNbFainted             GetNbFainted               GetSentryDutyGamePoints

No new types. struct adventure_log already exists in include/adventure_log.h with
every field these touch named and offset-commented, and struct dungeon_floor_pair
in dungeon.h; every offset in the asm lands on a named field. That header is the
reason a batch this size was tractable: the only modelling needed was reading
which field each function touches.

Two orderings decided four of the thirty, both instances of the rule added to
MATCHING_TIPS in the previous commit. IsAdventureLogNotEmpty needed its loop
index declared before the pointer local, not after -- with the pointer first the
two land in swapped registers, at 35. The three Set functions needed the
completion-flag write placed before the clamp rather than after: written in the
obvious order the clamp is emitted ahead of the first pointer load and the two
literal-pool words come out reversed, at 250 each. Reordering the statement fixed
all three.

The counters clamp at 0x000F423F, decimal 999999, and each function sets one bit
of completion_flags[0]. The exact shape differs per function and is reproduced
rather than normalised: three increment then clamp, two clamp then increment, and
the three setters write the flag before storing the value. Those differences are
what the target does.

The clamp comparisons are signed even though the fields are u32, so each is
written with an explicit (s32) cast rather than retyping a shared struct.

ADVENTURE_LOG_PTR and _022AB69C move into adventure_log.h. src/main_0204FDFC.c
declared the pointer itself, from before any of this was decompiled; that line is
replaced by the header it already includes, and the object rebuilds unchanged.

Nine adventure-log functions remain in asm and are not in this commit:
ClearAdventureLogStruct, ComputeSpecialCounters, CopyLogTo, CopyLogFrom,
SetItemAcquired, GetNbItemAcquired, SetChallengeLetterCleared, SetPokemonBattled
and SetSentryDutyGamePoints.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:05:26 -07:00
cecilarmitais
59c4a95e44 Decomp the volume and pan fade track events
Decompile from asm:

  DseTrackEvent_VolumeFade  0x020723C0
  DseTrackEvent_PanFade     0x02072668

Both were deferred near-matches. Each reads a signed byte as the fade target and
a little-endian pair as its duration in ticks, writes the target, and then either
snaps current to it when the duration is zero, zeroes the duration when there is
nothing to travel, or divides the distance by the duration to get the per-tick
delta. The two differ only in which struct dse_fade they address -- volume at
0x2c, pan at 0x3c -- both of which already exist in dse.h.

What closed them was declaration order, and in the opposite direction to the
obvious one. The target loads ptr_next_byte[2] first, then [0] and [1]; writing
the target expression first, so that its load comes first in the source, leaves
the loads in ascending address order at score 620. Declaring ticks first and the
target second emits them in the target's order and scores 0. So for this
scheduler the later-declared expression's loads are issued first, and the fix is
to write the declarations in the reverse of the order the asm reads them.

Seven other spellings of the same two statements were tried -- explicit locals
for each byte in the target's load order, s8/s16/s32 intermediates, an inline
cast, and a pointer-cast subscript -- and every one of them stayed at 620. Only
the declaration swap moves it.

The lib/DSE headers extract_function.py generates do not include dse.h, so both
were given it; every other header in that directory already does.

Three deferred DSE functions remain, and this commit does not close them.
DseTrackEvent_TuningFade is the same family with a bend fade plus the SetTuning
tail; hoisting container out of channel before the flag test takes it from 935 to
760, which is progress and not a match. DseTrackEvent_SetupKeyBendLfo is
unchanged in substance: its instructions have matched for some time and only
register assignment differs. All 120 orderings of its five byte locals were tried
this time, along with eleven structural variants, and the best is 55 rather than
the 65 it sat at; the earlier note suggested exhaustive permutation as the untried
move, and it has now been tried and does not close it.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 15:48:24 -07:00
cecilarmitais
702c4c85fe Decomp DeleteWindow and seven window state helpers
Decompile from asm:

  sub_02027A08  0x02027A08      sub_0202811C  0x0202811C
  sub_02028080  0x02028080      sub_0202812C  0x0202812C
  sub_020280C0  0x020280C0      DeleteWindow  0x02028194
  sub_0202810C  0x0202810C      sub_0202822C  0x0202822C

DeleteWindow explains _022A7A74, which the previous commit landed two accessors
for without knowing what it held. On deleting a window it walks the other
nineteen, and for every active one on the same background takes
base_tile + width * height, keeping the largest and a floor of 1, then stores
that at _022A7A74[bg_id]. So the pair is a next-free-tile watermark per
background, which also explains NewWindowScreenCheck setting the entry to 1 when
a background has no windows left: that resets the allocator.

Four of the eight return their callee's result rather than void, and only one of
them shows it. sub_020280C0 never touches r0 after calling sub_02027E30, which
is only consistent with r0 staying live to the return; typed void it scored 90,
typed s32 it scores 5, and the 5 is the literal-pool naming described below.
sub_0202810C and sub_0202811C are tail calls that score 0 either way, so their
own bytes settle nothing; they are typed s32 to match, and a reviewer should
read those two as following the family rather than as read off the target.

DeleteWindow needed two things past the obvious form. The background comparison
is bg == p->template.bg_id, not the reverse, which is worth 5 on its own. And
its four locals have to be declared i, top, p, bg: every one of the 24 orders
was tried, they range from 0 to 140, and only that one reaches 0. The
instructions were already identical at 35 -- the entire remaining difference was
which register held bg and which held i.

sub_0202812C and sub_02027A08 land at 10, and sub_020280C0 at 5, all of it the
literal-pool symbol naming: the target writes _022A8990, _022A8992 and _022A88E4
where C indexing WINDOW_LIST emits WINDOW_LIST+0xb4, +0xb6 and +0x8. Same
address, identical bytes once linked, at a flat 5 per word. The check for this
is in MATCHING_TIPS; the build is what settles it.

struct unk_020AFD4C is introduced for the 12-byte object at that address, sized
by the gap to _020AFD58. Only its word at 0x8 is touched here, a bitmask that
five of these functions set a bit in, indexed by bg_id.

DeleteWindow had a provisional declaration in include/main_0202AAA8.h from the
menu commits, with three decompiled callers. That is replaced by an include of
the new header and all three objects rebuild.

sub_0202836C now has a fifth declaration in the tree, and the existing four do
not agree: overlay_15 says int, overlay_24_end says s32, and overlay_24_init and
overlay_25_init say s8. It is declared s32 here, in the caller's own header
rather than in window.h, so that no overlay including window.h sees a conflicting
one. They should collapse into a single header when the callee lands.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build with the three DeleteWindow callers rebuilt:
build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 04:42:41 -07:00
cecilarmitais
7f6977e268 Decomp ten window accessors, including UpdateWindow and ClearWindow
Decompile from asm:

  sub_02027624   0x02027624      ClearWindow    0x02027B58
  UpdateWindow   0x02027AF0      sub_0202825C   0x0202825C
  sub_02027B1C   0x02027B1C      sub_02028270   0x02028270
  sub_020282C8   0x020282C8      sub_020282F4   0x020282F4
  sub_0202830C   0x0202830C      sub_02028324   0x02028324

Four bss symbols these functions index at stride 0xE0 are aliases into
WINDOW_LIST[0], not separate objects: _022A88E4 is +0x8, _022A88F0 is +0x14,
_022A88F8 is +0x1C and _022A8994 is +0xB8. The block from WINDOW_LIST to the end
of _022A8994 measures 0xB8 + 0x10C8 = 0x1180 = 20 * 0xE0, and every one of those
boundaries falls on a field boundary of the Window layout added earlier. That is
independent corroboration of the layout, from the linker rather than from the
struct definition.

Five of the ten do not reach score 0 on a scratch, and are landed anyway. Their
instructions all match; the only difference is the literal-pool word, which the
target names _022A8994 where C indexing WINDOW_LIST emits WINDOW_LIST+0xb8. Same
address, same relocation target, identical bytes once linked -- the scratch diff
compares symbol names, at a flat 5 per word, so ClearWindow and sub_02027B1C
report 10 and the three single-literal ones report 5. The matching build is what
settles it.

Which literal appears is decided by whether the index is constant. A constant
offset keeps the base symbol in the pool and puts the offset in the instruction;
a variable index folds the offset into the pool word because the index has to be
scaled separately. That rule cost a build here: NewWindowScreenCheck stores to
_022A7A6C at #8 and #0xa, and rewriting those two lines as _022A7A74[0] and [1]
to remove an apparent duplicate changed the pool word and failed main.sbin, with
no compile error and no overlay cascade. It is reverted, and both declarations
are kept, because the target keeps both.

sub_020282C8 writes width*8 and height*8 through an out parameter. It reuses
Point rather than adding a second two-s32 struct; the layout is identical and
the type already exists, but a size is not a coordinate, so read that as
structural reuse and not as a claim about meaning.

Two prototypes in the tree contradicted the ones landing here and are replaced
with includes: overlay_31_02382820.c declared UpdateWindow itself, and
overlay_31_02383880.c declared sub_020282F4 taking s8. Both objects are rebuilt
to confirm the retyping is byte-neutral rather than assumed.

Two more are left alone and are worth a later pass. overlay_13_0238BDA8.c
declares UpdateWindow and sub_02027B1C taking s8, and overlay_25_init.c declares
both taking char *; the second is a genuine mistyping, since the value it passes
is a window id, but correcting it means retyping ov25_0238B414's own parameter
and its callers, which is a larger change than this batch. The s8 declaration in
overlay_13 carries an upstream annotation identifying sub_02027B1C, which is
worth keeping in place rather than deleting to make a byte-neutral edit.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build with both overlay_31 objects rebuilt: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 03:51:32 -07:00
cecilarmitais
b6faa49387 Decomp NewWindowScreenCheck, SetScreenWindowsColor and the palette getter
Decompile from asm:

  NewWindowScreenCheck             0x02027648
  GetPaletteBaseAddress__020278A8  0x020278A8
  SetScreenWindowsColor            0x02027A80

NewWindowScreenCheck counts the active windows on each background before
delegating to NewWindow, and sets a flag when either screen has none. It reads
is_active as a signed byte at 0xB6 and bg_id at 0x8, both named by the Window
layout added in the previous commit, so the loop reads as what it does.

It walks with a cursor rather than indexing. Indexing WINDOW_LIST[i] emits an
mla per iteration; the target advances a pointer by 0xE0 at the bottom of the
loop. Its two counters also have to be assigned in the order sub then main --
declaration order alone was not enough, and every permutation of the four locals
left between 20 and 95.

SetScreenWindowsColor's colour parameter is s32, not u8, and that distinction is
not visible in its own bytes: both score 0 against it, because the store is a
strb either way. It is visible one level up. SetBothScreensWindowsColor, landed
matching in the previous commit, passes its argument straight through; a u8
parameter makes the compiler truncate at that call site, which changes an object
that already matched, changes the size of main.sbin, and shifts every overlay
after it. The first build of this batch failed 33 checksums for that reason,
with no compile error and nothing wrong in the function being added.

GetPaletteBaseAddress__020278A8 reads through _020AFC70, which include/
main_02064FFC.h already declares as u8 *. It is declared the same way here
rather than being given a second, differently typed declaration.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 03:25:19 -07:00
cecilarmitais
30ea1f34f0 Replace the Window placeholder with its real layout
struct Window was landed two commits ago as mostly filler: the fields those
functions happened to touch, and padding to reach the 0xE0 stride GetWindow
indexes by. This replaces it with the full 0xE0 layout, supplied by the project
maintainer, together with the four types it is built from -- WindowTemplate,
WindowBlock, WindowTile and CursorParams, plus Point.

Four things the layout has to satisfy, and does:

  sizeof is 0xE0, the stride GetWindow multiplies by.
  is_active is an s8 at 0xB6, which is where DeleteWindow does ldrnesb.
  WindowTemplate.width is at 0x06, where the previous partial struct, inherited
    from overlay_31_02382820.h, already had width.
  x, y, width and height at 0x04 through 0x07 account for GetWindowRectangle
    exactly: it writes y*8, y*8 + height*8, x*8 and x*8 + width*8, so its output
    is top, bottom, left, right. That function is rewritten against the named
    fields rather than shifting anonymous bytes.

Three choices worth noting for review. The struct keeps its tag, so both
struct Window and Window resolve and no existing use changes. GetWindowContents
still returns void * rather than the u32 the field is typed as, because all 22
of its callers assign the result to a pointer; the cast is a no-op and avoids 22
integer-to-pointer conversions. And width moving inside the template means
overlay_31_02382820.c reads window2->template.width, the one source change the
layout forces.

The field names here are the maintainer's, not derived in this commit. The
offsets and widths are checkable against the asm; the names are not, and should
be read as supplied rather than proven.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction, from a struct definition
supplied by the maintainer. Confirmed by a matching build with
overlay_31_02382820.o rebuilt: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 02:48:56 -07:00
cecilarmitais
643fd53bcf Decomp GetWindowContents, GetWindowRectangle and SetBothScreensWindowsColor
Decompile from asm:

  SetBothScreensWindowsColor  0x02027A80
  GetWindowRectangle          0x02028284
  GetWindowContents           0x0202833C

GetWindowContents is the function the menu accessors landed over the last several
commits all call. It reads the pointer at 0xC of a WINDOW_LIST entry, and its
callers cast that to whichever menu layout they use.

struct Window gains the fields these two touch: bytes at 0x4, 0x5, 0x7 and 0x8
around the existing width at 0x6, and the pointer at 0xC. The trailing padding
shrinks to keep the entry at 0xE0, which is the stride GetWindow indexes by.

GetWindowRectangle converts four of those bytes into a rectangle in pixels,
multiplying each by 8, writing top and bottom from 0x5 and 0x7 and left and
right from 0x4 and width. Its out parameter has no existing type and becomes
struct unk_02028284.

GetWindowContents needed the entry address bound to a pointer local. Returning
WINDOW_LIST[window_id].field_0xC folds the field offset into the literal pool
entry, giving a .word WINDOW_LIST+0xc and an indexed load; the target keeps
WINDOW_LIST as the literal and reaches the field with mla plus ldr [r1, #0xc].

SetScreenWindowsColor is still asm and had no declaration in the tree, so its
prototype is provisional.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 02:28:23 -07:00
cecilarmitais
aade0cdfc8 Decomp GetWindow; retype overlay_31's window callbacks as ids
Decompile from asm:

  GetWindow  0x020275F8

It indexes WINDOW_LIST with a stride of 0xE0, so its argument is a window id
rather than a pointer, and its result is the address of one entry.

overlay_31_02382820.c declared it as taking a struct Window * and passed one,
which cannot be what the target computes: multiplying a pointer by 0xE0 and
adding it to WINDOW_LIST is meaningless. The value it passes is an id, and the
same value goes to DrawTextInWindow and UpdateWindow, so those take ids too.
This retypes the three window callbacks and both of those prototypes
accordingly, along with the callback type CreateTextBox stores.

That retyping is byte-neutral -- a pointer and an id both travel in r0 -- but
overlay_31_02382820.o was already matching, so it is rebuilt here to confirm
rather than assumed.

struct Window keeps its existing return type and gains padding to its real 0xE0
size, which is what lets &WINDOW_LIST[id] stride correctly. Its first seven
bytes are unchanged, so the width field overlay_31 reads is where it was. The
struct moves from the overlay's header into include/window.h, which already
exists and is where a main-binary function returning one can reach it; the
overlay includes it rather than the main binary including an overlay header.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build with overlay_31_02382820.o rebuilt:
build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 02:09:00 -07:00
cecilarmitais
2472ad54f7 Decomp four allocation, keyboard and area-name accessors
Decompile from asm:

  AllocUnkBagStruct        0x02042B98
  FreeUnkBagStruct         0x02042BBC
  SetAreaNameBoxState3     0x0202FD3C
  GetKeyboardStringResult  0x0203755C

AllocUnkBagStruct and FreeUnkBagStruct manage the pointer at offset 8 of
UNK_STORAGE_STRUCT_0XC, the field the selection accessors in the previous commit
read. The allocation is 0xC8 bytes, which is 0x32 words -- the same count
ClearBagSelectedItemTable zeroes and the same inventory size the bag walkers
use.

MemAlloc is still asm and has no owning header, so its prototype is provisional
and declared beside the caller, matching the signature overlay_31_02382820.c
already uses. That is now a second declaration of it; both should collapse into
one header when MemAlloc lands.

SetAreaNameBoxState3 writes a word at 0xA0 of the window contents, so the filler
before 0x198 in struct unk_0202AAA8 is split to expose it. Nothing after it
moves.

GetKeyboardStringResult reads through the unnamed pointer _020AFDF0, which keeps
its placeholder name, into a word at 0xF8 of whatever it points at.

GetWindow was looked at and deliberately left in asm. It indexes WINDOW_LIST
with a stride of 0xE0, so its argument is an index, but overlay_31_02382820.c
declares it as taking a struct Window * and passes one. Landing it correctly
means retyping that caller, and struct Window there describes only its first 7
bytes, so the two disagree about more than this function.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 01:49:58 -07:00
cecilarmitais
80c33c3540 Decomp six storage-selection and notify-note accessors
Decompile from asm:

  ClearBagSelectedItemTable   0x02042AF8
  IsBagItemIndexSelected      0x02042B84
  IsStorageItemIndexSelected  0x02043568
  GetNotifyNote               0x020484A0
  SetNotifyNote               0x020484B0
  EventFlagBackupVeneer       0x02048758

UNK_STORAGE_STRUCT_0XC and UNK_STORAGE_STRUCT_0X14 already carry those names in
asm. Only their field at offset 8 is touched here, a pointer to an array, so
they are typed with placeholder structs holding that field and filler before it.
The element widths come from the loads: words for the bag table, bytes for the
storage one.

Their struct names use the globals' own addresses, 0x020AFED4 and 0x020AFEE0,
derived by walking back from UNK_STORAGE_STRUCT_0X8_PTR_1, which sits at the
labelled address 0x020AFEF4, through the two definitions' byte counts. That is
arithmetic on the data rather than a lookup, and a reviewer may want to confirm
it; nothing in this commit depends on the names being right, only on the layout.

ClearBagSelectedItemTable zeroes 0x32 entries, the same count the bag walkers in
earlier commits use for an inventory.

EventFlagBackup is still asm and had no declaration in the tree, so its
prototype is provisional.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 01:36:47 -07:00
cecilarmitais
f7fbcd80ba Decomp six collection-menu setters; add a second window-contents view
Decompile from asm:

  SetCollectionMenuField0x1BC  0x0202C5E0
  SetCollectionMenuField0x1C8  0x0202C794
  SetCollectionMenuField0x1A0  0x0202C7A8
  SetCollectionMenuField0x1A4  0x0202C7BC
  SetCollectionMenuVoidFn      0x0202C7D0
  SetCollectionMenuField0x1B2  0x0202D0D8

These reach the same buffer as the parent, simple and advanced menu accessors,
through the same GetWindowContents, but they do not share its layout: this set
writes a word at 0x1A0 where CheckParentMenuField0x1A0 reads a byte there, and a
byte at 0x1B2 which falls inside the word the advanced text box uses at 0x1B0.

So they are given their own view, struct unk_0202C5E0, named for the
lowest-addressed function that takes it, rather than forcing one struct to
describe both. GetWindowContents returns void *, so each menu type casting the
buffer to its own layout is consistent with what the target does; a single
merged struct would have to assert that the two layouts agree, which the stores
show they do not.

Two of the offsets are inferred from the store width alone and nothing else:
0x1A8 is written with a word and the function is named VoidFn, so it is typed
void *, but the target would look the same for any pointer or 32-bit value.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 01:16:24 -07:00
cecilarmitais
ec362491e1 Decomp six menu and text-box setters
Decompile from asm:

  SetSimpleMenuField0x1AC       0x0202BA0C
  CloseAdvancedMenu             0x0202BC44
  IsAdvancedMenuActive2         0x0202BCBC
  SetAdvancedTextBoxField0x1C4  0x020307A4
  SetAdvancedTextBoxField0x1C2  0x0203083C
  SetAdvancedTextBoxState5      0x0203088C

struct unk_0202AAA8 gains fields at 0x1AC, 0x1BC, 0x1C2, 0x1C3 and 0x1C4. The
filler that stood between 0x1A8 and 0x1B0 is split to expose 0x1AC; nothing
after it moves.

CloseAdvancedMenu differs from the simple- and parent-menu versions: it frees
only the window contents, not the pointer at 0x198 those two free first. That is
what the target does and is not an omission here.

IsAdvancedMenuActive2 tests the same states as IsSimpleMenuActive, 7 and 8, and
like it binds the state to a local so the field is loaded once.

SetAdvancedTextBoxField0x1C2 stores a constant 1 rather than an argument, so it
takes only the window id.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 01:06:05 -07:00
cecilarmitais
363d3edb85 Decomp GetSelectedMenuItemIdx and five window-id wrappers
Decompile from asm:

  GetAdvancedMenuCurrentOption    0x0202BCFC
  GetWindowIdSelectedMenuItemIdx  0x0202C748
  IsDialogueBoxActive             0x0202F180
  GetWindowIdPageStart            0x02030A18
  GetAdvancedTextBoxFlags2        0x02030A40
  GetSelectedMenuItemIdx          0x02032578

GetSelectedMenuItemIdx computes the absolute item index from the paging struct
added in the previous commit, current page times items per page plus the
selection within the page, which the target folds into one mla.

GetWindowIdPageStart, GetWindowIdSelectedMenuItemIdx and
GetAdvancedMenuCurrentOption resolve a window and forward to the paging
functions at offset 4 of its contents. The last two have identical bodies and
are decompiled as written rather than one calling the other, since that is what
the target does.

struct unk_0202AAA8 gains two fields the other two functions read: a byte at 0x8
and a word at 0x1B0. The filler before 0x198 is split to expose the first
without moving anything after it.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:57:43 -07:00
cecilarmitais
75c76fa71f Decomp seven menu paging getters
Decompile from asm:

  GetSelectedItemOnPage  0x02032558
  GetCurrentPage         0x02032560
  GetPageStart           0x02032568
  GetTotalNumMenuItems   0x0203258C
  GetNumItemsOnPage      0x02032594
  GetMaxItemsOnPage      0x0203259C
  GetTotalNumPages       0x020325A4

Six read one word each from a struct with no existing type, introduced here as
struct unk_02032558 with placeholder fields at 0xBC through 0xD0. GetPageStart
multiplies two of them, current page by max items per page, which is consistent
with the names but is an observation about the arithmetic rather than something
the bytes label.

GetSelectedItemOnPage was declared provisionally in include/main_0202AAA8.h last
commit, taking void *, because it was still asm. That declaration is removed and
its caller now includes the real header. The argument reaching it there is a
void * offset by 4 from the window contents, which converts implicitly; the
caller's object is rebuilt here and still matches.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:38:40 -07:00
cecilarmitais
75f2f81394 Restore four literal-pool annotations stripped by earlier commits
Earlier commits on this branch stripped `; =VALUE` annotations off loads that
survived a file split, because precommit.py discarded the removals rule 2
matches against. Four such lines remain in the tree; the rest were on code that
has since been extracted to C, so they left legitimately with their functions.

  asm/main_020108B4.s          ldr r5, _02010AC4 ; =0x0000270F
  asm/main_02010BC0.s          ldr r5, _02010DC8 ; =0x0000270F
  lib/DSE/asm/main_02072054.s  ldr r1, _02072140 ; =0x04000208
  lib/DSE/asm/main_02072054.s  ldr r2, _02072140 ; =0x04000208

These are regenerated from each file's own literal pool rather than recovered
from history: the annotation is the `.word` of the label the instruction
references, so it is derivable and exact. Two earlier attempts to restore
comments by matching instruction text were discarded instead of committed --
repeated instructions made them paste annotations onto unrelated lines.

The convention is unambiguous upstream: across the two files sampled, 263 of 263
pool-referencing loads carry an annotation and none lack one.

No comments are added to any pmd-sky file beyond these, which are restorations
of upstream's own annotations rather than anything authored here.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:28:35 -07:00
cecilarmitais
fb757337d0 Decomp five simple-menu accessors
Decompile from asm:

  GetWindowIdSelectedItemOnPage  0x0202AB80
  CloseSimpleMenu                0x0202B4C4
  IsSimpleMenuActive             0x0202B520
  CheckSimpleMenuField0x1A0      0x0202B540
  GetSimpleMenuField0x1A4        0x0202B558

These are the simple-menu counterparts of the parent-menu accessors in the
previous commit and reach the same window contents through GetWindowContents,
so they reuse struct unk_0202AAA8, which gains a field at 0x1A4.

IsSimpleMenuActive tests states 7 and 8 where the parent-menu version tests 8
and 9. As there, the state is bound to a local: comparing the field against two
values inline emits two loads and the target loads it once.

DeleteWindow moves into the shared header and MemFree is taken from
main_02001188.h, rather than each source declaring them again. Two of these
functions need both, and a second local copy of a prototype is the kind of
disagreement no build can report.

The asm files this split produces keep upstream's jump-table annotations. An
earlier run of precommit.py stripped them, having discarded the removals that
rule 2 matches against; that is fixed separately in the workspace repo, and the
158 comments in this split are intact here.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:19:24 -07:00
cecilarmitais
3fc19ec702 Decomp IsEmptyString and four parent-menu accessors
Decompile from asm:

  IsEmptyString              0x0202A66C
  SetParentMenuState7        0x0202AAA8
  CloseParentMenu            0x0202AABC
  IsParentMenuActive         0x0202AB40
  CheckParentMenuField0x1A0  0x0202AB60

The four menu functions all resolve their window through GetWindowContents,
which src/overlay_10_022BCC60.c already declares as returning void *. What it
returns has fields at 0x198, 0x19C and 0x1A0 and no existing type, so it is
introduced as struct unk_0202AAA8, named for the lowest-addressed function that
uses it, with placeholder fields.

The struct and the GetWindowContents prototype live in include/main_0202AAA8.h
and are included by the second source rather than repeated in it. Two copies of
a struct definition in two translation units is the same drift risk as two
prototypes, and nothing in the build would report them disagreeing.

Return widths differ across these and are read off the target. IsEmptyString and
CheckParentMenuField0x1A0 end in and r0, r0, #0xff, so they return bool8;
IsParentMenuActive does not, so it is word-sized despite reading like a
predicate.

IsParentMenuActive also needed its state field bound to a local. Comparing
menu->field_0x19C against two values inline emits two loads; the target loads it
once.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:59:35 -07:00
cecilarmitais
6f907c680c Decomp eight veneers and wrappers
Decompile from asm:

  PlayBgmByIdVeneer              0x02017B58
  PlayBgmByIdVolumeVeneer        0x02017B64
  PlaySeByIdVolumeWrapper        0x02017C80
  GroupOamAttributesWrapper      0x0201BA9C
  CopyAttributesToOamWrapper     0x0201BAAC
  CopyAndInterleaveWrapper       0x0201BFF0
  SetAnimationControlPausedFlag  0x0201D198
  DeleteWanTableEntryVeneer      0x0201D72C

Most are tail calls the compiler reaches with bx rather than bl, either passing
their arguments through unchanged or supplying one: PlaySeByIdVolumeWrapper
fixes the volume at 0x100, the two Oam wrappers offset their argument by 0x20,
and CopyAndInterleaveWrapper halves its length.

That halving is a shift, not a division. Written as len / 2 the compiler adds the
round-toward-zero correction a signed divide needs; the target has a bare asr #1,
which is what len >> 1 produces.

SetAnimationControlPausedFlag sets or clears bit 0x4000 of the bitfield at the
start of struct animation_control, which already exists in graphics.h.

Two cautions for a reviewer. The callees are all still asm and had no
declarations in the tree, so every prototype here is provisional and declared
beside its caller. And a veneer that only forwards constrains nothing about its
own argument list -- the arguments are already in the right registers, so
declaring none, one or several all produce the same bytes. The parameter lists
are chosen to read sensibly against each callee's name, not read off the target.
The exception is PlaySeByIdVolumeWrapper, whose signature is pinned by an
existing C caller in overlay_25_init.c that passes a single s32.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 22:43:20 -07:00
cecilarmitais
152f6c7ea6 Decomp six shop and item helpers; split bag_items' filler
Decompile from asm:

  SetMoneyStored                            0x0201070C
  GetCurrentKecleonShop1ItemByIndex         0x02010898
  GetCurrentKecleonShop2ItemByIndex         0x02010BA4
  GetExclusiveItemOffset                    0x02010E40
  SwapShopFreeDoublePointer                 0x020114F8
  IsMonsterAffectedByGravelyrockGroundMode  0x02011830

The two Kecleon accessors index arrays hanging off pointers at 0x132C and
0x1370. Unlike the previous two extensions those offsets fall inside
fill2[0x1009] rather than past the struct's end, so the filler is split into
three runs with the two pointers between them. maybeMoney still lands at 0x1394
and nothing after it moves, which the rebuild of every struct bag_items user
confirms.

SetMoneyStored clamps to 0x0098967F, decimal 9999999, and to zero below.

GetExclusiveItemOffset returns zero unless the item is in
CATEGORY_EXCLUSIVE_ITEMS, which item.h already defines as 15, and otherwise the
item's distance from 0x1bc, narrowed to 16 bits.

IsMonsterAffectedByGravelyrockGroundMode normalises the species through
FemaleToMaleForm and compares against two ids, left as raw values because naming
them is not this commit's job. Its return is word-sized: bool8 appends
and r0, r0, #0xff, which the target does not have.

SwapShopFreeDoublePointer frees through two levels of indirection and clears the
outer pointer, returning early when it is already null.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:37:04 -07:00
cecilarmitais
35134eced4 Decomp twelve game-state and recycle-shop accessors
Decompile from asm:

  AddMoneyStored                      0x02010758
  SetEggSpecies                       0x02010794
  SetUnkGameState0x13a6               0x020107C4
  SetEggHatchTimer                    0x020107F4
  DecrementEggHatchTimer              0x0201080C
  GetRecycleItemId                    0x02011DF0
  RecycleItemHasTradeTypePrizeTicket  0x02011DFC
  GetRecycleItemBonusOdds             0x02011E18
  ClearRecycleShopOffer               0x02011F14
  GetGameStateRecycleCount            0x02011F30
  GetRankForRecycleShop               0x02011F48
  GetRecycleOfferCooldown             0x0201227C

Nine of these reach BAG_ITEMS_PTR_MIRROR. Five are the setters matching getters
already landed; the other four use offsets past them, so struct bag_items gains
four more fields, up to 0x13B4. As with the previous extension they are appended
past the end, and the field at 0x13AC is left to align naturally rather than
declaring a byte at 0x13AB that nothing reads.

AddMoneyStored tail-calls SetMoneyStored, which is still asm; its prototype is
provisional. DecrementEggHatchTimer only decrements a non-zero timer, which the
target expresses with predicated subne/strneh rather than a branch.

The remaining three take a pointer to a pointer to a small struct with fields at
0x0, 0x4 and 0x12. No existing type matches that layout, so it is introduced as
struct unk_02011DF0, named for the lowest-addressed function that takes it, with
placeholder fields. The bytes between 0x8 and 0x12 are unexamined and left as
filler.

Every one of the twelve matched on the first candidate.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:28:27 -07:00
cecilarmitais
06c828e19b Decomp CountItemTypeInBag and GetEquippedThrowableItem
Decompile from asm:

  CountItemTypeInBag        0x0200EE88
  GetEquippedThrowableItem  0x0200F208

CountItemTypeInBag totals matching slots, adding the stack size for thrown items
and one for everything else, using IsThrownItem from item_util.c.

GetEquippedThrowableItem returns the index of the first existing slot flagged
usable by L+R, or -1. It reads item->flags through struct item_volatile. The
target loads the flags byte twice, once for each test, where a plain struct item
lets the compiler reuse the first load; item.h already carries item_volatile for
exactly this purpose and says so. The cast is written at each access, following
dungeon_ai_items.c and special_move_types.c rather than casting the cursor once,
though both spellings score 0.

Only the second flag test is left as a bare mask. The existence test goes through
an explicit bool8 because the target boolifies it -- tst, movne, moveq, tst
#0xff -- while the second test branches straight off tst, which is what a bare
mask in an if produces.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:16:47 -07:00
cecilarmitais
a49bcedffc Decomp CountNbItemsOfTypeInBag and HasStorableItems
Decompile from asm:

  CountNbItemsOfTypeInBag  0x0200EE4C
  HasStorableItems         0x0200F0FC

CountNbItemsOfTypeInBag counts every slot whose id matches, without checking the
existence flag, so empty slots are counted when the caller asks for whatever id
they hold. That is what the target does; it is not an omission here.

HasStorableItems checks the existence flag through the same explicit bool8 the
other bag walkers use, then calls IsStorableItem, which is already decompiled in
item_util_4.c, and returns on the first slot satisfying both.

HasStorableItems sits at a lower address than GetItemIndex, so it is prepended
to that file. The extern for BAG_ITEMS_PTR_MIRROR was below the existing
function and is moved to the top, above both, since the new first function needs
it too.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:06:03 -07:00
cecilarmitais
3c5de72d23 Decomp IsItemWithFlagsInBag and GetItemIndex
Decompile from asm:

  IsItemWithFlagsInBag  0x0200EF20
  GetItemIndex          0x0200F14C

IsItemWithFlagsInBag matches an item by id and then by any of a flag mask, and
returns on the first item satisfying both. The two conditions can be written as
a single && or as nested ifs; both score 0, so the target does not distinguish
them and the && form is used as the more direct reading.

GetItemIndex takes a struct item pointer rather than an id. The target compares
the walking cursor against the argument, not a field of it, so this returns the
index of a bag slot the caller already holds a pointer to, and -1 when the
pointer is not in the active inventory. The index is returned through a 16-bit
narrowing, which is why the return type is s16 rather than the s32 the loop
counter uses.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:57:10 -07:00
cecilarmitais
fdbc544fcb Decomp GetNbItemsInBag and IsItemInBag
Decompile from asm:

  GetNbItemsInBag  0x0200EDFC
  IsItemInBag      0x0200EEE0

Both walk the active inventory with a cursor rather than indexing, with the
index and the cursor both advanced in the for clause, which is what puts the two
adds at the top of the loop body as the target has them.

GetNbItemsInBag's existence test goes through an explicit boolean:
(item->flags & ITEM_FLAG_EXISTS) != 0 assigned to a bool8 before the branch.
That is what produces the target's tst / movne / moveq / tst #0xff sequence;
testing the masked value directly collapses it.

IsItemInBag returns early on the first match, which the target reaches with
bxeq lr mid-loop, and falls through to a single return of zero.

GetNbItemsInBag lands in the existing src/main_0200EDC0.c, which already
includes item.h; IsItemInBag needs a new file and declares
BAG_ITEMS_PTR_MIRROR alongside it, matching how the other bag sources in this
tree declare it.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:48:08 -07:00
cecilarmitais
399618a493 Decomp GetItemAtIdx and RemoveEmptyItemsInBag
Decompile from asm:

  GetItemAtIdx           0x0200F340
  RemoveEmptyItemsInBag  0x0200F370

GetItemAtIdx returns a pointer into the active inventory, or null for a negative
index. Its index parameter is s16, not s32: struct item is 6 bytes, and at 16-bit
width the compiler folds the index scaling and the base into a single smlabb,
which is what the target emits. An s32 index produces a separate multiply and
add, at score 1100.

RemoveEmptyItemsInBag tail-calls RemoveEmptyItems over the active inventory with
INVENTORY_SIZE, which is already 50 in item.h and matches the 0x32 immediate.

RemoveEmptyItems is still asm and was not declared anywhere in the tree, so its
prototype is provisional and declared here.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:42:09 -07:00
cecilarmitais
d4b2c2fc01 Decomp two bag helpers that forward to asm callees
Decompile from asm:

  RemoveFirstUnequippedItemOfType  0x0200F798
  AddItemToBagNoHeld              0x0200F874

Both are thin forwarders. AddItemToBagNoHeld tail-calls AddItemToBag with a zero
second argument, which the target reaches with bx rather than bl.
RemoveFirstUnequippedItemOfType feeds GetFirstUnequippedItemOfType's result
straight into RemoveItemNoHoleCheck.

All three callees are still asm and had no declaration anywhere in the tree, so
their prototypes are provisional and declared in the new headers with the
loosest types that compile. They should collapse into the callees' own headers
when those land.

The return types are not determined by these functions' bytes. Both forward
whatever the callee returns without touching it, so declaring them void scores 0
as well. They are written as returning u32 because that is what a pass-through
of a word-sized result reads as, but a reviewer should treat the return type as
a guess rather than something the target settles.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:34:39 -07:00
cecilarmitais
27c9ec9af6 Decomp four bag accessors; extend struct bag_items past maybeMoney
Decompile from asm:

  GetMoneyStored         0x0201070C
  GetEggSpecies          0x0201077C
  GetUnkGameState0x13a6  0x020107AC
  GetEggHatchTimer       0x020107DC

Each dereferences BAG_ITEMS_PTR_MIRROR and reads one field. struct bag_items in
include/item.h ended at 0x13A0, immediately before every offset these four use,
so it gains four fields: a word at 0x13A0, a signed halfword at 0x13A4 and
halfwords at 0x13A6 and 0x13A8. The widths come from the loads -- GetEggSpecies
uses ldrsh where the other two halfword readers use ldrh.

The fields are placeholders rather than named after their accessors. The
function names are suggestive, and a later pmdsky-debug sync can name them; the
offsets and widths are facts read off the asm, which is all this commit claims.

Extending a shared struct is the risk here, so it was checked rather than
assumed. The four fields are appended past the end, after maybeMoney, so no
existing member moves. All four translation units that use struct bag_items --
dungeon_ai_items.c, main_0200ECFC.c, main_0200EDC0.c and
overlay_31_02383478.c -- are rebuilt by this commit's build and the ROM still
matches.

The two constants the target splits the offset into differ per function
(0x1300 + 0xa8 against 0x1000 + 0x3a0) because each is a valid ARM immediate
encoding of the same address; the compiler reproduces the choice from the field
offset alone.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:28:07 -07:00
cecilarmitais
0888986aaf Decomp the tempo and signal track events
Decompile from asm:

  DseTrackEvent_SetBpm   0x02071AE0
  DseTrackEvent_SetBpm2  0x02071B20
  DseTrackEvent_Signal   0x0207296C

SetBpm and SetBpm2 have identical bodies. The two differ only in the label their
literal pool uses, so they are the same routine reached from two opcodes rather
than variants of one; both are decompiled as written rather than one calling the
other, because that is what the target does.

They scale the sequence's tempo fade by the new bpm and divide 0x03938700 --
sixty million microseconds, i.e. one minute -- by the result to get
microseconds_per_beat, guarding a zero divisor by substituting 1. The shifts
type the intermediate: the tempo is read with an arithmetic shift and the
product with a logical one, so the value handed to _u32_div_f is unsigned.

Signal writes the stream byte to the sequence and passes it to the sequence's
signal_callback with code 8, following DseTrackEvent_SetInstrument in
lib/DSE/src/main_02071BF4.c, which makes the same call with a different code.
The callback's four arguments -- id, code, value, callback_arg -- come from the
existing function-pointer type in dse.h, not from the call site.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:14:25 -07:00
cecilarmitais
59fad373cc Decomp three LFO setup track events; complete struct dse_lfo_settings
Decompile from asm:

  DseTrackEvent_SetupVolumeLfo  0x020724A8
  DseTrackEvent_SetupPanLfo     0x020726C4
  DseTrackEvent_SetupLfo        0x02072770

Each reads five bytes and initialises one lfo_settings entry: waveform index,
a signed 16-bit amplitude, a phase-change time, and zeroes for the envelope
fields. The two fixed-slot handlers also set type and output_type, using the
same slot-to-output_type mapping as the Use handlers -- 2 for volume, 3 for pan.
SetupLfo is the indexed form and takes its slot from channel + 0x61.

struct dse_lfo_settings gains field_0xE. All four of these handlers store a zero
byte at that offset, which the struct previously described as trailing padding.
The struct's size is unchanged: it already rounded to 0x10 for the s32
amplitude's alignment, and the 0x10 stride is confirmed by the shift-by-4 the
asm uses to index the array. Adding the field is therefore byte-neutral to
every existing user, which the build confirms.

All five stream bytes are read into locals before the first store. Reading them
inline instead interleaves the loads between stores, and the target issues all
five ldrb up front.

DseTrackEvent_SetupKeyBendLfo is deliberately not included. It is the same
shape and reaches an instruction-for-instruction identical body -- every
mnemonic, offset and immediate matches -- differing only in which registers the
allocator picks, at score 65. Reordering the locals, introducing an explicit
amplitude temporary, and dropping the pointer local were all tried and moved the
score without closing it. It is left in asm rather than landed as a near-match.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 16:06:07 -07:00
cecilarmitais
882d2edf64 Decomp the three tuning-delta track events
Decompile from asm:

  DseTrackEvent_TuningDeltaCoarse  0x02071EB4
  DseTrackEvent_TuningDeltaFine    0x02071F3C
  DseTrackEvent_TuningDeltaFull    0x02071FC4

All three are DseTrackEvent_SetTuning with the new tuning derived from the old
one instead of replacing it, and they share its tail verbatim: recompute
bend_final from the tuning, the channel's bend fade and the synth's bend, then
mask interrupts and set update_flags bit 0x10 on every voice in the channel.

They differ only in how the delta is scaled. Coarse shifts the signed byte left
8, Fine shifts it left 2, and Full takes a little-endian pair of bytes and adds
it whole.

Coarse and Fine need the delta written before the existing tuning --
(delta << n) + channel->tuning, not channel->tuning + (delta << n). The target
loads the stream byte first and folds the shift into the add's second operand;
the other order reverses both the loads and the add, at score 215. Full is
unaffected because its byte pair is assembled before the add either way.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 15:55:57 -07:00
cecilarmitais
019b4543a2 Decomp the volume, expression and pan track events
Decompile from asm:

  DseTrackEvent_SetVolume      0x0207227C
  DseTrackEvent_VolumeDelta    0x02072310
  DseTrackEvent_SetExpression  0x0207241C
  DseTrackEvent_SetPan         0x02072554
  DseTrackEvent_PanDelta       0x020725D4

All five follow DseTrackEvent_SetTuning in lib/DSE/src/main_02071BF4.c, which
was already landed and is a complete template for this shape: write the fade,
recompute the final value, then mask interrupts through IME while walking
channel->voice_list and setting an update_flags bit on every voice. The bit
differs by what changed -- 0x20 for volume and expression, 0x40 for pan, where
SetTuning uses 0x10.

Offsets all resolve against existing dse.h types. struct dse_fade is 0x10, so
the channel's bend, volume and pan fades sit at 0x1c, 0x2c and 0x3c, which is
what the stores at 0x2c/0x34/0x38 and 0x3c/0x44/0x48 address. container is at
0xc4 and struct dse_synth puts pan at +7 and song_and_global_volume at +8, both
of which the asm loads with ldrsb.

The volume handlers scale by song_and_global_volume * volume * expression and
divide by 127 * 127; the compiler reproduces the target's smull-based division
by that constant, so the magic word 0x82061029 needs no special handling.

Two details that were not obvious. The final pan is v + (container->pan - 0x40),
not (v + container->pan) - 0x40: the target subtracts before adding, and the
other grouping emits the two instructions in the opposite order. And the discarded
read of IME before restoring it only survives if IME is declared through a
volatile type; without volatile the compiler drops the load and the restore is
all that remains.

No comments are added to any pmd-sky file.

Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 15:48:27 -07:00