@softov/ahpc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +296 -0
  3. package/dist/src/ahp/channels.d.ts +146 -0
  4. package/dist/src/ahp/channels.js +423 -0
  5. package/dist/src/ahp/connection.d.ts +656 -0
  6. package/dist/src/ahp/connection.js +1 -0
  7. package/dist/src/ahp/fake.d.ts +69 -0
  8. package/dist/src/ahp/fake.js +1996 -0
  9. package/dist/src/ahp/live.d.ts +160 -0
  10. package/dist/src/ahp/live.js +2641 -0
  11. package/dist/src/ahp/operate.d.ts +36 -0
  12. package/dist/src/ahp/operate.js +56 -0
  13. package/dist/src/ahp/publish.d.ts +39 -0
  14. package/dist/src/ahp/publish.js +370 -0
  15. package/dist/src/ahp/status.d.ts +23 -0
  16. package/dist/src/ahp/status.js +23 -0
  17. package/dist/src/ahp/types.d.ts +745 -0
  18. package/dist/src/ahp/types.js +27 -0
  19. package/dist/src/app.d.ts +54 -0
  20. package/dist/src/app.js +369 -0
  21. package/dist/src/blocks.d.ts +88 -0
  22. package/dist/src/blocks.js +102 -0
  23. package/dist/src/cli/main.d.ts +13 -0
  24. package/dist/src/cli/main.js +1537 -0
  25. package/dist/src/cli/render.d.ts +43 -0
  26. package/dist/src/cli/render.js +91 -0
  27. package/dist/src/config.d.ts +63 -0
  28. package/dist/src/config.js +45 -0
  29. package/dist/src/connect.d.ts +53 -0
  30. package/dist/src/connect.js +69 -0
  31. package/dist/src/control.d.ts +189 -0
  32. package/dist/src/control.js +2241 -0
  33. package/dist/src/diff.d.ts +44 -0
  34. package/dist/src/diff.js +111 -0
  35. package/dist/src/flags.d.ts +28 -0
  36. package/dist/src/flags.js +76 -0
  37. package/dist/src/main.d.ts +10 -0
  38. package/dist/src/main.js +54 -0
  39. package/dist/src/schedule.d.ts +46 -0
  40. package/dist/src/schedule.js +140 -0
  41. package/dist/src/screens.d.ts +79 -0
  42. package/dist/src/screens.js +1322 -0
  43. package/dist/src/state.d.ts +355 -0
  44. package/dist/src/state.js +531 -0
  45. package/dist/src/terminal.d.ts +44 -0
  46. package/dist/src/terminal.js +101 -0
  47. package/dist/src/tui.d.ts +92 -0
  48. package/dist/src/tui.js +372 -0
  49. package/dist/src/view/automations.d.ts +31 -0
  50. package/dist/src/view/automations.js +76 -0
  51. package/dist/src/view/bood/art.d.ts +90 -0
  52. package/dist/src/view/bood/art.js +152 -0
  53. package/dist/src/view/bood/beetle.d.ts +7 -0
  54. package/dist/src/view/bood/beetle.js +123 -0
  55. package/dist/src/view/bood/bunny.d.ts +8 -0
  56. package/dist/src/view/bood/bunny.js +228 -0
  57. package/dist/src/view/bood/cat.d.ts +9 -0
  58. package/dist/src/view/bood/cat.js +358 -0
  59. package/dist/src/view/bood/crab.d.ts +7 -0
  60. package/dist/src/view/bood/crab.js +121 -0
  61. package/dist/src/view/bood/index.d.ts +29 -0
  62. package/dist/src/view/bood/index.js +25 -0
  63. package/dist/src/view/bood/motion.d.ts +178 -0
  64. package/dist/src/view/bood/motion.js +298 -0
  65. package/dist/src/view/bood/owl.d.ts +9 -0
  66. package/dist/src/view/bood/owl.js +285 -0
  67. package/dist/src/view/bood/registry.d.ts +36 -0
  68. package/dist/src/view/bood/registry.js +218 -0
  69. package/dist/src/view/bood/render.d.ts +31 -0
  70. package/dist/src/view/bood/render.js +196 -0
  71. package/dist/src/view/bood/sprite.d.ts +18 -0
  72. package/dist/src/view/bood/sprite.js +130 -0
  73. package/dist/src/view/bood/sprout.d.ts +8 -0
  74. package/dist/src/view/bood/sprout.js +121 -0
  75. package/dist/src/view/bood/types.d.ts +158 -0
  76. package/dist/src/view/bood/types.js +57 -0
  77. package/dist/src/view/bubble.d.ts +75 -0
  78. package/dist/src/view/bubble.js +86 -0
  79. package/dist/src/view/changes.d.ts +35 -0
  80. package/dist/src/view/changes.js +22 -0
  81. package/dist/src/view/composer.d.ts +64 -0
  82. package/dist/src/view/composer.js +192 -0
  83. package/dist/src/view/controls.d.ts +44 -0
  84. package/dist/src/view/controls.js +49 -0
  85. package/dist/src/view/creature.d.ts +69 -0
  86. package/dist/src/view/creature.js +83 -0
  87. package/dist/src/view/customizations.d.ts +28 -0
  88. package/dist/src/view/customizations.js +53 -0
  89. package/dist/src/view/details.d.ts +65 -0
  90. package/dist/src/view/details.js +65 -0
  91. package/dist/src/view/filediff.d.ts +29 -0
  92. package/dist/src/view/filediff.js +24 -0
  93. package/dist/src/view/files.d.ts +31 -0
  94. package/dist/src/view/files.js +39 -0
  95. package/dist/src/view/hitl.d.ts +43 -0
  96. package/dist/src/view/hitl.js +171 -0
  97. package/dist/src/view/icons.d.ts +13 -0
  98. package/dist/src/view/icons.js +71 -0
  99. package/dist/src/view/picker.d.ts +42 -0
  100. package/dist/src/view/picker.js +71 -0
  101. package/dist/src/view/sessionhead.d.ts +41 -0
  102. package/dist/src/view/sessionhead.js +60 -0
  103. package/dist/src/view/sessions.d.ts +34 -0
  104. package/dist/src/view/sessions.js +61 -0
  105. package/dist/src/view/terminal.d.ts +30 -0
  106. package/dist/src/view/terminal.js +32 -0
  107. package/dist/src/view/toolcall.d.ts +27 -0
  108. package/dist/src/view/toolcall.js +48 -0
  109. package/dist/src/view/transcript.d.ts +50 -0
  110. package/dist/src/view/transcript.js +60 -0
  111. package/package.json +72 -0
