This is a learning process!
Most of my programming experience stems from Web development, although I have been dabbling in hardware/electrical engineering in the past few years. In web development, I rely heavily on automated tests to guarantee that my code works, and sometimes I even practice Test Driven Development. Game development is a relatively new area of focus for me, and therefore much of my experience has been exploratory in nature. Whereas in web development, the various modules and components that I make tend to be exercised in specific, isolated scenarios that are easy to test at the boundaries, in the sort of game I am making, there are a massive number of possible interactions between the various objects and systems and no clear way to test them in an automated way.
Finite State Machines to the rescue!
As I started to add more creatures, plants, and their respective behaviors to Into the Thicket, I began encountering strange, hard to track-down bugs (no pun intended) such as ants getting stuck in weird places, attempting to cut down a blade of grass when they already have one held in their mouths, or staying engaged in combat indefinitely with no victor or loser. I realized the ony way to solve the issue was to fall back on a technique I've used to great effect in my hardware projects, The Finite State Machine. Instead of trying to manage all the individual creature's decisions based off the various parameters of their internal state, I modeled each creature/plant as an FSM. What this essentially means is that there are a finite number of specific states that each of the creatures can exist in at any given moment in time, and so all those various internal attributes only matter in their deicision making process while they are in specific pertinent states.
Ant Finite State Machine Diagram
Grasshopper Finite State Machine Diagram
These State Machine diagrams will evolve over time as additional behaviors are added or changed. Here are the links if you are interested in checking them out:
A (sorta) bug-free experience
It was a non-trivial amount of work to refactor all the systems to use this new architecture, but the benefits were realized immediately. Many of the bugs, especially surrounding ant combat, disappeared overnight and the simulation feels much more stable overall. I purposely exported this version with the targeting system and Foraging/Attack radiuses visualized so you can more easily take in the complexity of the underlying simulation.
Ant debugging visualizations
Debugging visualizations explained
Each creature with the Targeting behavior gets a targeting arrow that extends between it and it's target. Ants have two line of sight radii. The first, larger radius is the Ant's foraging radius. The ant can only target food if it is within this line of sight. The second, smaller radius represents the Ant's attack radius. If an enemy ant appears within this radius, so long as the ant is not already delivering food back to its Ant Hill, the ant will prioritize meeting the threat with force. When an Ant decides to 'charge' an enemy, its attack radius turns yellow. When an ant collides with an enemy ant, the attack radius turns red, to indicate that combat has begun. When combat has finished, the Ant's attack radius turns white to indicate it has returned to the 'idle' state, and the Ant picks a new target. When an Ant has a viable food source in its line of sight that is not already targeted by a member of its team, it selects it as its target, and the arrow turns green to indicate that the Ant is on its way to cut down and gather that food source. Once an Ant has successfully acquired a viable food source, the arrow turns yellow to indicate that it is delivering it back to the Ant Hill. In the event that an Ant has no viable food source or enemy in its line of sight, it picks a random location on the map to scout. If, during foragin the Ant comes across a viable food source, it selects that food as its target and the cycle continues. Otherwise, if the ant gets all the way to its foraging target without coming across food or an enemy, it selects another random foraging target and starts looking again.