Tak szczerze powiedziawszy, bez sensownego dostępu do wielowątkowości, to byłoby to zapewne trudne do osiągnięcia - ale jakby mocno skupić się na optymalizowaniu, to może nawet w miarę sensowny klatkarz można uzyskać.
Kilka propozycji jak można zoptymalizować rysowanie fog of war:
Nie aktualizuj wszystkich jednostek, ale tylko komórki, które są zajęte przez jednostki. Do tego możesz wykorzystać "słownik", np. tworzysz loopa po liście monitor_list tak jak do tej pory, ale przy okazji zapisujesz w której to komórce jednostka rysowała swój fog of war i np. przed shroud_clear_position może sprawdzić, czy tej komórki już nie liczyłeś, taki pseudo kod:
var dict = new Dictionary
for unit of unit_list:
if dict.has(key: unit.x + '_' + unit.y)):
continue
shroud_clear_position(unit.x, unit.y)
dict.set(key: unit.x + '_' + unit.y, value: unit.shroud_radius)
jeżeli każda jednostka ma własny shroud_radius, to możesz przed shroud_clear_position zapełnić nową listę ale tylko z jednostkami o największym shroud_radius na daną komórkę.
Dodatkowo możesz zoptymalizować samą funkcję shroud_clear_position - https://www.redblobgames.com/grids/circle-drawing/ polecam
Nie modyfikuj tablicy shroud_grid jednostkami poza ekranem, monitor_list powinien posiadać tylko jednostki, której pierścień fog of war może być widoczny.
Możesz spróbować też brać tylko wycinek z listy monitor_list i co kolejna klatka aktualizować tylko część jednostek - jeżeli aktualizacja tablicy będzie się działa np. 15x na sekundę to raczej nikt nawet nie zauważy zastosowanego triku.
Jeżeli jeszcze na coś wpadnę to chętnie się podzielę pomysłami.