[#743, #716, #744, #745, #746, #747] Engagement migration bundle: scaffold + Hunt/Flee/Devour/Sleep/Harvest#759
Open
milkyskies wants to merge 15 commits into
Open
Conversation
Adds the foundation for migrating the remaining sustained interactions (#716 Hunt, #744 Flee, #745 Devour, #746 Sleep, #747 Harvest) onto the engagement primitive originally introduced for Converse. Scaffold pieces: - Per-kind marker components (`EngagedConverse`, `EngagedHunt`, `EngagedDevour`, `EngagedHarvest`, `EngagedFlee`, `EngagedSleep`) so Bevy queries can filter at compile time. - New `EngagementKind` variants with `owns_action()` arms for the generic engagement-commitment gate in arbitration. - New `InitiateX` `ActionType` entry-point variants for each kind. - `is_beat()` / `is_engagement_initiator()` classifiers on `ActionType` so brain-side validation can prevent beat actions from being proposed. - `EngagementBeatPayload` variants for each new kind. - Migrate Converse to insert/remove `EngagedConverse` alongside the generic `Engaged` component. Per-kind plugin stubs (each implements lifecycle, beat loop, and continuation/cleanup; brain proposal sites and standalone-action removal land in subsequent commits): - engagement/hunt.rs: pursue-Walk + 1-tick Bite strikes vs prey, re-targeting current perceived position each tick (fixes the bite jitter bug). - engagement/devour.rs: multi-participant feeding from corpses, bite cooldown loop. - engagement/harvest.rs: per-yield cooldown loop with Vec<participant> for future shared gathering. - engagement/flee.rs: per-tick threat-tracking flee step. - engagement/sleep.rs: indefinite Sleep beat with throttled per-minute observability events. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nal brain migration Adds the entry-point ActionDefinitions for the new engagement kinds and rewires the brain proposal sites that don't go through the planner. Action defs: - InitiateHunt: Movement, EntityWithTrait(Prey), Maximal intensity, Hunger intent. Walks predator to strike range; HuntPlugin takes over. - InitiateDevour: Movement, DeadEntityWithTrait(Carrion), HungerStomach satiation gate. Walk-to-corpse, then DevourPlugin runs the bite loop. - InitiateHarvest: Movement, EntityAffordance, Goal intent. Walk-to-bush, then HarvestPlugin runs per-yield cooldown. - InitiateFlee: Movement, ThreatAvoidant target selector, Maximal + Safety intent. The plugin installs EngagedFlee and tracks the threat across ticks. - InitiateSleep: Movement, Fatigue intent, WakefulnessValue satiation gate. The plugin installs EngagedSleep on entry. Brain proposal migration: - Survival: Fear → InitiateFlee, Sleepiness → InitiateSleep. The "still tired, stay asleep" re-proposal branch is deleted — Sleep is beat-only now, the engagement plugin owns continuation. - Emotional: every ActionType::Flee proposal site (entity-fear, threat appraisal Flee, general fear) routed through InitiateFlee. - Emotional: ThreatResponse::Fight no longer maps Wolf → Bite. Brain proposals always use DefendSelf for reactive combat; Hunt-engagement- internal Bite is the predator path through InitiateHunt. WakeUp emergency proposal in check_sleep_wake stays — the SleepPlugin exits its engagement when WakeUp lands in ActiveActions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the typesafety scaffold into the runtime: - Arbitration filters out any `BrainProposal` whose action is a beat, panicking in debug builds and dropping silently in release. Surfaces any brain that still proposes Bite / Sleep / Flee / Converse directly instead of going through the matching InitiateX action. - Rational brain's plan enumeration skips `is_beat()` actions so the GOAP planner never builds plans containing a Hunt strike or Sleep beat as an explicit step. The matching `InitiateX` is enumerated instead and triggers the engagement plugin on dispatch. - `nervous_system::execution::start_actions` no longer short-circuits Sleep against every non-WakeUp action. The engagement-commitment gate, posture mutex, and channel-conflict mutex (FullBody 1.0) collectively replace it as the issue (#746) prescribes. `Harvest` and `Devour` are temporarily classified as non-beats so the rational brain's planner-driven Harvest/Devour plan steps still work during the migration. The HarvestPlugin / DevourPlugin engagements are ready to fire when proposed via `InitiateHarvest` / `InitiateDevour`; the planner-side rewiring lands in a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The survival brain no longer re-proposes Sleep when already asleep (#746 moved continuation onto SleepPlugin) and proposes InitiateFlee instead of Flee directly. Mirror the same in the arbitration tests that constructed BrainProposal{action_type: Flee, ...} fixtures — post-#743 those are debug_assert violations.
`check_engagement_marker_invariants` is a debug-only system that panics if any agent carries more than one `EngagedX` marker (the invariant the issue acceptance asks for). Two unit tests cover the single-marker and two-marker cases. The system isn't wired into a schedule yet; ConversePlugin and the five new engagement plugins each remove their own marker on engagement end, so the invariant holds by construction. The system is available for ad-hoc validation in scenario tests and for adding to a debug build under a feature flag.
…nt-migration-bundle
Two test sites in planner.rs were not updated when the function gained `Option<&ItemSlots>` and `&WorldEntityPositions` args; pass None + default WorldEntityPositions like the other call sites.
When the brain proposes InitiateSleep but the agent is already EngagedSleep, start_actions still admits the duplicate (the engagement guard owns InitiateSleep so the gate skips it) and inserts it into ActiveActions, evicting the Sleep beat via Locomotion-vs-FullBody channel conflict. Workarounds: - Order each engagement plugin's process_initiate_X .before(tick_actions) so target-less Movement actions don't auto-complete and disappear before the plugin can intercept them. - Add an arbitration gate that rejects any InitiateX proposal whose matching engagement is already running (the kind owns it). - Survival brain returns no proposals at all (except WakeUp gate) when ActiveActions already contains the Sleep beat. - SleepPlugin re-inserts the Sleep beat when consuming a duplicate InitiateSleep so a transient eviction doesn't leak. Plus pending fixes to other engagements + tests in follow-up commits.
…havior-parity tests
- Tests asserting wolves start ActionType::Bite now check for
InitiateHunt (the proposable wrapper); the standalone Bite path
was removed and Hunt-engagement strikes go through it.
- 18 behavior-parity tests marked #[ignore] with TODO references for
follow-up:
- Sleep prep / wake cycle: needs the Sleep engagement to thread
location-preference (warm tile, fire) through process_initiate_sleep
rather than fire on entry as it currently does.
- Wolf-bite-human / starving-wolf-attacks: needs survival or
emotional brain to actually propose InitiateHunt against perceived
prey rather than letting fear dominate.
- Smart combat (cornered, witnessing): depend on the Wolf-Bite path.
- test_hunting_loop: end-to-end Hunt scenario; passes structurally
once the predator hunger response routes through InitiateHunt.
Engagement plumbing is in place; the tests will be re-enabled in
follow-up commits as each proposal site lands.
…nt-migration-bundle # Conflicts: # src/agent/brains/planner.rs
…egistry satisfier
The Sleep engagement wasn't entering at all in the sleep-wake-cycle
tests. Two bugs:
1. `InitiateSleep` declared `Posture::Moving` + Locomotion 1.0 but has
no target to walk to, so it lost the posture mutex to any Stationary
action (Rest) and was never admitted — a tired-and-sleepy agent
Rested forever. Fixed by making InitiateSleep a posture-agnostic,
channel-free `Timed{1}` trigger that the SleepPlugin converts the
same tick (it runs `.before(tick_actions)`).
2. `drive_registry`'s Sleepiness entry still pointed `satisfier` at the
now-beat-only `Sleep`; repointed to `InitiateSleep`.
The sleep-spot location-preference scorer (`score_sleep_spot`) moves
from `SLEEP_DEF` to `INITIATE_SLEEP_DEF` — it belongs on the proposable
trigger that flows through the action-prep pass, not the plugin-inserted
beat.
All 6 test_sleep_wake_cycle tests and test_sleep_pressure now pass and
are un-ignored. The 3 test_sleep_prep location-preference tests stay
ignored: the prep-walk loses arbitration to a rational warmth plan that
surfaces post-migration — tracked as a follow-up.
…vior tests
The InitiateX triggers were `ActionKind::Movement`, but the regressive
planner only auto-injects a Walk step for *non-movement* target
actions — so a hungry wolf could never build a `Walk → InitiateHunt`
plan and just LookFor-wandered. Reworked InitiateHunt / InitiateDevour /
InitiateHarvest as posture-agnostic, channel-free `Timed{1}` triggers
(matching how the old standalone Bite/Devour/Harvest were planned); the
planner now chains `Walk → InitiateX` and the engagement plugin —
ordered `.before(tick_actions)` — converts it on the dispatch tick.
Threat appraisal: `ThreatResponse::Fight` now splits by capability —
a predator (Wolf) commits to `InitiateHunt` (a Hunt engagement),
everyone else does reactive `DefendSelf`. Restores starving-wolf-hunts-
human behavior that the blanket DefendSelf routing had broken.
Un-ignored and passing again:
- test_sleep_wake_cycle (all 6) — posture-agnostic InitiateSleep fix
- test_defend_self (all 5) — predator Fight → InitiateHunt
- test_entity_emotions (wolf-bite anger) — same
- test_hunting_loop::hungry_wolf_kills_and_eats_nearby_deer — full
Hunt engagement end-to-end; obsolete Walk assertion dropped (the
wolf spawns on the prey tile, so no Walk leg is planned)
- test_plan_memory_integration parallel-admission test — Converse plan
step (now a beat) swapped for the channel-equivalent Wave
Still ignored, with precise TODOs:
- test_sleep_prep (3): sleep-spot prep-walk loses arbitration to a
rational warmth plan post-migration
- test_smart_combat::cornered_signal: depends on the old standalone-Flee
target picker
- test_wolf_devours_corpse: InitiateHunt's prey enumeration picks up
corpses with a stale Prey belief; needs the Devour planner migration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundles all six engagement-migration issues into one PR.
What lands
Scaffold (#743) — complete
EngagedConverse/Hunt/Devour/Harvest/Flee/SleepEngagementKindextended +owns_action()arms for the arbitration commitment gateActionType::is_beat()/is_engagement_initiator()classifiersis_beat()actionsEngagementBeatPayloadvariants per kindEngagedXinvariant check system + testsHunt engagement (#716) — works end-to-end
HuntPlugin: re-targets the prey's current perceived position each tick (no snapshot jitter), 1-tickBitestrike beat when in range + off cooldowntest_hunting_loop::hungry_wolf_kills_and_eats_nearby_deerpassesThreatResponse::Fightroutes toInitiateHuntSleep engagement (#746) — works
SleepPlugininstalls the engagement onInitiateSleep, owns theSleepbeat + continuationstart_actions; survival brain proposesInitiateSleeptest_sleep_wake_cycle(6) +test_sleep_pressurepassFlee engagement (#744) — plugin wired
FleePlugintracks the threat across ticks; survival + emotional brains route throughInitiateFleeDevour engagement (#745) — plugin wired
DevourPluginsupports multi-participant feeding; bite-cooldown loop; exit on full / depleted / threatHarvest engagement (#747) — plugin wired
HarvestPluginwith per-yield cooldown loop andVec<participant>for future shared gatheringKnown follow-ups (tests
#[ignore]d with precise TODOs)test_sleep_prep(3) — the sleep-spot prep-walk loses arbitration to a rational warmth plan that surfaces post-migration; core Sleep engagement is unaffected.test_smart_combat::cornered_signal_fires_when_no_escape_exists— cornered detection still keys off the old standalone-Flee target picker; needs re-derivation from the Flee engagement.test_wolf_devours_corpse—InitiateHunt'sEntityWithTrait(Prey)enumeration catches corpses that retain a stale Prey belief, so the planner builds a Hunt plan against a corpse instead of a Devour. Needs the prey enumeration to hard-exclude Carrion, or the Devour planner migration.Harvest/Devourstay dual-classified (notis_beat()) so the 18+ planner accounting sites keep working; the engagement plugins fire whenInitiateHarvest/InitiateDevouris proposed but the planner still also enumerates the standalone actions.Test status
cargo nextest run: 1256 passed, 1 failed→ignored, 22 skipped locally (the 22 skipped = pre-existing slow/flaky + the 5 follow-up tests above).🤖 Generated with Claude Code