As you stated solana has two types of storage:
- State: This is all data that is stored onchain that can be accessed and edited by programs (i.e. data stored in accounts). State is expensive because validators are forced to keep it in memory in order to validate new transactions. Current state cannot be pruned.
- Ledger space: This is all data that is stored onchain (i.e. in the ledger) that is not directly accessible by programs e.g. past transactions, instruction logs, calldata for transactions, timestamps etc. . Ledger space is cheap because validators don't need data in ledger space to validate new transactions, so they can choose to prune it at will and/or keep it in slower memory.
What does this mean for state compression? If we have some data that we don't need our program to access directly (like NFT metadata in the case of cNFTs) we can store this in the ledger and only store a hash/a Merkle tree of our data in the state. That way our program doesn't know what data we're storing exactly, but it can always verify later that some piece of data we claim to be our NFTs metadata is actually that.
Finally, how can we write to this ledger space from our program? The two most common ways are via calldata (i.e. passing the piece of data we want to save as a param to some instruction) and logs (i.e. just logging the piece of data we want to save from our program). Note here that not all ledger space is created equal:
Using logging, you can write more data to the ledger space per transaction than you can pass via calldata since you're not constrained by maximum transaction size. On the flipside, validators don't need logs to validate new transactions OR to reconstruct current state whereas they need calldata to reconstruct current state, meaning logs are more likely to be pruned than calldata, generally speaking.