project() read canvas.clientWidth/clientHeight on every call, and it runs for every tile corner each frame. Profiling a 1,676-file repo put 216 ms of each 3D frame in project(); with the size cached per frame and the trigonometry per camera change it drops to about 7 ms.