1. avoid floats (fixed point arithmetic saved quite a bit of space)
2. avoid hashmaps (originally used hashmaps since it was easy to port JS maps to, have since ported everything to vecs)
3. avoid strings (for awhile there were no strings, but eventually brought it in for display logic)
4. use a small allocator, like talc
5. avoid dependencies. I only use rand & fxhash. I should probably get rid of rand (fxhash only used to hash game state to check for desyncs)
6. avoid generic diversity. I try to keep a small set of instantiated types, for example Vec<i16> is there so no need to bring in Box<[i16]> or anything. Getting away from floats/hashmaps helped reduce type diversity
7. design algorithms with size in mind, I have a couple lookup tables where I pack bits https://github.com/serprex/openEtG/blob/2011007dec2616d1a24d... encodes an adrenaline mechanic where multiple attacks give lower attack power creatures more attacks than higher attack power creatures. Care was taken comparing how much decoding logic cost compared to storing unpacked values. AI evaluation uses 6 bit fixed precision because 64 encodes more efficiently than 128 in webassembly
Similarly there's a targeting mechanism with AND/OR & predicates. I used to have an AST like format with each predicate getting an enum & AND/OR being a slice of expressions. Now each expression is 32 bit integers encoding expression in polish notation, AND/OR have 2 bit codes & predicates are 6 bits (polish notation won over reverse polish here because with polish notation I was able to have AND/OR short circuit evaluation)