LoopStrategyStateTrait
{@see \NHA\StateStore} slice: the autoplay loop's strategic memory — the forced-objective rotation and its cooldown (armed when {@see \NHA\Brain\AutoPlayer::detectLoop()} catches the agent looping), the persisted {@see \NHA\Brain\Stance}, and the inventor-points trend that tells the fallback whether research is still paying.
Relies on the host's array $data and save().
Tags
Table of Contents
Constants
- OBJECTIVE_ROTATION : mixed = ['explore', 'wealth', 'build', 'research']
- Objectives cycled through, in order, each time the agent is caught looping.
- FORCED_OBJECTIVE_TTL_TICKS : mixed = 45
- A forced objective sticks for this many world ticks after a loop break.
- LOOP_BREAK_COOLDOWN_TICKS : mixed = 24
- After a loop break, do not break again for this many ticks — let it play out.
- RESEARCH_PAYING_WINDOW : mixed = 900
- Research counts as "paying" for this long after the last inventor-point gain.
Methods
- bumpForcedObjective() : string
- Advances the agent's forced-objective cursor one step through {@see self::OBJECTIVE_ROTATION} and returns the new objective. Called by {@see \NHA\Brain\AutoPlayer} when it detects the agent is stuck in a loop, so each successive loop break tries a *different* kind of goal.
- getForcedObjective() : string|null
- The objective forced by the most recent loop break, or `null` once it has aged out ({@see self::FORCED_OBJECTIVE_TTL_TICKS} ticks) — after which the agent is back on the normal ladder.
- getStance() : array{stance: string, tick: int}
- The agent's persisted strategic stance and the tick it last changed (`{stance, tick}`). Defaults to `homestead` at tick 0.
- loopBreakCooldownActive() : bool
- Whether a loop break happened too recently to break again — the forced objective (and the brain turns after it) need a few ticks to actually change the situation before the loop detector is allowed to fire once more.
- noteInventorPoints() : bool
- Records the agent's current lifetime `inventor_points` and reports whether research is paying *right now* — the score rose this turn, or rose within the last {@see self::RESEARCH_PAYING_WINDOW} seconds.
- peekNextForcedObjective() : string
- The objective {@see bumpForcedObjective()} WOULD return next, without advancing the cursor or arming the cooldown — so the loop guard can name the objective in the brain prompt and only commit it once the turn actually applies it.
- setStance() : void
- Records the agent's stance. `$tick` is only stamped when the stance actually changes, so it marks the last *switch* for the dwell timer.
Constants
OBJECTIVE_ROTATION
Objectives cycled through, in order, each time the agent is caught looping.
public
mixed
OBJECTIVE_ROTATION
= ['explore', 'wealth', 'build', 'research']
FORCED_OBJECTIVE_TTL_TICKS
A forced objective sticks for this many world ticks after a loop break.
private
mixed
FORCED_OBJECTIVE_TTL_TICKS
= 45
LOOP_BREAK_COOLDOWN_TICKS
After a loop break, do not break again for this many ticks — let it play out.
private
mixed
LOOP_BREAK_COOLDOWN_TICKS
= 24
RESEARCH_PAYING_WINDOW
Research counts as "paying" for this long after the last inventor-point gain.
private
mixed
RESEARCH_PAYING_WINDOW
= 900
Methods
bumpForcedObjective()
Advances the agent's forced-objective cursor one step through {@see self::OBJECTIVE_ROTATION} and returns the new objective. Called by {@see \NHA\Brain\AutoPlayer} when it detects the agent is stuck in a loop, so each successive loop break tries a *different* kind of goal.
public
bumpForcedObjective(int $agent_id, int $tick) : string
Parameters
- $agent_id : int
- $tick : int
Tags
Return values
stringgetForcedObjective()
The objective forced by the most recent loop break, or `null` once it has aged out ({@see self::FORCED_OBJECTIVE_TTL_TICKS} ticks) — after which the agent is back on the normal ladder.
public
getForcedObjective(int $agent_id, int $tick) : string|null
Parameters
- $agent_id : int
- $tick : int
Tags
Return values
string|nullgetStance()
The agent's persisted strategic stance and the tick it last changed (`{stance, tick}`). Defaults to `homestead` at tick 0.
public
getStance(int $agent_id) : array{stance: string, tick: int}
Parameters
- $agent_id : int
Tags
Return values
array{stance: string, tick: int}loopBreakCooldownActive()
Whether a loop break happened too recently to break again — the forced objective (and the brain turns after it) need a few ticks to actually change the situation before the loop detector is allowed to fire once more.
public
loopBreakCooldownActive(int $agent_id, int $tick) : bool
Without this the loop-break moves themselves keep the "no productive action" window full and it thrashes every turn.
Parameters
- $agent_id : int
- $tick : int
Tags
Return values
boolnoteInventorPoints()
Records the agent's current lifetime `inventor_points` and reports whether research is paying *right now* — the score rose this turn, or rose within the last {@see self::RESEARCH_PAYING_WINDOW} seconds.
public
noteInventorPoints(int $agent_id, int $points) : bool
Inventor points never drop (a rejected invention only refunds ingredients),
so an absolute > 0 test stays true forever once an agent has invented
anything. The autoplay fallback needs the recent trend instead: keep
speculating while discoveries are still landing, fall to infrastructure
once they dry up. The first sighting only sets the baseline and returns
false (no trend yet).
Parameters
- $agent_id : int
- $points : int
Tags
Return values
boolpeekNextForcedObjective()
The objective {@see bumpForcedObjective()} WOULD return next, without advancing the cursor or arming the cooldown — so the loop guard can name the objective in the brain prompt and only commit it once the turn actually applies it.
public
peekNextForcedObjective(int $agent_id) : string
Parameters
- $agent_id : int
Tags
Return values
stringsetStance()
Records the agent's stance. `$tick` is only stamped when the stance actually changes, so it marks the last *switch* for the dwell timer.
public
setStance(int $agent_id, string $stance, int $tick) : void
Parameters
- $agent_id : int
- $stance : string
- $tick : int