The memory map
Knight Lore is loaded, not unpacked: a snapshot taken after loading is the whole game. It occupies from font to the top of memory, and its variables sit below that from $5BA0 up, with the stack below them again. Nothing lives under $5BA0 that the game cares about.
What is where
$5BA0-$5C07 are the game's variables and $5C08-$6107 the object table -- forty records of thirty-two bytes. $6108-$D8F2 is the game itself, 30699 bytes of code and data, opening with the status font at font. $D8F3-$F0F2 is the screen buffer the drawing code composes into, and $F100 upwards holds lookup tables the game builds for itself at start-up.
Living on top of the system variables
The object table starts at $5C08, which on a stock Spectrum is LAST-K -- where the ROM leaves the last key pressed. That is not an accident and not a conflict: Knight Lore never calls the ROM once it is running, so the whole system-variable area from $5BA0 up is just memory. It reads the keyboard itself, at read_port: an IN from port $FE with the half-rows wanted on the high byte of the address.
The main loop
One frame is onscreen_loop walking all forty object records, followed by end_of_frame drawing the result. Each object is dispatched on its type byte through the table at upd_sprite_jmp_tbl, and the loop pushes its own continuation before jumping so that a handler ends with a plain RET. The stack is reloaded to $5BA0 before every object, so no handler can leave anything behind for the next one.
Randomness
There is no random number generator as such. START seeds from the ROM's frame counter at start-up, and ret_from_tbl_jp stirs in the refresh register after every object -- so the sequence depends on how long the machine has been on and how much work each frame did. That is what makes two games different.
The sprites
103 sprites share one format: a width in bytes, a height in rows, then a mask byte and a bitmap byte for every cell. Carrying the mask with the artwork is what lets the game draw objects in any order and still have them overlap correctly, which is the whole trick of the isometric view. The table at sprite_tbl has a word per graphic number, and an object's first byte is that number: walking and turning are done by changing it. The header's top two bits are not part of the width but a record of whether the stored sprite is currently upside down or mirrored -- the game flips sprites in place when an object wants the other way round, and notes which way the data now lies.