I've been reading up on how transactions actually work, but I'm not sure I understand how testnet bitcoins are kept from being spent on mainnet. My best understanding is that coins are actually references to transactions on the chain, so referencing a testnet transaction on the mainnet chain would not work. Is this correct?
Indeed. When if you try to reference a
utxo (unspent transaction output) from a previous transaction of the testnet in the mainnet, most likely there will be no match.
In case there was a match, you will need to prove that you can spend such output (by performing a digital signature normally), which is even less likely.