Thanks to therahedwig and Roses for the answers.
Regarding growths: Unless I'm blind (which cannot be ruled out) it's not in df.global.world.plants.all (which is what I referred to), and I've failed to find anything useful in the map blocks or columns. The closest thing is a list of references to plants present in the block, but that's just the same type as in the vector (and, I assume, are the same objects as those in the vector). There's also a vector of items on those tiles, but growths don't become items until they've been collected (I counted the number of bitter lemons in the item list [10] with a script, and that matched the number on the stocks screen, but that's far less than the 50-60 plants designated for gathering).
I tried to take a quick look at Stonesense (searching for "growth"), and it looks to me that growths are added based on whether the plant_raw data shows that the time is right for the growths, rather than whether the growths are actually there (I guess users of Stonesense could confirm whether a tree stripped of fruit looks different from a neighbor that hasn't been designated for fruit gathering). The Stonesense code also uses the data that specifies where growths should appear (trunk, branch, etc.), from the plant_raws, coupled with the generic tree info in the plant vector object that specifies where branches, etc. of each tree are located, so if there's a branch there and the tree species has a growth that grows on branches and the time is right growth tile processing is performed.