Loading a file from a FAT32 filesystem in 16 bit real mode on x86 (Intel syntax assembly)
Style


  feedback on how clear my comments were and how easy the code was to follow


I'm not a big fan of line comments. I think they make the code a bit harder to follow. That's why I prefer tail comments along with a nice tabular layout of the labels, instructions, operands, and tail comments.
And of course don't write redundant comments like "Update the disk address packet sector count" when looking at the names of the identifiers already tells what is happening.

You've got some pushes and pops that are fairly distant apart. If you would number these (or tag them somehow), verifying what the stack holds and whether the stack was correctly balanced becomes a bit easier.

push    dx                              ; (1)
push    eax                             ; (2)

...

push    ax                              ; (3)
movzx   ax, byte [bpbSectorsPerCluster]
mov     [dapSectorCount], ax
pop     ax                              ; (3)

...

pop     eax                             ; -(2)-
push    eax                             ; -(2)-

...

pop     eax                             ; (2)

...

pop     dx                              ; (1)


Optimizations


mov word [dapDataOffset], dx



Most assemblers will not require you to write this word size tag because the use of the DX register made already clear that this has to be a word-sized operation. Not writing the redundant size tag makes your code a bit clearer.

mov     [dapDataOffset], dx


You did this several times.




xor ebx, ebx
mov bl, byte [bpbSectorsPerCluster]



The instruction set has the MOVZX instruction for this kind of situations. It will also reduce the code's footprint which is important in a bootloader that is limited to just 512 bytes. Notice that here the byte size tag is required.

movzx   ebx, byte [bpbSectorsPerCluster]


You did this several times.




push eax
xor ax, ax
mov al, byte [bpbSectorsPerCluster]
mov word [dapSectorCount], ax
pop eax



Pushing and popping the 32-bit register EAX from real address mode uses an operand size prefix. In this case the high word was never changed and so you can shave off 2 bytes if you only push/pop AX.  

Better still, the program doesn't need you to preserve AX at this point. Again 2 bytes less.

movzx   ax, byte [bpbSectorsPerCluster]
mov     [dapSectorCount], ax





pop eax
and ax, 0x007F

; Multiply offset by 4 to get it in bytes
shl ax, 2

; Calculate the memory address of the cluster in the FAT
add ax, 0x7E00

; Read cluster value
mov bx, ax
mov eax, dword [bx]



Here's another opportunity to shave off a byte. Pop the value directly in the register that you'll use to retrieve the cluster number: 

pop     ebx
and     bx, 0x7F
shl     bx, 2      ; Multiply offset by 4 to get it in bytes
add     bx, 0x7E00 ; Calculate the memory address of the cluster in the FAT
mov     eax, [bx]  ; Read cluster value


Exactly the same length but less instructions:

pop     ebx
and     ebx, 0x7F
mov     eax, [ebx*4 + 0x00007E00]  ; Read cluster value





; Update the disk address packet sector count
mov word [dapSectorCount], 1

; Update the disk address packet data offset to load the fat to 0x7E00
mov word [dapDataOffset], 0x7E00



The above instructions total 12 bytes. Because dapSectorCount and dapDataOffset follow each other in memory, you can combine the writes. The total then becomes 9 bytes.

; Update dapSectorCount=1 and dapDataOffset=0x7E00 together
mov     dword [dapSectorCount], 0x7E000001


This last optimization is of course killing readability in favor of code size. Your choice!

Beware

Your loadClusterChain routine uses some 32-bit registers. Given that this is 16-bit code the pusha and popa instructions will not preserve the high words. Maybe this can become a problem for the rest of your program!