diff options
Diffstat (limited to 'src/LivingVillage.Desktop')
| -rw-r--r-- | src/LivingVillage.Desktop/VillageArt.fs | 54 |
1 files changed, 46 insertions, 8 deletions
diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs index 489da23..11a6d71 100644 --- a/src/LivingVillage.Desktop/VillageArt.fs +++ b/src/LivingVillage.Desktop/VillageArt.fs @@ -79,6 +79,19 @@ module VillageArt = | GreyCap | StrawHat + /// Presentation-only occupational archetype. Kernel NPCs carry no occupation, so the + /// visible trade is a pure function of NpcId; three walking rhythms coexist on screen. + type NpcTrade = + | Farmer + | Peddler + | Scholar + + /// Per-trade cadence in simulation ticks plus a phase offset, all deterministic. + type WalkRhythm = + { WalkWindow: int64 + IdleWindow: int64 + PhaseOffset: int64 } + type FacingDirection = | NorthFacing | SouthFacing @@ -291,23 +304,48 @@ module VillageArt = let remainder = value % divisor if remainder < 0L then remainder + divisor else remainder - /// Four-pose walk cycle over a 16-tick window (one pose per 4 sim ticks). - let animationFrame (tick: int64) : AnimationFrame = - match positiveModulo64 4L (tick / 4L) with + /// Trade cycle: NPCs repeat Farmer -> Peddler -> Scholar by id, so all three rhythms + /// are present without touching kernel state. + let npcTrade (NpcId npcId) : NpcTrade = + match positiveModulo 3 npcId with + | 0 -> Farmer + | 1 -> Peddler + | _ -> Scholar + + /// Farmer ambles at the original cadence; Peddler shuffles faster; Scholar strolls slower. + let rhythmFor (trade: NpcTrade) : WalkRhythm = + match trade with + | Farmer -> { WalkWindow = 4L; IdleWindow = 24L; PhaseOffset = 0L } + | Peddler -> { WalkWindow = 3L; IdleWindow = 20L; PhaseOffset = 1L } + | Scholar -> { WalkWindow = 6L; IdleWindow = 36L; PhaseOffset = 2L } + + /// Four-pose walk cycle at a rhythm's window (one pose per WalkWindow sim ticks). + let animationFrameFor (rhythm: WalkRhythm) (tick: int64) : AnimationFrame = + match positiveModulo64 4L (tick / rhythm.WalkWindow + rhythm.PhaseOffset) with | 0L -> FrameOne | 1L -> FrameTwo | 2L -> FrameThree | _ -> FrameFour - /// Two-pose standing breath cycle over a slower 48-tick window, fully deterministic. - let idleFrame (tick: int64) : IdleFrame = - match positiveModulo64 2L (tick / 24L) with + /// Two-pose standing breath cycle at a rhythm's IdleWindow, fully deterministic. + let idleFrameFor (rhythm: WalkRhythm) (tick: int64) : IdleFrame = + match positiveModulo64 2L (tick / rhythm.IdleWindow + rhythm.PhaseOffset) with | 0L -> IdleOne | _ -> IdleTwo /// Picks the walk cycle while moving and the breath cycle while standing still. + let characterFrameFor (rhythm: WalkRhythm) (isMoving: bool) (tick: int64) : CharacterFrame = + if isMoving then WalkFrame(animationFrameFor rhythm tick) else IdleFrame(idleFrameFor rhythm tick) + + /// Farmer cadence is the base cadence (window 4/24, phase 0), preserving older semantics. + let animationFrame (tick: int64) : AnimationFrame = + animationFrameFor (rhythmFor Farmer) tick + + let idleFrame (tick: int64) : IdleFrame = + idleFrameFor (rhythmFor Farmer) tick + let characterFrame (isMoving: bool) (tick: int64) : CharacterFrame = - if isMoving then WalkFrame(animationFrame tick) else IdleFrame(idleFrame tick) + characterFrameFor (rhythmFor Farmer) isMoving tick /// Water surface ripple phase: three frames, one per 32 simulation ticks, fully deterministic. let waterFrameTick (tick: int64) : int = @@ -342,7 +380,7 @@ module VillageArt = let npcSpriteSpec (npcId: NpcId) (direction: FacingDirection) (isMoving: bool) (tick: int64) : NpcSpriteSpec = { Variant = npcVisualVariant npcId Direction = direction - Frame = characterFrame isMoving tick } + Frame = characterFrameFor (rhythmFor (npcTrade npcId)) isMoving tick } /// Villagers breathe while standing at their target and walk while approaching it. let npcSpriteSpecAtTarget (npcId: NpcId) (position: Vec2) (target: Vec2) (tick: int64) : NpcSpriteSpec = |
