Reading both the OSDev Wiki article and the Intel documentation about the two different available APIC types that said article links to left me with more questions than answers, specifically when it comes to the length of the fields. According to both sources, the IOAPIC destination field is supposed to contain a local APIC ID in bits 56-59 — that’s 4 bits out of an 8-bit field:
Destination field. If the destination mode bit was clear, then the lower 4 bits contain the bit APIC ID to sent the interrupt to. If the bit was set, the upper 4 bits also contain a set of processors. (See below)
Yet according to the same sources, the ID along with everything else in the LAPIC registers is 32 bits long:
The local APIC registers are memory mapped to an address that can be found in the MP/MADT tables. Make sure you map these to virtual memory if you are using paging. Each register is 32 bits long, and expects to be written and read as a 32 bit integer. Although each register is 4 bytes, they are all aligned on a 16 byte boundary.
This begs the question: how can the ID of the APIC to send the interrupt to possibly fit there?
What’s also interesting is that those upper 4 bits are supposed to contain a list of CPU cores to send an interrupt to if the IOAPIC is configured in logical mode, which is also interesting considering that most modern CPUs have more than 4 cores (my own i5-8400 notwithstanding) but that’s a completely different topic.
Solved my own problem by looking here. It turns out that only bits 24-32 of the ID value are the actual ID; all the others are mere padding.