The eleven handlers decompiled in the two previous commits used
(position, seq, track, channel). The handlers already landed in
lib/DSE/src/main_02071A98.c, which are the same dispatch family reached from the
same SMD_EVENTS_FUN_TABLE, use (ptr_next_byte, sequence, track, channel). This
renames the arm9 ones to match so the family reads consistently, and follows the
existing pointer spelling for the sub-loop local.
Parameter names do not affect code generation; the rebuild below is to confirm
that rather than to assume 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>
Decompile from asm:
DseTrackEvent_SetOctave 0x02071AC0
DseTrackEvent_OctaveDelta 0x02071ACC
DseTrackEvent_SetTuningJitterAmplitude 0x0207216C
DseTrackEvent_SetUnknown2 0x020721B0
DseTrackEvent_SetKeyBendRange 0x020721C8
DseTrackEvent_Dummy2Bytes2 0x0207299C
These are the same handler family as the previous two commits, but they live in
lib/DSE, which is compiled with different flags: -O4,p and -enum int, where the
arm9 modules use -O4,s and -enum min. Signatures follow the parameter names
already used by the landed handlers in lib/DSE/src/main_02071A98.c rather than
the ones the previous two commits introduced.
Field names come from struct dse_channel in lib/DSE/include/dse.h and match the
offsets the asm uses: bend_jitter_amplitude at 0xa, field_0x56 at 0x56, and
bend_sensitivity_override at 0x59, whose meaning agrees with SetKeyBendRange
writing to it.
SetTuningJitterAmplitude and SetUnknown2 read their two operand bytes in
opposite orders -- little-endian for the first, big-endian for the second --
which the asm's shift operands show directly.
Landing these needed two adjustments the arm9 path does not. extract_function.py
hardcodes asm/, src/, include/ and main.lsf, so it was run with lib/DSE as the
working directory; the object lines it then inserts into main.lsf carry no
lib/DSE/ prefix and were corrected by hand. It also emits
.include "main_XXXX.inc", which is right for arm9 where the search path is
./asm/include, but lib asm is built with -Ilib/DSE/asm/ and its existing files
use .include "include/main_XXXX.inc"; the four split files were fixed to 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>
Decompile from asm:
DseTrackEvent_WaitUntilFadeout 0x0207199C
DseTrackEvent_EndTrack 0x020719C8
DseTrackEvent_SubLoopBegin 0x02071A1C
DseTrackEvent_SubLoopEnd 0x02071A5C
This empties asm/main_0207199C.s and asm/main_02071A1C.s, so both files and
their .inc are removed rather than split, and the handlers merge into the
adjacent sources added by the previous commit.
EndTrack confirms the second parameter's type, which the previous commit could
only infer from the dispatcher: it writes 1 to the byte at offset 3 of that
argument, which is field_0x0.has_looped in struct dse_sequence. A track that
reaches the end of its main loop setting has_looped is consistent, and no other
struct in dse.h places a byte with that meaning at offset 3.
SubLoopBegin and SubLoopEnd index track->loop_stack, the existing
struct dse_sub_loop[4] at offset 0x24 with a 12-byte stride, which is what the
asm's multiply by 0xc computes.
SubLoopEnd needed two changes past the obvious form. Its index and count locals
are int, not u8: at byte width the compiler inserts and rX, rX, #0xff after each
decrement, which the target does not have. And the early return has to be the
fall-through path -- written as an early return the compiler if-converts the
tail into predicated stores, where the target branches with bxeq lr. Inverting
the condition, so the longer body sits inside the if, produces the branch.
DseVoice_CountNumActiveInChannel and DseChannel_DeallocateVoices are still asm;
their prototypes are provisional and declared in this file's header. Neither was
declared anywhere else in the tree.
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>
Decompile from asm:
DseTrackEvent_Invalid 0x0207191C
DseTrackEvent_WaitSame 0x02071928
DseTrackEvent_WaitDelta 0x02071934
DseTrackEvent_Wait8 0x0207194C
DseTrackEvent_Wait16 0x0207195C
DseTrackEvent_Wait24 0x02071978
DseTrackEvent_MainLoopBegin 0x02071A14
These are handlers in SMD_EVENTS_FUN_TABLE, dispatched by ParseDseEvent. No
new types are introduced: struct dse_track and struct dse_sub_loop already
exist in lib/DSE/include/dse.h, and every offset these functions touch lines up
with a named field there -- is_playing at 2, wait_ticks_left at 0xc,
previous_wait_ticks at 0x10, loop_start at 0x20.
The shared four-argument signature is read off the dispatcher rather than
guessed. ParseDseEvent loads the handler from SMD_EVENTS_FUN_TABLE and calls it
with r0 already advanced past the opcode byte, r1 its own first argument, r2 the
track, and r3 track->channel from offset 0x54; the returned r0 becomes the
track's new position. Its caller passes a sequence and that sequence's track at
offset 0xa8, which is what types the second parameter.
The second parameter is unused by all seven bodies, so their own bytes do not
constrain its type; it is typed from the dispatcher's call site, and the
handlers that do use it are still asm.
Six of these are contiguous and merge into one source file; MainLoopBegin sits
after WaitUntilFadeout and EndTrack, which remain asm, so it splits separately.
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>
Decompile from asm:
IsMonsterIdInNormalRange 0x02056294
Rgb8ToRgb5 0x02004FCC
Arm9LoadUnkFieldNa0x2029EC8 0x02029EC8
Arm9StoreUnkFieldNa0x2029ED8 0x02029ED8
CanMonsterBeUsedForMissionWrapper 0x02062A58
Each was extracted with extract_function.py, splitting its .s and adding the
new objects to main.lsf. PREV_MENU_ITEMS is declared alongside the two
accessors that use it, sized 0x18 from the .space in main_bss_020B3380.s.
Four prototypes previously declared from call sites are replaced by includes of
the new headers: IsMonsterIdInNormalRange in main_02056D70.c, Rgb8ToRgb5 in
main_0201E380.c, and both Arm9 accessors in overlay_31_02382820.c.
Arm9LoadUnkFieldNa0x2029EC8 keeps the two-parameter prototype that was already
declared in overlay_31_02382820.c. Its body reads only the first argument -- the
second register is overwritten with the table address before any use -- so the
function's own bytes do not distinguish a one-parameter form from a two-
parameter one, and both score 0 against the target. The call site does
distinguish them: overlay_31_02382820.c passes two arguments and already
matches, so the existing declaration is the load-bearing evidence. That object
is rebuilt here and still matches.
CanMonsterBeUsedForMission is still asm, so its prototype is provisional and
declared in the caller's header. Its second parameter is byte-width: a caller
in main_0205D1F4.s masks with and r1, r1, #0xff before the call.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 5) under human direction. Confirmed by a matching
build of all five together: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decompile OverlayIsLoaded (0x02003ED0) from asm/main_02003DB8.s. It maps an
overlay group id to whether that group is currently loaded, via a jump table
over ids 0-0x24: group 0 is always loaded, and the other ranges check one of
the three LOADED_OVERLAY_GROUP_0 slots, gated on the parent slot holding the
expected group. Out-of-range ids fail an assert through Debug_FatalError with a
prog_pos_info record for overlay.c line 177.
That record is the reason this commit also moves data. The function copies
_02092558[2] to the stack to pass its address, and the copy's two loads only
schedule in the target's order when the compiler can see the array's
initializers -- an extern declaration of the same array compiles to the loads
transposed. So the five-record array moves into the C file as a const
definition. The values are the ones decoded from the data: five entries sharing
the overlay.c filename with lines 887, 534, 177, 269 and 626.
The array's neighbours cannot move with it. The two strings that follow it in
retail cannot be produced by this compiler in place: mwccarm 2.0/sp2p2 emits
anonymous string literals into writable .data sections, which the link places in
the far data region, and it has no -str readonly (only [no]reuse and [no]pool);
a named const u8 string does land in .rodata but is emitted before the array
regardless of definition order. The strings therefore stay as assembly, in a new
asm/main_rodata_02092580.s holding exactly overlay.c and check group error.
asm/main_rodata_020908D4.s is accordingly split: it now ends just before
_02092558, and the remainder from _020925A0 onward becomes
asm/main_rodata_020925A0.s. Both new rodata files include the original
main_rodata_020908D4.inc, which other rodata files also include as a shared
declaration header and which is left untouched. main.lsf orders the pieces
src/main_02003ED0.o, asm/main_rodata_02092580.o, asm/main_rodata_020925A0.o, so
the linker lays out the array, the strings and the remaining data at their
retail addresses; the extraction also splits the text file, with the trailing
functions moving verbatim into asm/main_020040AC.s.
No new names are introduced: OverlayIsLoaded, struct prog_pos_info,
Debug_FatalError, LOADED_OVERLAY_GROUP_0 and the three data placeholders all
already exist in the tree.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK. Intermediate layouts were verified by
binary-diffing the built main.sbin against a clean build, which is what located
each divergence exactly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile ov11_022E96E4 from asm/overlay_11.s. It returns the byte at offset 4 of
ov11_02324FA0. This is overlay_11's first decompiled function, so it creates
src/overlay_11_022E96E4.c rather than merging; the trailing functions move
verbatim into asm/overlay_11_022E96F4.s, the .inc files are regenerated, and the
two objects are added inside main.lsf's Overlay OVY_11 section.
ov11_02324FA0 is declared as u8[0x20] rather than given a struct. The symbol is
.space 0x20 in the data, so the size is a fact, and other functions in the
overlay touch offsets 0x0, 0xc, 0x10, 0x14 and 0x1c -- none of which are
decompiled, so a struct would have to invent a layout around the one field this
function reads. The array asserts only the size.
The return type is u8. The load is ldrb, so the width is known, but every
candidate return type produces identical bytes here; bool8 would additionally
assert that the value is a truth value, which is only an inference from the two
callers testing it in a condition. u8 states what is known and nothing more.
sub_02065050 and sub_0206549C call it and had a provisional int prototype in
main_02064FFC.h while only the call site was understood. That is replaced by an
include of the new header, and both were rebuilt to confirm they stay
byte-identical.
No new names are introduced: both ov11_022E96E4 and ov11_02324FA0 are
pre-existing labels.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile two more callees of the actor cluster, from asm/main_02055894.s and
asm/main_020151E4.s. Each splits its file; the trailing functions move verbatim
into asm/main_02056504.s and asm/main_0201E3AC.s, the affected .inc files are
regenerated, and main.lsf gains the four objects.
sub_020564B0 takes a member index and reports whether it is the appointed
leader's. It repeats the roster read that GetAppointedLeaderMemberIdx performs --
active_team selects one of the number_active_team_members fields, and that value
indexes active_roster_member_idxs -- and compares the result against its
argument, returning false if either the argument or the intermediate is -1. With
this, both NPC-collection filters are accounted for: GetAdventureNpcIds excludes
the appointed leader, and GetUnitNpcIds excludes the three main characters.
sub_0201E380 writes one palette entry. It converts an 8-bit RGB triple through
Rgb8ToRgb5 into the halfword at index (c << 8) + d of the table its first
argument points at, then sets a byte at index c in a second table, which reads as
marking that palette dirty.
That first argument is a struct with a halfword pointer at 0x18 and a byte
pointer at 0x1c. No global has that type -- callers pass an interior offset of
whatever _020AFC70 points at -- so it takes the address of the function that
receives it, struct unk_0201E380, with fields named for their offsets. The
parameter was previously declared u8* while only the call site was understood,
so sub_02064F94 now casts at that call site; it was rebuilt to confirm it stays
byte-identical. src/main_02056D70.c likewise drops its local extern for
sub_020564B0 in favour of the new header.
No other names are introduced. Rgb8ToRgb5, TEAM_MEMBER_TABLE_PTR,
struct team_member_table and its fields all already exist, and both functions
keep their sub_ names.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile both from asm/main_02056960.s. Each walks the four entries of
TEAM_MEMBER_TABLE_PTR->active_roster and, for every entry with bit 1 of its flags
byte set, takes member_index and keeps it if IsMonsterIdInNormalRange accepts it
and a filter rejects it: GetUnitNpcIds excludes the three main characters via
sub_02055410, sub_02055474 and sub_020554D8, and GetAdventureNpcIds excludes via
sub_020564B0. Matching indices are appended to the caller's buffer and the count
is returned.
Both tolerate a null buffer. The target reserves eight bytes of stack and
redirects the pointer there when the argument is null, which is why the frame
reserves space nothing else uses. That is reproduced with a local s16 scratch[4].
They merge into a new src/main_02056D70.c; extracting them splits
asm/main_02056960.s, the trailing functions move verbatim into
asm/main_02056EB8.s, and main.lsf gains the two objects.
The buffer parameter is s16*, which is what both call sites pass and what the
stores write. Their prototypes move out of main_02064FFC.h, where they were
declared as void* while their callers were decompiled, into the new header. That
tightening also required sub_02065050's two scratch buffers to change from
u8[8] to s16[4] -- the same eight bytes, and already how sub_0206549C declares
its own. sub_02065050 was rebuilt to confirm it stays byte-identical.
The flag test reads the first byte of struct team_member directly, as
(*(u8 *)m & 2). common.h models that byte as a bitfield of f_is_valid:1 followed
by flags_unk1:7, and no access through those members reproduces the target's
single tst: reading flags_unk1 emits a shift pair to extract the field first, and
splitting bit 1 into its own one-bit member is worse still. The raw byte read is
the only form that matches, so it is used here rather than changing a struct
shared with the rest of the tree.
No new names are introduced. struct team_member, struct team_member_table, their
fields and TEAM_MEMBER_TABLE_PTR already exist, and both function names were
already labelled.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile the three member-index predicates from asm/main_02054E4C.s. Each takes
a member index, checks that TEAM_MEMBER_TABLE_PTR->members[idx] is valid, and
returns whether that index is the one a particular main character occupies for
the current game mode: sub_02055410 tests index 2 during a special episode and 0
otherwise, sub_02055474 tests 3 and 1, and sub_020554D8 tests 4 and is false
outside a special episode. Those are the same indices GetMainCharacter1,
GetMainCharacter2 and GetMainCharacter3 select, so each predicate answers
"is this index that main character" without going through the accessor.
They merge into a new src/main_02055410.c; extracting them splits
asm/main_02054E4C.s, the trailing functions move verbatim into
asm/main_02055528.s, and main.lsf gains the two objects.
The return type is bool8 rather than int, and the parameter is subscripted as
members[(s16)idx] so the index multiply compiles to smulbb. Their prototypes move
out of main_02064FFC.h, where they were declared while sub_02065050 was
decompiled, into the new header, which main_02064FFC.h now includes;
sub_02065050 was rebuilt to confirm the return-type change leaves it
byte-identical.
The new header includes util.h. Without it the header is not self-contained:
bool8 is declared there, and a header that uses it before common.h is reached
makes the compiler infer int and then conflict with the real typedef.
No new names are introduced. struct team_member_table, its fields,
TEAM_MEMBER_TABLE_PTR, GetGameMode and GAME_MODE_SPECIAL_EPISODE already exist in
the tree, and all three functions keep their sub_ names.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile the last two accessors of this cluster from asm/main_02055894.s. Both
resolve the appointed leader through the roster region of team_member_table:
they read the active team from active_team, index the three
number_active_team_members fields by it, and use that value to index
active_roster_member_idxs. GetAppointedLeaderMemberIdx returns the resulting
member index, or -1 when the first read is -1. sub_02056914 is its pointer
counterpart, returning &members[index] or NULL when either read is -1 -- the
same index/pointer pairing as GetHeroMemberIdx and GetHero.
Each sits in the middle of asm/main_02055894.s and is not adjacent to the other,
so each extraction splits the file: the trailing functions move verbatim into
asm/main_020568DC.s and asm/main_02056960.s, the affected .inc files are
regenerated, and main.lsf gains the four objects.
GetAppointedLeaderMemberIdx returns u32, consistent with the other MemberIdx
functions in this cluster. Its prototype and sub_02056914's move out of
main_02064FFC.h, where they were declared while their callers were decompiled,
into the new headers; main_02064FFC.h now includes those. sub_02065050 and
sub_0206549C call them and were rebuilt to confirm the u32 change leaves both
byte-identical.
The three number_active_team_members fields are separate scalars in common.h but
are indexed here as an array, which is what the asm does; the expression relies
on them being contiguous, which the struct layout guarantees.
No new names are introduced. struct team_member_table, struct ground_monster,
their fields and TEAM_MEMBER_TABLE_PTR all already exist in the tree, and both
function labels were already present.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile GetHeroMemberIdx, GetPartnerMemberIdx, GetMainCharacter1MemberIdx,
GetMainCharacter2MemberIdx and GetMainCharacter3MemberIdx from
asm/main_02054E4C.s. Each returns the index of a TEAM_MEMBER_TABLE_PTR->members
entry when that entry's is_valid bit is set, or -1 otherwise: hero is index 0,
partner index 1, and the GetMainCharacter functions choose the index by game
mode -- 2/3/4 during a special episode, 0/1/none otherwise. They are the index
returning counterparts of the pointer accessors in the previous commit and merge
into a new src/main_0205567C.c.
The return type is u32. This matters: with a signed int return, the compiler
recognises `valid ? 0 : -1` as the idiom -(!valid) and emits an arithmetic
negate, but the target uses a conditional-move select. Under u32, -1 is
0xFFFFFFFF, the negate idiom no longer applies, and the compiler emits the
select the target has. The distinction is only observable for hero, whose index
is the constant 0; for the others the index is non-zero or runtime, so both
signednesses produce the select. The whole family is typed u32 for consistency,
and sub_02065050, which calls two of these, was rebuilt to confirm the type
change leaves it byte-identical.
No new names are introduced. struct team_member_table, struct ground_monster and
TEAM_MEMBER_TABLE_PTR already exist in the tree, and the five function names were
already labelled; this commit only fills in bodies.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile GetHero, GetPartner, GetMainCharacter1, GetMainCharacter2 and
GetMainCharacter3 from asm/main_02054E4C.s. Each returns a pointer to a
struct ground_monster in TEAM_MEMBER_TABLE_PTR->members, or NULL when that
entry's is_valid bit is clear: GetHero returns members[0], GetPartner
members[1], and the GetMainCharacter functions pick the index from the game
mode -- members[2]/[3]/[4] during a special episode, members[0]/[1]/NULL
otherwise.
They merge into a new src/main_02055770.c; extracting them splits
asm/main_02054E4C.s, the trailing functions move verbatim into
asm/main_02055894.s, and main.lsf gains the two objects. Their prototypes were
temporarily declared in main_02064FFC.h for the previous commits; they move to
the new main_02055770.h, which main_02064FFC.h now includes. sub_02056914 keeps
its prototype there since it is still asm.
No new names are introduced. struct ground_monster and struct team_member_table
already exist in common.h, and these six function names were already labelled in
the tree; this commit only fills in bodies.
Decompiling these confirms by construction what the previous commits declared:
the accessors return struct ground_monster*, so the ->id reads that feed the
monster-id output of sub_0206549C are reading struct ground_monster::id, the
documented "Monster ID" field.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous commit declared struct unk_020B0A48 for the records that
TEAM_MEMBER_TABLE_PTR points at. That global is already typed in the tree as
struct team_member_table (common.h), whose members[] elements are
struct ground_monster -- a 0x44-byte record whose field at 0x4 is the documented
s16 Monster ID. The placeholder was a redundant reinvention of it, and it
declared TEAM_MEMBER_TABLE_PTR a second time, conflicting with the extern that
two other decompiled files already use.
Replace it with the canonical types: TEAM_MEMBER_TABLE_PTR becomes
struct team_member_table*, the six accessors return struct ground_monster*, and
the nine reads that fed *out through ->field_0x4 now read ->id (or
->members[i].id for the table-indexed pair). struct unk_020B0A48 is removed and
common.h is included for the definitions.
members[] sits at offset 0 of team_member_table and ground_monster has the same
0x44 stride and 0x4 field as the placeholder, so the reads compute identical
addresses. Confirmed unchanged by a matching build.
Beyond removing a duplicate struct, this makes the output type legible from the
decompilation itself: every value written to *out in sub_0206549C now reads,
in-tree, as a monster id -- a ground_monster.id, a monster-valued script
variable, or an entry of a table that decodes to species.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile sub_0206549C (0x0206549C), which completes the actor cluster. It calls
sub_02065050 to resolve the entity id in place, then maps the result to a
monster id written through its second argument: entity kinds of 3 resolve
against the loaded unit or adventure NPC list, and everything else dispatches
through a switch over the script entity ids, reading team members, script
variables or SPECIAL_ACTORS slots. It merges into src/main_02064FFC.c, which
removes the last of asm/main_0206549C.s; main.lsf drops the object.
enum monster_id gains MONSTER_INVALID = -1, for the same reason
enum script_entity_id gained ENTITY_NONE in the previous commit. This function
reads nine SPECIAL_ACTORS slots of that type with ldrsh, and an enum with no
negative enumerator is unsigned under -enum min, so those compiled to ldrh. The
name avoids MONSTER_NONE, which the _MONSTER_ID_GENDERED macro already defines
as 0. The sentinel was verified in isolation -- added alone, with nothing else
in the tree modified -- and the ROM still matches, so it is byte-neutral for all
existing code and only affects what reads such a field.
struct unk_020A7FF0 field_0x2 becomes s16 on the same evidence: the switch's
default case reads it with ldrsh. That is the third field corrected this way,
after field_0x0 and the script_entity_id slots, and the reason they all surfaced
late is that storing to a field cannot reveal its signedness.
struct unk_020B0A48 is declared for the 0x44-byte records that
TEAM_MEMBER_TABLE_PTR points at, named for that pointer's address, which is
derived by counting to the next label in the data. GetMainCharacter1,
GetMainCharacter2, GetMainCharacter3, GetHero, GetPartner and sub_02056914 all
return it, and none of them had a declaration.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile sub_02065050 (0x02065050), the resolver that rewrites a script entity
id in place and reports which storage it maps to. It redirects the four special
ids 0x2E-0x31 through SPECIAL_ACTORS, then dispatches on the entity's kind
field: kind 1 resolves the player slot from script variable 0x34, kind 2
resolves a team member from variables 0x35 and 0x36, kind 3 range-checks against
the loaded unit or adventure NPC list, and anything else handles the appointed
leader. It merges into src/main_02064FFC.c; asm/main_02065050.s splits, with
sub_0206549C moving verbatim into asm/main_0206549C.s.
Two type declarations are corrected, both diagnosed from the load instruction
rather than assumed. Storing to a field cannot reveal its signedness -- strh is
the same either way -- so neither error was observable until something read
these fields, and this is the first decompiled function that does.
enum script_entity_id gains ENTITY_NONE = -1. Under -enum min an enum with no
negative enumerator is unsigned, so the SPECIAL_ACTORS redirect slots compiled
to ldrh where the target has ldrsh. An explicit (s16) cast does not help: the
destination store is 16-bit, so the compiler folds the conversion away. The name
follows EPISODE_NONE = -1, already in this file. The sentinel was verified in
isolation, with no other change in the tree, and the ROM still matches -- it is
byte-neutral everywhere else and only affects code that reads such a field.
struct unk_020A7FF0 field_0x0 becomes s16 for the same reason. It was declared
u16 when the struct was introduced for sub_02065B14, which only reads field_0x8
-- genuinely unsigned, ldrh. Both resolvers read field_0x0 with ldrsh.
sub_02055410 is declared as taking the member index rather than void. The
argument is already in r0 from GetAppointedLeaderMemberIdx, so the call needs no
register move, which is what leaves r0 occupied across the sentinel comparison.
Declared void, the function is otherwise identical but allocates that constant
to r0 instead of r1.
The nine functions this calls that had no declaration are declared alongside;
parameter names are positional where only the call site is understood.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile sub_02064F94, sub_02064FE4, sub_02064FFC and sub_02065014 from asm.
All four merge into a new src/main_02064FFC.c; extracting them splits
asm/main_0205EDA4.s, the trailing functions move verbatim into
asm/main_02065050.s, the affected .inc files are regenerated, and main.lsf gains
src/main_02064FFC.o and asm/main_02065050.o. The two remaining functions of the
cluster, sub_02065050 and sub_0206549C, are now alone in that tail file.
sub_02064FFC and sub_02065014 index the same 12-byte table. EVENTS already
exists in the tree; its record type is declared here as struct unk_020A5488,
named for the table's address. Worth knowing for anyone reading the diff against
the asm: _020A5490 is a separate .global sitting at EVENTS + 8, inside the first
record, so sub_02064FFC's literal pool entry reads .word _020A5490 in the target
and EVENTS+0x8 from this source. Both resolve to the same address, which the
matching build confirms.
sub_02064F94 walks an array calling sub_0201E380, and sub_02064FE4 is a tail
call into it. Their two argument structs have no global, so they take the
address of the function that receives them -- struct unk_02064F94 and struct
unk_02064FE4 -- per the placeholder convention. sub_0201E380 and _020AFC70 are
still asm and have no header of their own, so both are declared alongside; move
them if a better home appears.
Nothing here is named for what it means. The structs and their fields are
placeholders, and local names stay structural: data is a cursor over 4-byte
records, count is the loop bound, value is the field the comparison reads.
sub_0201E380's parameters are positional, since only its call site is understood
and not the function itself. Anything more specific would be inferred from code
two calls downstream rather than read off the asm.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile two script-entity accessors from asm.
sub_02065B14 (0x02065B14) merges into src/main_02065B3C.c, ahead of the
special-actor setters. It returns 0 for an id of -1, and otherwise a 16-bit
field of the ENTITIES record for that id. ENTITIES already exists in the tree
and keeps its name; the record type it needs is declared here as struct
unk_020A7FF0, 12 bytes, fields named for their offsets. Identifying that table
was not part of this change, so nothing here asserts what the fields mean --
only the layout, which is read off the asm: the stride comes from the smlabb
multiplier and the returned field from the ldrh displacement.
GetScriptEntityMonsterId (0x02065974) lands in a new src/main_02065974.c. It
takes the address of its own parameter and an s16 local, hands both to
sub_0206549C, and returns the local. sub_0206549C is still asm and has no header
of its own, so its prototype is declared alongside; move it if a better home
appears.
Extracting it splits asm/main_0205EDA4.s: the functions after it move verbatim
into asm/main_02065998.s, the affected .inc files are regenerated, and main.lsf
gains src/main_02065974.o and asm/main_02065998.o.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile RandomizeDemoActors (0x02065C48) from asm into src/main_02065C48.c.
It picks one of the 18 title-screen demo teams at random and stores that team's
hero/partner species into the SPECIAL_ACTORS demo slots.
DEMO_TEAMS is declared as an array of a 4-byte record, 18 entries, two 16-bit
fields read with ldrsh. The record type and its fields keep placeholder names --
struct unk_020A68C8, field_0x0, field_0x2 -- because identifying this table was
not part of this change. The offsets are facts read off the asm; what the fields
mean is not, so nothing here asserts it. DEMO_TEAMS itself already exists in the
tree and keeps its name.
Note that a separate .global, _020A68CA, sits at DEMO_TEAMS + 2, on the second
field of the first record; both symbols resolve to the same address.
The species are cast to enum monster_id when stored into the slots: enum
monster_id declares no negative enumerator, so under -enum min it is unsigned
and cannot reproduce the signed (ldrsh) reads. Whether monster_id should instead
declare a -1 sentinel -- the game does return -1 from GetScriptEntityMonsterId
-- is deliberately left open for upstream discussion rather than diverging from
pmdsky-debug here.
Extracting the function splits asm/main_02065BD8.s, which keeps the three
functions ahead of it; the trailing functions move verbatim into
asm/main_02065C84.s, the affected .inc files are regenerated, and main.lsf gains
src/main_02065C48.o and asm/main_02065C84.o.
No comments are added to any pmd-sky file; comments cannot be checked by the
matching build, so the code is left to stand on its own.
Based on the decomp-actor-globals branch, which introduces SPECIAL_ACTORS.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompile UFixedPoint64CmpLt (0x02001A30) from asm. It compares two 64-bit
values held as high/low word pairs and returns whether the first is less than
the second.
asm/main_02001A30.s held only this function, so the file and its .inc are
removed rather than split, the function merges into the adjacent
src/main_020018D0.c, and main.lsf drops asm/main_02001A30.o.
Three details of the interface are read off the target rather than assumed,
each tested with the others held fixed. The four words arrive as separate
parameters: a 2-word struct passed by value makes the compiler push all four
argument registers and reload them, which the target does not do. Those
parameters are unsigned: the comparisons select movlo/movhi, where signed high
words give movlt/movgt. The return is word-sized: a u8-width return such as
bool8 appends and r0, r0, #0xff, which the target does not have. BOOL is used
rather than int because it is the tree's word-sized boolean, and it produces
the same bytes.
src/main_02001BB4.c declared this function from its call site, before it was
decompiled. That declaration is replaced by an include of the header. It was
never visible to the compiler at the same time as the definition, so the two
disagreeing prototypes could not be caught by a build; its parameter types were
signed, which does not describe this function.
No comments are added to any pmd-sky file.
Authored by Claude (Opus 4.8, amended by Opus 5) under human direction.
Confirmed by a matching build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decompile eight ground-mode setters from asm into src/main_02065B3C.c:
SetActorTalkMainAndActorTalkSub, SetActorTalkMain, SetActorTalkSub,
SetActorEventMain, SetRandomRequestNpcs1And2, SetRandomRequestNpc03KindVar,
SetAllEventNpcs and SetNewFriendActor.
Identify the global they write, _020B0B08, as a 28-byte table of slots keyed by
the special members of enum script_entity_id (ENTITY_TALK_MAIN 46 through
ENTITY_NPC_DEMO_PARTNER 59). Every slot offset is corroborated by both a writer
above and the reader that resolves it at the matching entity id: sub_0206549C,
reached via GetScriptEntityMonsterId, and sub_02065050, reached via
GetActorMatchingStorageId. Name the type struct special_actors and relabel the
symbol _020B0B08 -> SPECIAL_ACTORS at its definition in
asm/main_rodata_020A2808.s and at every reference.
Extracting the block splits asm/main_0205EDA4.s: the trailing functions move
verbatim into asm/main_02065BD8.s, the affected .inc files are regenerated, and
main.lsf gains the two new objects.
Add enum script_entity_id to include/enums.h, taken from pmdsky-debug, since
struct special_actors refers to it.
No comments are added to any pmd-sky file; comments cannot be checked by the
matching build, so the code is left to stand on its own.
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Authored by Claude (Opus 4.8) under human direction. Confirmed by a matching
build: build/pmdsky.us/pmdsky.us.nds: OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>