Releases: ConGan98/FS25_AnimalHerdingLite
Release list
v0.0.6-Dev
individual variation pass: personality, behaviour, sounds + audit fixes + Hof Bergmann species support
Adds deterministic per-animal variation across personality, idle behavior,
and sound layers so herds no longer move/sound in lockstep. All variation
seeded from self.id via Knuth-multiplicative hash — server/client agree,
no global RNG mutation, no new bytes on the wire.
Personality jitter
- New PersonalityJitter.lua: per-individual ±10–25% offsets on user-visible
behavior fields (startleThreshold, arousalDecay, gaze/linger timings,
walk/run distances, grazeChance). Group-level and mechanical fields not
jittered — would break flocking coherence rather than add personality. - Single integration point in HerdableAnimal:validateSpeed.
- Fix: client path called validateSpeed before readStream populated self.id,
causing server/client desync. Added second validateSpeed call after
readStream in both HerdingEvent paths (idempotent). - Known limitation: loadFromXMLFile mints fresh ids, so personalities
reshuffle across save/reload. Stable within a session.
Bucket follow + coalescing flee
- HerdableAnimal:1247: removed the
bucketDist > 15mearly-exit on the
turn gate. Created a 15–20m band where animals walked toward bucket
carrier without turning, often fleeing the sustain bubble. Bucket is
the destination — always turn toward it. - HerdableAnimal:1429 (Stage C): flee vector now blends 0.4 toward herd
centroid so prey species tighten under threat instead of starbursting.
Tunable via COALESCE_WEIGHT.
Calm behavior cycler
- New CalmBehaviorCycler.lua: replaces lockstep isGrazing=true with a
per-animal weighted pool of {graze, eat, chew, idle}, species-tuned to
real animal time budgets, deterministic phase offset per animal. - AnimalAnimation idle branch reads calmBehavior.id instead of hardcoding
"idle"/"graze". Pool entries filtered against cache.states at pick time. - Fix: AnimalAnimation:413 getRandomAnimation used math.random — two
animals installing the same state on the same tick picked the same
variant. Replaced with per-(animId, time-bucket) hash so animals
desync naturally and a single animal also varies over time. - Deferred: sleep/lying poses, species specialty clips, drink-at-trough.
Bunny-hop fix
- HerdableAnimal:784/1008: world translation and animation playback ratio
had asymmetric ceilings. During fast run→walk, body translated at full
self.speed while feet capped at 1.3× targetSpeed → foot-skating. - Added per-frame translation cap mirroring the animation ceiling:
effSpeed = min(self.speed * speedScale, 1.3 * targetSpeed). Worst case
is now a tiny brake instead of a hop.
Animal sounds — phase 1 (yells)
- New AnimalSounds.lua with per-species def tables (cow/pig/sheep/horse/
chicken). Lazy sample loading with pcall guard; failed species marked
and never retried. - Per-animal scheduling via deterministic Knuth hash (self.id, cycle).
Crowd scaling matches engine yellTimerCrowdScale so herd-wide yell
density stays roughly constant regardless of headcount. - Distance cull before any work; linear volume attenuation from listener
distance (createSample API is 2D — no exposed 3D node attachment). - Single integration point in AnimalManager per-farm loop. Map-bridge
species (rabbit/goose/alpaca/etc.) get 60s deferred check.
Yell tuning
- First playtest: too frequent, too loud, occasional calf-pitched moos.
- Roughly halved frequency (intervals ~doubled, crowdScale halved).
- Halved baseVolume across all species — AHL's linear attenuation rolls
off softer than engine spatial audio, so engine nominal volumes felt
too loud as-is. - pitchVar 0.06–0.10 → 0.03–0.04. ±8% on cow pushed adult samples into
calf register; ±3–4% still masks repetition without sounding juvenile.
Animal sounds — phase 2 (eating)
- AnimalSounds.eatTick: pulsed one-shots at 1.5–3s intervals while
CalmBehaviorCycler is in graze/eat/chew. stopSample isn't visible from
decoded scripts, so pulsed playback avoids stuck-loop risk; pulse
intervals tuned just under sample length to read as continuous. - Tighter 22–25m cull than yells (eating is intimate). Cull-skip
reschedules timer rather than nilling so playback resumes cleanly. - Chicken: no eat sound — engine doesn't ship one.
Animal sounds — phase 3 (footsteps)
- AnimalSounds.stepTick: pulsed one-shots driven by isWalking/isRunning,
with separate walk/run intervals so a fleeing herd's gait audibly
speeds up. ±10% per-step jitter so rhythm doesn't sound mechanical. - Skips inPureTurn (rotating in place, no translation) and idle/calm.
- Tightest cull of any sound category (15–25m). Worst-case load: 30 cows
× 2 steps/sec; aggressive cull means only 3–6 audibly stepping at once.
Player collision vibration
- AnimalCollisionController front probe includes CollisionFlag.PLAYER,
causing walk → hard-cut idle → position already advanced into player →
player shuffles → overlap clears → translate again → re-collide. Visible
forward/back chatter. - Routed player hits to separate hasFrontPlayerCollision flag (parent-
chain walk to g_localPlayer.rootNode, bounded). Existing stuck-timer
and escape-turn logic no longer fires for player-only collisions. - HerdableAnimal:782: skip per-frame position write and force isIdle when
flag set last frame. One-frame stale is fine — the vibration scenario
is exactly when the previous frame had collision.
Audit fixes (HIGH)
- Player-collision idle force no longer overrides stuckEscaping; previously
cancelled mid-escape rotation if player walked in front. - BUCKET_ARRIVED_DIST 3.0 → 2.2m. Old deadzone could overlap wrong-
husbandry delivery areas and trigger auto-absorb on the same tick. - typeToSpecies reverse lookup in AnimalSounds — was iterating SOUND_DEFS
pairs() per yell/eat/step schedule (dozens/frame on a herd). O(1) now. - Cached getListenerXZ by g_time — three sound ticks per animal previously
queried world translation independently (90+ engine queries/frame on a
30-cow herd). Single lookup per frame now.
Audit fixes (MED)
- CalmBehaviorCycler returns idle-only when cacheStates is nil (early-load
race). Old fall-through could pickeatfor an animal whose cache lacked
the state — visually fell back to idle but isGrazing tracked wrong id. - AnimalAnimation:getMovementSpeed: combined two transition iterations into
one shared accumulate(trackSet) local. - Per-node trigger cache in AnimalCollisionController. string.lower +
string.find on every overlap hit per frame is now a per-node bool cached
on first encounter. - HerdableAnimal nearest/threat tables reused from per-animal scratch
instead of fresh allocations per closer candidate (~150 alloc/frame on
a 30-cow herd with 5 influencers each).
v0.0.5-Dev
FS25_AnimalPackage_vanillaEdition compatibility, RABBIT species + Witcombe support
Pickup (own InputAction):
- Replace piggy-backed engine INTERACT/onInputEnter approach with dedicated
AHLPickup action bound to KEY_e in modDesc.xml (EN/DE/FR i18n added). - Forward-declare onAHLPickupAction in PlayerInputComponent.lua so the
appended registerGlobalPlayerActionEvents wrap captures it as an upvalue
(file loads via extraSourceFiles before AnimalManager.lua exists). - New activatePickupPrompt(self, textKey) helper replaces four
setActionEventText/Active(self.enterActionId, ...) call sites; stashes
active input component on g_animalManager for the global callback. - Default-off block in PlayerInputComponent.update prevents sticky prompts.
- Nil-guard Animal:getCanBePickedUp (weight, age) and HandToolAnimal:
setEngineAnimal (husbandry/animal/clone) for Witcombe bridge subTypes
missing reproductionMinAgeMonth.
Trailer auto-load flush (RLRM 1.2.2.0 regression):
- RLRM 1.2.2.0 removed per-cluster updateNow() from LivestockTrailer:
addCluster (batched at addAnimals tail). Single-cluster callers now
must flush themselves. - Add pcall'd clusterSystem:updateNow() after addCluster in
returnAnimalToTrailer (herding-mode, 8m) and loadCarriedAnimalIntoTrailer
(carry-mode, 4m). Fixes animals vanishing on auto-load.
Pickup prompt recovery after vehicle teleport:
- AHLPickup eventId went stale after input-context churn; engine doesn't
always re-call registerGlobalPlayerActionEvents on teleport. - Extend refreshHerdingFootEvent to also remove + re-register AHLPickup
in the same beginActionEventsModification transaction. Stash callback
ref on g_animalManager._onAHLPickupAction for cross-file access. Bails
cleanly if InputAction.AHLPickup or stash is nil.
RABBIT species + Witcombe shed Y-vibration:
- Add RABBIT block to AnimalSpeciesConfig.lua: prey tuning (low
startleThreshold, slow arousalDecay, tight cohesion, small body, brief
preFleeGazeMs, high speedAccelMps2). - Suppress +0.2m step-up bump on elevated placeable surfaces via new
onElevatedSurface flag (set when surfaceRaycastHitY > terrainH).
STATIC_OBJECT shed floor was triggering the probe-hit/step-up/probe-miss
vibration loop. - Call updateTerrainHeight per-tick from HerdableAnimal:update (was every
10 ticks); don't clear surfaceRaycastHitY before re-firing raycast;
smooth step-up toggle via smoothedStepUp ramp (~1.0 m/s).
FS25_AnimalPackage_vanillaEdition compatibility:
- Detect pack via RLSettings.SETTINGS.useCustomAnimals.state == 2 and
non-nil animalsXMLPath (accessed through FS25_RealisticLivestock(RM)
mod table, not global scope). Sets CONFLICTS.ANIMAL_PACK_VANILLA. - Add third pickup path in HandToolAnimal: setEngineAnimal and onPostLoad
clone animated cache.root + applySleepPose, skipping applyRLVisuals
(its hardcoded safeIndexToObject paths corrupt pack i3ds, manifesting
one frame later as a silent crash). setHerdingAnimal relaxed to steal
herded mesh whenever RL is loaded. - Expand applySleepPose clip fallback list to 9 names: sleepSideLBabySource,
FA_sleep01/02, BR_sleep01/02, FA_rest01, BR_rest01. - README: pack row changed to "Compatible".
v0.0.4-Dev
Locomotion & Animation Overhaul: Turn Clips, Speed Sync, Flicker Fix & Dog Passenger Feature
Changelog
2026-04-21 — Locomotion & Animation Overhaul: Turn Clips, Speed Sync, Flicker Fix
Turn animation clips
- Wired dedicated turn clips (45°/90°/135°/180° per side) into per-species
turnLeft/turnRightstates; horse reuses one clip at speed ×2/3/4. AnimalManagerandAnimalSystemhusbandry loaders now readrotationanddistanceattributes from<animation>elements and build per-sideturnBucketssorted by rotation, so the runtime picks the smallest clip covering the remaining angle.- Added
cache.hasTurnStatesas the fast fallback flag for species without turn data (baby sheep, dogs).
Turn gating & rotation
HerdableAnimal's yaw lerp still drives world rotation (turn clips havedistance=0, pure bone animation).- New force-idle gate: when
state.isTurning=trueAND remaining angle > 30° (turnInPlaceMinDeg, chicken 20°), translation freezes soAnimalAnimation's turn branch plays the dedicated clip. - Shallow heading changes keep the existing walk path with subtle left/right track bias.
- Bucket-follow and collision-escape bypass the gate to preserve their existing motion.
Speed smoothing
- Added
turnInPlaceMinDegandspeedAccelMps2fields toAnimalSpeciesConfigper species. speedAccelMps2clamps the rate of change onself.speed(2.0 cow/horse, 2.5 sheep/pig, 3.5 chicken) so the walk↔run transition no longer lurches at the momentspeedModifiersnaps.- The engine's clip blend already ramps the pose over 750 ms; this clamp ramps world translation to match.
Animation playback speed tracking
- New
AnimalAnimation:setPlaybackSpeedScale(scale)adjusts every enabled track'ssetAnimTrackSpeedScaleby (baseModifier × scale). HerdableAnimalcalls it each tick withscale = (self.speed × lastMoveSpeedScale) / targetSpeed, wheretargetSpeed = animation:getMovementSpeed()(nominal m/s the clip was designed for).- Fixes visible moonwalk/skating-feet when the walk clip played at full nominal rate while
self.speedwas still ramping. - Ratio clamped to
[0.3, 1.3]so transition-edge spikes (briefly tinytargetSpeedduring walk→idle blend) don't send playback to 5×. - 2% deadband on re-apply to avoid per-frame API churn.
invalidatePlaybackSpeedCache()called at every track-install site so new tracks get the correct scale on their first tick.
Misc locomotion fixes
- Reset
self.lastMoveSpeedScale = 1at the top of eachHerdableAnimal:update— previously a stale grazing scale (0.5) leaked into the next walk session, making animation appear half-speed. - Raised minimum graze distance from 0 to 2 m (
r = 2 + random() × max(grazeRadius - 2, 0.5)). Previously ~25% of graze targets landed inside the 0.5 m arrival threshold, so walks "arrived" within a second — start-walk clip never completed. Walks now run ≥ ~4 s.
Turn-clip flicker fix
- Animated turn clip entry was creating a 30–60 installs/sec flicker loop (idle-install ↔ turnRight-install) as flee/alert/calm branches bounced
state.isTurningevery frame via the ±0.05 radalreadyFacingcheck. - Fix: persistent
self._inPureTurnstate onHerdableAnimalwith explicit entry/exit:- Enter:
state.isTurningAND remaining > 30°. - Exit: remaining < 3° AND NOT
state.isTurning(both required, so a single-frame drop-out doesn't bounce out).
- Enter:
- Publishes
state.inPureTurnsoAnimalAnimation's turn branch keys off that flag instead ofstate.isTurning(decoupled from the turn block's per-frame clear). AnimalAnimationalso early-outs when a turn clip is already playing — never re-picks side/bucket mid-turn, so a transienttargetDirY=0can't cause a left↔right swap.
Behaviour tuning
- Cow behavior radius 0.6 → 0.45 and
separationWeight1.0 → 0.5 so cows side-by-side don't visibly space out. Behavior-only; does not affect engine physics-proxy collision.
Dog reaction time (partial)
DOG_MOVE_COMMAND_INTERVAL4 → 1:GotoEntityre-issued every frame (~16 ms) instead of every 4 ticks (~66 ms).- Immediate re-issue when the orbit jumps > 0.8 m since the last command (catches phase flips without waiting for the interval).
- Adaptive orbit projection during APPROACH:
- Orbit at target + 10 m when dog is > 5 m from target (far enough to push the engine into run gait past the ~5 m follow-range hysteresis).
- Shrinks to target + 3 m when close (matches the companion's ~3 m arrival threshold so the dog rests AT the target, not short of it).
- Straight 15 m projection was tried and reverted — dog overshot the animal.
Stuck-escape interaction
- Animal stuck-escape vs. flee guarded via
self.stuckEscaping— flee branch no longer overwrites the escape heading; animal rotates to pen interior and breaks free instead of oscillating against the fence.
2026-04-22 — Dog Passenger Feature
Feature summary
- Replaces the previous "dog runs home when you enter a vehicle" safety behaviour.
- When the local player enters a vehicle exposing a
passengerSeat##PlayerSkinnode (##= any digit sequence) while their companion dog is following, the dog now rides along — seated with a sit-loop animation playing — and resumes following on foot when the player exits. - Vehicles without a matching seat node fall back to the original
goToSpawn()behaviour, so the feature only engages where a seat is available.
Why a cloned mesh, not the live companion
- Live companion rendering ignores scene-graph parenting (confirmed by the failed attempt on 2026-04-18), and the engine exposes no API to move it into a seat.
- Instead, the mod loads the shipped animated dog i3d for the matching breed:
dataS/character/animals/domesticated/dog/labradorRetriever/labradorRetriever.i3ddataS/character/animals/domesticated/dog/borderCollie/borderCollie.i3d
- Links the loaded root under the vehicle's seat node and drives the
sitSourceclip on track 0 via the i3d's animation character set. - New file
src/DogPassengerMesh.luahandles acquire/release. - Posed i3d variants were ruled out — they carry no skeleton and can't take animation.
Live companion handling
- Does NOT toggle visibility or physics-inert —
setCompanionsVisibility/setCompanionsPhysicsUpdatecrashed the game on exit (companion manager left inconsistent, hard crash on next tick with nothing in the log). - On enter:
setCompanionPositionteleports live dog 10 km off-map, thenDog:goToSpawn(). - On exit:
setCompanionPositionteleports live dog to player's position, thenDog:followEntityresumes follow. - Keeps both dogs at the same spot the player sees.
Texture atlas copy
- Atlas is copied from the live companion to the clone on install.
- Base-game dog i3ds share one material and differentiate breed variants via
atlasInvSizeAndOffsetUVin the shader parameter block — an un-copied clone renders the default 0,0 atlas tile regardless of variant. - Reader walks the live companion's scene subtree for shader-parameter nodes gated by
getHasClassId(node, ClassIds.SHAPE). - Calling
getHasShaderParameteron non-shape nodes (bones, transforms) throws a Lua error per call; FS25 printed full stack traces for every bone, filling disk and freezing the game. TheClassIds.SHAPEgate silently skips non-shape nodes and eliminates the spam.
Multiplayer sync
- New
DogPassengerEvent(src/events/DogPassengerEvent.lua). - Fields:
dog(NetworkUtil node object),vehicle(only if entering),enterbool. - Routing: clients send to server, server rebroadcasts to all clients (standard pattern).
installPassengerVisualis idempotent for the same vehicle so the echo back to the sender doesn't flicker.applyRemotePassengerEnter/applyRemotePassengerExitrun the visual swap without further network echo.
Per-vehicle offsets
src/DogPassengerOffsets.luakeyed byvehicle.configFileName.- Default offset is zero (dog sits at seat-node origin).
- Override entries can be added for vehicles whose seat-node origin places the dog poorly.
DogPassengerOffsets.set/.getare the runtime API for live tuning (console-command wiring left for a future pass).
Deferred shared-i3d release
- Calling
g_i3DManager:releaseSharedI3DFile(requestId)immediately on vehicle-exit crashed the game — engine appears to still hold the handle for a frame or two afterward. DogPassengerMesh.releasenow unlinks + deletes the clone root (withentityExistsguard + re-parent to root before delete, in case the seat node is being torn down) and returns thesharedLoadRequestId.DogHerdingenqueues it inself.pendingI3dReleaseswithreleaseAtMs = g_time + 500and drains the queue at the top ofupdate().- Fixes the ~1.7 MB per-session leak without re-introducing the crash.
Safety watcher
- If the vehicle carrying the passenger dog is deleted mid-ride,
DogHerding's per-tick validity check (entityExists(state.vehicle.rootNode)) tears down the passenger state and resumes follow automatically, so the cloned mesh doesn't end up orphaned under a freed scene graph.
v0.0.3-Dev
Realistic animal pipeline, boids flocking, animation naming, and herding QoL fixes Add dog herding(50% working)
Animal behavior:
- New AnimalSpeciesConfig with per-species tuning (cow/sheep/pig/horse/chicken)
- Replaced nearest-influencer flee with 4-stage pipeline: sense → arousal → forces → blend
- Added Reynolds boids flocking (cohesion/alignment/separation) via spatial hash
- Grazing behavior when calm + idle; random targets within grazeRadius
- Vehicle-class influencers with species-specific scare multipliers
- Fixed stale-threat check using pedestrian threshold for vehicles
- Front-neighbor yield + velocity-weighted geometric push to prevent clustering
- 500ms stall detector forces idle when walking in place
Dog herding 50% working:
- Rewrote FSM to minimal GATHER/DRIVE with hysteresis (5m/3m thresholds)
- Dog injected as single vehicle-class influencer at farm.position
- Auto-return to doghouse when player enters vehicle while dog follows
- Removed failed dog-passenger experiment (engine limitations)
- Retained dumpDogHerdState debug command
Regular herding:
- Reaction radius (10m) and rotation radius (12m) for localized response
- Per-animal walk-toward-player synthetic pull
- Arrival (3m) and peer-cone (2m, ±45°) force-idle checks
- Force walk-only animation clamped to dog herding (not regular)
- Dog-reaches-animal-first gate (1.5m) before herd starts moving
Animations:
- Configurable per-type + per-age clip names (AnimalAnimationNaming.lua)
- Applied on all three HerdableAnimal creation paths
- Baby sheep walk mirrored from run clip (missing run asset workaround)
- Removed per-species speedMultiplier and YOUNG_SHEEP_SPEED_BOOST
Input/UX fixes:
- Auto-stop herding on animal/husbandry GUI open
- Auto-stop herding on game quit (saves reflect pen state)
- Fixed missing F1 prompt after tab-through-vehicles via full
removeActionEvent + registerActionEvent cycle - Added input-context-change watcher (GUI-close watcher missed vehicle tabs)
- refreshHerdingFootEvent applies correct display state immediately
- Reduced husbandry auto-deposit radius to 15m (0 for carried)
Trailer loading:
- Auto-load carried animal into compatible farm-owned livestock trailer
within 4m; falls back to "Return Animal" prompt
Debug:
- toggleAnimalAnimDebug console command for clip ID / playback speed dumps
v0.0.2-Dev
Return Animal & Pickup Warnings +Trailer Capacity, Chicken Pickup & Herding Guard
-
Added "Return Animal" action: pressing E while carrying an animal returns it
to its original husbandry. Falls back to the nearest compatible husbandry with
free slots if the original is full or no longer exists. -
Fixed returnCarriedAnimalToHusbandry crash ("attempt to index nil with
'placeable'"): was trying to access the HandToolAnimal spec via g_currentModName
which is nil at runtime. Now reads the placeable ID from the carriedAnimals list. -
Added wrong husbandry type warning: a blinking message is shown when a player
carrying an animal walks into a husbandry that does not accept that animal type. -
Reduced maximum pickup age from 6 months to 3 months for cows, pigs, and sheep
in both Animal.lua (RL) and AnimalCluster.lua (vanilla). -
Added pickup rejection warnings: when a player approaches an animal that cannot
be picked up, a blinking warning explains why:- "This animal is too heavy to pick up" (over 100kg, RL weight check)
- "This animal is too old to pick up" (3 months or older)
Works for both herded animals and animals in pens. Rate-limited to once per 5s.
-
Added all new translation keys (EN/DE/FR): ahl_returnAnimal,
ahl_wrongHusbandryType, ahl_tooOldToPickup, ahl_tooHeavyToPickup -
Added trailer unload capacity check: unloadTrailerIntoHusbandry now verifies
the husbandry has enough free slots before transferring. Shows "This husbandry
is full" or "Not enough space in this husbandry for all animals in the trailer".
All-or-nothing transfer prevents partial unloads. -
Added husbandry full warning when carrying an animal into a matching husbandry
that has no free slots. -
Set chicken pickup age limit to 60 months (effectively all chickens).
-
Fixed game crash when picking up chickens: applySleepPose now skips chickens
entirely (different node structure caused a hard engine crash with no log entry).
Also added nil/child-count guards for getChildAt calls to prevent crashes on
unexpected node structures. -
Chickens can now be picked up directly from pens without herding. Added
proximity-based detection using getAnimalPosition since chickens lack collision
proxy nodes and can't be found by the targeter. Works with both RL (nested
animalIdToCluster) and vanilla (flat animalIdToCluster) structures. -
Fixed getNumOfAnimals error: replaced nonexistent global function call with
direct iteration over clusterHusbandry.animalIdToCluster. -
Disabled animal pickup while herding mode is active to prevent conflicts
between herding and pickup interactions. -
Added translation keys (EN/DE/FR): ahl_husbandryFull, ahl_notEnoughSpace
v0.0.1-Dev
-First Comit and Test of FS25_AnimalHerdingLite