One of the things a 6502 is bad at is multitasking. I've tried to understand how to mitigate the shortcomings by designing the (virtual) HW around it so that it becomes as useful as possible. Always with a mind that a real implementation in HW would be possible and not too complex.
I've implemented a simple bank-switched system with 8KiB banks. The top page $FFxx is always locked to the same memory block, so vectors and its code can be the same for all tasks. The I/O-page at $FExx is also always locked.
A task gets 1-8 blocks of 8KiB memory, and the first always reside in bank 0. A task will thus always get a FULL zero page and stack of its own, which is very useful on a 6502. A task will get minimum 7.5KiB for code and data, maximum 63KiB without bank switching. And don't forget: a full zero page for each task.
The timer is designed so that it was as easy as possible to implement an efficient pre-emptive multitasking scheduler.
- Every task gets a time slot allowance (timer value).
- The timer can be paused, so the slot time is correct even if the task was interrupted by an NMI.
- NMI is a specific task done at frame rate 50Hz so it is easy to sync precisely.