Well. Yes. I’ll try to add some background:
TSFE->tmpl has been substituted with the new TS parser. That property still exists in v12, but is unused and has been removed with v13.
The fact that the determined TS ‘constants’ and ‘setup’ are added to the Request directly with v12 is in general a very good thing, since it is one part in the puzzle of modeling FE state better, which ultimately leads to getting rid of Request attribute ‘frontend.controller’, $GLOBALS[‘TSFE’] and TypoScriptFrontentController class. This is an ongoing v13 process and we already made quite some progress.
The easiest way to deal with this in v12: Have a middleware after PrepareTypoScriptFrontendRendering (maybe even after the shortcut/redirect middleware), to manipulate (and re-with()) the ‘frontend.typoscript’ attribute to your liking, and if you can, without involving ContentObjectRenderer at best, to reduce complexity and overhead. This approach is similar to what you found already. This is why there is no event in v12: It can be done with a middleware. Also note that we modeled TS as object tree in v12, the array representation is only an extract from that: The object tree is the ‘leading’ structure. Core v13 starts actively using this in favor of the array representation, and maybe at some point in time, the array may be gone. This is why you may want to manipulate the object tree instead of the array with your custom middleware, if you want to be future proof (unfortunately, the object tree is still marked @internal, so it kinda depends on which specific risk you want to take with this detail: using the array and risking it breaks later, or using the tree and risking it breaks).
Note the v12 code-state is a bit ‘under construction’ in this area. The fact that TSFE->getFromCache() is such a longish thing is simply due to the fact that we did not manage to take next steps into v12 before time run out. I’ve added a couple of weeks worth of work to v13 in this area already, and we’re close to refactor exactly this part in v13, which makes the entire thing much easier to grasp and follow, and - of course and as usual - more flexible along the way. Looking at v13, you’ll find that a lot of state that hangs around in the TSFE ‘god’ class has already been turned into properly modeled Request state - basically all state that is important on the path to getFromCache() is done at the time of this writing already, except getFromCache() itself: That upcoming patch will crack some hard nuts related to long standing ‘locking’ related bugs, and it will make the ‘page cache’ logic much more powerful.
There is one additional detail you may need to want to keep in mind when transitioning TS related custom code from before v12 to v12: The new TS parser comes with improvements related to caching: TS is now cached across pages, plus it can be re-used when calling cached pages that have USER_INT or COA_INT objects. The first one means that a request to one page warms up TS of other pages that have ‘similar’ TS. The latter one means your FE with USER_INTs no longer scales with the amount of TS, but makes it a nearly static offset only. This has a huge positive impact on pages with INT’s, since an entire per-request parsetime multiplier has been substituted with a rather small, rather static offset. This is achieved by multiple pretty effective cache-layers within the FE TS parsing logic, including a ‘final’ uppermost layer to cache the full FE TS, which can be re-used across pages, and which did not exist before.
This is the main reason there is no general “manipulate your TS here” event within getFromCache() or the calling PrepareTypoScriptFrontendRendering middleware: When you add dynamic code at this point which adds TS state based on the current rootline, this is cached, and then spills over into other TS of pages that re-use the cache entries, because final cache TS does not include root line information, but only TS record information. This is also the reason why you won’t run into “cache overspill” trouble when you manipulate the TS records itself in an earlier middleware (as you outlined, and as b13 ext:bolt does as well, but only on a ‘per-site’ basis). However, in your case, it may be better to do your dynamic thing in a middleware after the TS Request attribute has been added, when TS stuff has been done, as outlined above, since that will suppress a multiplier to the number of different TS cache entries that can exist.
I hope that insight is helpful …