@@ -0,0 +1,178 @@
1
+ import type { Facing, Mood, Motion, Pose } from './types.js';
2
+ /**
3
+ * The moving part, in numbers.
4
+ *
5
+ * No JSX and no TextUI import, on purpose. Everything here is arithmetic over
6
+ * a plain object, which means it can be stepped ten thousand times in a test
7
+ * with nothing drawn - and three of the bugs this shipped with were only ever
8
+ * going to be found that way. A creature that walks into the right-hand wall
9
+ * and falls asleep there looks fine for the first four seconds somebody
10
+ * watches it.
11
+ *
12
+ * The layering is one direction and does not bend: mood decides intent, intent
13
+ * and the world decide the physics, and the pose is a pure read of the physics
14
+ * afterwards. Nothing downstream reaches back.
15
+ */
16
+ /** Cells and seconds, everywhere. The terminal's cell is the unit of length. */
17
+ export interface World {
18
+ gravity: number;
19
+ /** How fast a grounded creature reaches the speed it wants. */
20
+ grip: number;
21
+ /** What the air takes off horizontal speed once the ground is gone. */
22
+ air: number;
23
+ /** The row the feet rest on. */
24
+ floor: number;
25
+ left: number;
26
+ right: number;
27
+ }
28
+ export declare const WORLD: World;
29
+ /**
30
+ * A mood is not a behaviour. It is a set of dials on one behaviour loop.
31
+ *
32
+ * `bias` is a weight and not a command, which is the correction worth writing
33
+ * down: "when thinking, walk to the right" coded as `dir = bias` sends the
34
+ * creature to the right-hand wall, where the next target clamps onto its own
35
+ * position, it arrives instantly, rests, and stops moving for good. Almost all
36
+ * of `thinking` was spent asleep against a wall.
37
+ *
38
+ * `sleep` is per mood for the same class of reason: one threshold meant `sad`,
39
+ * which dwells for six seconds at a time and barely travels, was asleep two
40
+ * thirds of the time and its drawing almost never showed.
41
+ */
42
+ export interface Gait {
43
+ /** Odds a decision is a trip rather than a rest. */
44
+ wander: number;
45
+ speed: number;
46
+ dwell: [number, number];
47
+ /** Odds a second of walking turns into a jump. */
48
+ jump: number;
49
+ sit: number;
50
+ /** How far a trip goes, as a fraction of the field. */
51
+ reach: number;
52
+ bias: -1 | 0 | 1;
53
+ biasWeight: number;
54
+ /** Odds a flier takes off for a trip it could have walked. */
55
+ fly: number;
56
+ /** Seconds of stillness before it sleeps. Infinity never does. */
57
+ sleep: number;
58
+ /** Wall to wall, rather than a wander. */
59
+ patrol?: boolean;
60
+ /** Violent and going nowhere: it shakes on the spot instead of resting. */
61
+ jitter?: boolean;
62
+ /** Rooted. Wander is zero, so intent never leaves rest. */
63
+ freeze?: boolean;
64
+ }
65
+ export declare const GAITS: Record<Mood, Gait>;
66
+ /** What the brain wants. The only thing it is allowed to set. */
67
+ export interface Intent {
68
+ kind: 'rest' | 'goto';
69
+ x: number;
70
+ until: number;
71
+ }
72
+ export interface Body {
73
+ x: number;
74
+ y: number;
75
+ vx: number;
76
+ vy: number;
77
+ facing: Facing;
78
+ grounded: boolean;
79
+ held: boolean;
80
+ sitting: boolean;
81
+ /** Gravity is suspended this tick. Only ever true for a flier with a trip on. */
82
+ flying: boolean;
83
+ /** Decided once per trip, at intent time - never per tick, or it flickers. */
84
+ wantsFlight: boolean;
85
+ intent: Intent;
86
+ /** Seconds left of the landing squash. */
87
+ landFor: number;
88
+ /** Seconds it has been standing still, which is what sleep reads. */
89
+ stillFor: number;
90
+ /** Cells travelled, which is what the walk cycle reads. */
91
+ dist: number;
92
+ hopCool: number;
93
+ /**
94
+ * Airborne because it took a stride, rather than because it is falling.
95
+ *
96
+ * Only a flier reads this, and only to tell its two ways of being off the
97
+ * ground apart: a bird that hops along the floor is walking, and drawing it
98
+ * with its wings spread reads as an owl that has fallen off something.
99
+ */
100
+ hopping: boolean;
101
+ frontIn: number;
102
+ /** Seconds since the body was made, so nothing here needs a wall clock. */
103
+ now: number;
104
+ }
105
+ export declare function createBody(x?: number, y?: number): Body;
106
+ /**
107
+ * A gesture in progress: where the pointer took hold, and when it last said so.
108
+ *
109
+ * `last` is on the body's own clock rather than a wall clock, which is what
110
+ * makes a lost release recoverable. A terminal reports a button going down and
111
+ * coming back up, and it reports neither once the pointer has left the window
112
+ * - so a release outside the terminal is a release nothing is ever told about,
113
+ * and the runtime hands the next press to the hit test rather than to whoever
114
+ * was holding the pointer. Without a timeout, a creature dragged into a corner
115
+ * and let go off-screen is held there for the rest of the session, and no
116
+ * amount of clicking elsewhere reaches the handler that could put it down.
117
+ */
118
+ export interface Grip {
119
+ dx: number;
120
+ dy: number;
121
+ samples: {
122
+ at: number;
123
+ x: number;
124
+ y: number;
125
+ }[];
126
+ last: number;
127
+ }
128
+ /** Seconds a hand can hold a creature still before it wriggles out. */
129
+ export declare const GRIP = 2.5;
130
+ /** Take hold, at the offset the pointer took hold at. */
131
+ export declare function grab(body: Body, x: number, y: number): Grip;
132
+ /** Carry it, keeping the last few positions so a release can read the hand's speed. */
133
+ export declare function carry(body: Body, grip: Grip, x: number, y: number, world: World, at?: number): void;
134
+ /**
135
+ * Let go.
136
+ *
137
+ * With a grip it inherits the hand's speed, so letting go while moving is a
138
+ * throw and letting go still is a drop. Without one - a grip that timed out,
139
+ * or a pointer found moving with no button down - it is only ever a drop: the
140
+ * samples are from before whatever happened off-screen and reading them would
141
+ * fling the creature along a gesture that ended some seconds ago.
142
+ */
143
+ export declare function release(body: Body, grip?: Grip | null): void;
144
+ /** Nothing has touched this grip for long enough that the gesture is over. */
145
+ export declare function slipped(body: Body, grip: Grip): boolean;
146
+ /** What just happened to it, for a caller that wants to say so. */
147
+ export type Event = 'landed' | 'bounced' | 'hard' | 'wall' | 'took off';
148
+ /**
149
+ * Pick something to do. The brain's whole vocabulary is rest and goto.
150
+ *
151
+ * A target that clamps to within a stride of where the creature already is is
152
+ * not a trip - it turns round rather than arriving instantly, because the
153
+ * arrival is what resets the dwell and starts the walk to sleep.
154
+ */
155
+ export declare function decide(body: Body, motion: Motion, mood: Mood, world: World, random?: () => number): void;
156
+ /**
157
+ * One tick. Fixed `dt`, because a physics step that takes whatever the render
158
+ * loop gave it is a physics step that behaves differently on a busy machine.
159
+ */
160
+ export declare function stepBody(body: Body, motion: Motion, mood: Mood, world: World, dt: number, random?: () => number): Event | undefined;
161
+ /**
162
+ * A pure read of the physics, and the order is a priority order.
163
+ *
164
+ * Held beats airborne beats having just landed beats wanting to walk. Every
165
+ * one of those is a question somebody would otherwise answer with a boolean
166
+ * kept somewhere else, and get wrong the first time two of them were true.
167
+ */
168
+ export declare function poseOf(body: Body, motion: Motion, mood: Mood): Pose;
169
+ /**
170
+ * Which frame of the cycle, which is not one clock but three.
171
+ *
172
+ * Feet advance with distance, because a walk cycle on a timer moonwalks the
173
+ * moment the creature slows down. A flap is on the clock, because deriving it
174
+ * from `vy` pins a hovering owl to one wing position. Everything else idles.
175
+ */
176
+ export declare function frameOf(pose: Pose, body: Body, count: number): number;
177
+ /** A restful pose blinks, whatever mood it is in. */
178
+ export declare function faceOf(pose: Pose, body: Body, motion: Motion, mood: Mood): string;
@@ -0,0 +1,298 @@
1
+ export const WORLD = { gravity: 90, grip: 8, air: 0.4, floor: 0, left: 0, right: 0 };
2
+ const WALK_BASE = 7; /** cells a second at 1x */
3
+ const HOP_IMPULSE = 26; /** cells a second, upward */
4
+ const TAKEOFF = 17;
5
+ const STRIDE = 2.2; /** cells per walk frame */
6
+ const HARD_LANDING = 26;
7
+ const FACE_FRONT_AFTER = 1.9;
8
+ const FLY_FAR = 14; /** far enough that flying is worth the take-off */
9
+ const WALKING = 0.6; /** below this it is standing, whatever vx says */
10
+ export const GAITS = {
11
+ happy: { wander: 0.5, speed: 0.75, dwell: [1.2, 3], jump: 0.15, sit: 0.35, reach: 0.3, bias: 0, biasWeight: 0.75, fly: 0.35, sleep: 30 },
12
+ thinking: { wander: 0.8, speed: 0.45, dwell: [0.9, 1.8], jump: 0.02, sit: 0.1, reach: 0.22, bias: 1, biasWeight: 0.75, fly: 0.25, sleep: 45 },
13
+ executing: { wander: 1, speed: 1.4, dwell: [0.2, 0.6], jump: 0.28, sit: 0, reach: 0.9, bias: 0, biasWeight: 0.75, fly: 0.55, sleep: Infinity, patrol: true },
14
+ sad: { wander: 0.06, speed: 0.3, dwell: [3, 6], jump: 0, sit: 0.9, reach: 0.1, bias: 0, biasWeight: 0.75, fly: 0, sleep: 90 },
15
+ error: { wander: 0, speed: 0, dwell: [2, 4], jump: 0, sit: 0, reach: 0, bias: 0, biasWeight: 0.75, fly: 0, sleep: Infinity, freeze: true },
16
+ };
17
+ export function createBody(x = 0, y = 0) {
18
+ return {
19
+ x, y, vx: 0, vy: 0, facing: 'front',
20
+ grounded: true, held: false, sitting: false, flying: false, wantsFlight: false,
21
+ intent: { kind: 'rest', x, until: 0 },
22
+ landFor: 0, stillFor: 0, dist: 0, hopCool: 0, hopping: false, frontIn: 0, now: 0,
23
+ };
24
+ }
25
+ /** Seconds a hand can hold a creature still before it wriggles out. */
26
+ export const GRIP = 2.5;
27
+ /** Take hold, at the offset the pointer took hold at. */
28
+ export function grab(body, x, y) {
29
+ body.held = true;
30
+ body.vx = 0;
31
+ body.vy = 0;
32
+ body.stillFor = 0;
33
+ body.wantsFlight = false;
34
+ return { dx: x - body.x, dy: y - body.y, samples: [], last: body.now };
35
+ }
36
+ /** Carry it, keeping the last few positions so a release can read the hand's speed. */
37
+ export function carry(body, grip, x, y, world, at) {
38
+ body.x = clamp(x - grip.dx, world.left, world.right);
39
+ body.y = clamp(y - grip.dy, 0, world.floor);
40
+ grip.last = body.now;
41
+ if (at !== undefined)
42
+ grip.samples.push({ at, x: body.x, y: body.y });
43
+ while (grip.samples.length > 5)
44
+ grip.samples.shift();
45
+ }
46
+ /**
47
+ * Let go.
48
+ *
49
+ * With a grip it inherits the hand's speed, so letting go while moving is a
50
+ * throw and letting go still is a drop. Without one - a grip that timed out,
51
+ * or a pointer found moving with no button down - it is only ever a drop: the
52
+ * samples are from before whatever happened off-screen and reading them would
53
+ * fling the creature along a gesture that ended some seconds ago.
54
+ */
55
+ export function release(body, grip) {
56
+ const samples = grip?.samples ?? [];
57
+ const first = samples[0];
58
+ const last = samples[samples.length - 1];
59
+ if (first && last && last.at > first.at) {
60
+ const seconds = (last.at - first.at) / 1000;
61
+ body.vx = clamp((last.x - first.x) / seconds, -45, 45);
62
+ body.vy = clamp((last.y - first.y) / seconds, -45, 45);
63
+ }
64
+ body.held = false;
65
+ body.grounded = false;
66
+ }
67
+ /** Nothing has touched this grip for long enough that the gesture is over. */
68
+ export function slipped(body, grip) {
69
+ return body.now - grip.last > GRIP;
70
+ }
71
+ const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
72
+ const sign = (v) => (v < 0 ? -1 : v > 0 ? 1 : 0);
73
+ /**
74
+ * Pick something to do. The brain's whole vocabulary is rest and goto.
75
+ *
76
+ * A target that clamps to within a stride of where the creature already is is
77
+ * not a trip - it turns round rather than arriving instantly, because the
78
+ * arrival is what resets the dwell and starts the walk to sleep.
79
+ */
80
+ export function decide(body, motion, mood, world, random = Math.random) {
81
+ const gait = GAITS[mood];
82
+ const span = world.right - world.left;
83
+ if (gait.freeze) {
84
+ body.intent = { kind: 'rest', x: body.x, until: body.now + 1.5 };
85
+ body.sitting = false;
86
+ return;
87
+ }
88
+ const rest = () => {
89
+ body.intent = { kind: 'rest', x: body.x, until: body.now + gait.dwell[0] + random() * (gait.dwell[1] - gait.dwell[0]) };
90
+ body.sitting = motion.sits && random() < gait.sit;
91
+ };
92
+ if (random() > gait.wander) {
93
+ rest();
94
+ return;
95
+ }
96
+ let target;
97
+ if (gait.patrol) {
98
+ target = body.x < (world.left + world.right) / 2 ? world.right : world.left;
99
+ }
100
+ else {
101
+ const dir = gait.bias ? (random() < gait.biasWeight ? gait.bias : -gait.bias) : (random() < 0.5 ? -1 : 1);
102
+ target = clamp(body.x + dir * (4 + random() * gait.reach * span), world.left, world.right);
103
+ if (Math.abs(target - body.x) < 2.5)
104
+ target = clamp(body.x - dir * (6 + random() * 12), world.left, world.right);
105
+ }
106
+ if (Math.abs(target - body.x) < 2.5) {
107
+ rest();
108
+ return;
109
+ }
110
+ body.wantsFlight = motion.flies && (random() < gait.fly || (Math.abs(target - body.x) > FLY_FAR && random() < 0.7));
111
+ body.intent = { kind: 'goto', x: target, until: body.now + gait.dwell[0] + random() * (gait.dwell[1] - gait.dwell[0]) + 3.5 };
112
+ body.sitting = false;
113
+ body.stillFor = 0;
114
+ }
115
+ /**
116
+ * One tick. Fixed `dt`, because a physics step that takes whatever the render
117
+ * loop gave it is a physics step that behaves differently on a busy machine.
118
+ */
119
+ export function stepBody(body, motion, mood, world, dt, random = Math.random) {
120
+ const gait = GAITS[mood];
121
+ let happened;
122
+ body.now += dt;
123
+ if (body.landFor > 0)
124
+ body.landFor -= dt;
125
+ if (body.hopCool > 0)
126
+ body.hopCool -= dt;
127
+ if (body.now > body.intent.until)
128
+ decide(body, motion, mood, world, random);
129
+ if (body.intent.kind === 'goto' && Math.abs(body.intent.x - body.x) < 1.4) {
130
+ body.intent = { kind: 'rest', x: body.x, until: body.now + gait.dwell[0] };
131
+ body.sitting = motion.sits && random() < gait.sit;
132
+ body.wantsFlight = false;
133
+ }
134
+ if (body.held) {
135
+ body.grounded = false;
136
+ body.flying = false;
137
+ body.hopping = false;
138
+ body.stillFor = 0;
139
+ return undefined;
140
+ }
141
+ let want = 0;
142
+ if (body.intent.kind === 'goto')
143
+ want = sign(body.intent.x - body.x) * WALK_BASE * gait.speed;
144
+ if (gait.jitter && body.intent.kind === 'rest')
145
+ want = Math.sin(body.now * 14) * 2.5;
146
+ // The only place a species decides to leave the ground for something other
147
+ // than a stride. Flight is not a pose, it is a suspended gravity term.
148
+ if (motion.flies && body.wantsFlight && body.intent.kind === 'goto' && body.grounded) {
149
+ body.vy = -TAKEOFF;
150
+ body.grounded = false;
151
+ body.hopping = false;
152
+ happened = 'took off';
153
+ }
154
+ body.flying = motion.flies && body.wantsFlight && !body.grounded && body.intent.kind === 'goto' && !gait.freeze;
155
+ if (body.flying) {
156
+ const hover = world.floor - 5;
157
+ body.vy += (hover - body.y) * 7 * dt;
158
+ body.vy -= body.vy * 5 * dt;
159
+ body.vx += (want - body.vx) * 4 * dt;
160
+ }
161
+ else if (!body.grounded) {
162
+ body.vy += world.gravity * motion.fallSpeed * dt;
163
+ if (motion.flies && body.vy > 5)
164
+ body.vy = 5; // wings out, gliding
165
+ body.vx -= body.vx * world.air * dt;
166
+ }
167
+ else if (motion.gait === 'hop' && Math.abs(want) > 0.5) {
168
+ if (body.hopCool <= 0) {
169
+ body.vy = -HOP_IMPULSE * motion.hop * (0.6 + 0.4 * gait.speed);
170
+ body.vx = want;
171
+ body.grounded = false;
172
+ body.hopping = true;
173
+ body.hopCool = 0.12;
174
+ }
175
+ }
176
+ else {
177
+ body.vx += (want - body.vx) * world.grip * dt;
178
+ if (Math.abs(want) < 0.1)
179
+ body.vx -= body.vx * world.grip * dt;
180
+ if (random() < gait.jump * dt) {
181
+ body.vy = -HOP_IMPULSE * 0.7;
182
+ body.grounded = false;
183
+ }
184
+ }
185
+ body.x += body.vx * dt;
186
+ body.y += body.vy * dt;
187
+ body.dist += Math.abs(body.vx) * dt;
188
+ // Latched, not read off vx. A bunny is airborne for almost all of the time
189
+ // it is travelling, so the instant velocity is a fact about a body in flight
190
+ // rather than about which way the animal is pointed.
191
+ if (body.vx > 0.8) {
192
+ body.facing = 'right';
193
+ body.frontIn = FACE_FRONT_AFTER;
194
+ }
195
+ else if (body.vx < -0.8) {
196
+ body.facing = 'left';
197
+ body.frontIn = FACE_FRONT_AFTER;
198
+ }
199
+ else if (body.grounded) {
200
+ body.frontIn -= dt;
201
+ if (body.frontIn <= 0)
202
+ body.facing = 'front';
203
+ }
204
+ if (body.x < world.left) {
205
+ body.x = world.left;
206
+ body.vx = Math.abs(body.vx) * 0.4;
207
+ decide(body, motion, mood, world, random);
208
+ happened ??= 'wall';
209
+ }
210
+ if (body.x > world.right) {
211
+ body.x = world.right;
212
+ body.vx = -Math.abs(body.vx) * 0.4;
213
+ decide(body, motion, mood, world, random);
214
+ happened ??= 'wall';
215
+ }
216
+ if (body.y < 0) {
217
+ body.y = 0;
218
+ body.vy = Math.max(0, body.vy);
219
+ }
220
+ if (body.y >= world.floor) {
221
+ const impact = body.vy;
222
+ body.y = world.floor;
223
+ if (!body.grounded) {
224
+ body.landFor = 0.18;
225
+ happened = 'landed';
226
+ if (impact > HARD_LANDING) {
227
+ happened = 'hard';
228
+ if (motion.bounce > 0) {
229
+ body.vy = -impact * motion.bounce;
230
+ body.grounded = false;
231
+ return 'bounced';
232
+ }
233
+ }
234
+ }
235
+ body.grounded = true;
236
+ body.hopping = false;
237
+ body.vy = 0;
238
+ }
239
+ else if (body.grounded && body.vy !== 0)
240
+ body.grounded = false;
241
+ if (Math.abs(body.vx) < 0.4 && body.grounded)
242
+ body.stillFor += dt;
243
+ else
244
+ body.stillFor = 0;
245
+ return happened;
246
+ }
247
+ /**
248
+ * A pure read of the physics, and the order is a priority order.
249
+ *
250
+ * Held beats airborne beats having just landed beats wanting to walk. Every
251
+ * one of those is a question somebody would otherwise answer with a boolean
252
+ * kept somewhere else, and get wrong the first time two of them were true.
253
+ */
254
+ export function poseOf(body, motion, mood) {
255
+ if (body.held)
256
+ return 'held';
257
+ if (!body.grounded) {
258
+ if (body.flying)
259
+ return 'fly';
260
+ if (motion.flies)
261
+ return body.hopping ? 'walk' : 'fall'; // hop, or the glide
262
+ return body.vy < 0 ? 'jump' : 'fall';
263
+ }
264
+ if (body.landFor > 0)
265
+ return 'land';
266
+ if (Math.abs(body.vx) > WALKING)
267
+ return 'walk';
268
+ if (body.stillFor > GAITS[mood].sleep)
269
+ return 'sleep';
270
+ if (mood === 'error')
271
+ return 'alarm';
272
+ if (body.sitting)
273
+ return 'sit';
274
+ return 'idle';
275
+ }
276
+ /**
277
+ * Which frame of the cycle, which is not one clock but three.
278
+ *
279
+ * Feet advance with distance, because a walk cycle on a timer moonwalks the
280
+ * moment the creature slows down. A flap is on the clock, because deriving it
281
+ * from `vy` pins a hovering owl to one wing position. Everything else idles.
282
+ */
283
+ export function frameOf(pose, body, count) {
284
+ if (count <= 1)
285
+ return 0;
286
+ if (pose === 'walk')
287
+ return Math.floor(body.dist / STRIDE) % count;
288
+ if (pose === 'fly')
289
+ return Math.floor(body.now * 6) % count;
290
+ return Math.floor(body.now * 2.2) % count;
291
+ }
292
+ /** A restful pose blinks, whatever mood it is in. */
293
+ export function faceOf(pose, body, motion, mood) {
294
+ const restful = pose === 'idle' || pose === 'sit';
295
+ if (restful && Math.floor(body.now * 2.5) % 5 === 4)
296
+ return motion.blink;
297
+ return motion.faces[mood];
298
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * An owl. A comma for a beak, which is the only reason the face fits in three
3
+ * cells at every size.
4
+ *
5
+ * Angry is the one figure here that changes shape rather than expression - it
6
+ * opens its wings - and it is worth the extra rows, because an owl that is
7
+ * only scowling reads as an owl that is concentrating.
8
+ */
9
+ export declare const owl: import("./types.js").CreatureSpec;