@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.
- package/LICENSE +21 -0
- package/README.md +296 -0
- package/dist/src/ahp/channels.d.ts +146 -0
- package/dist/src/ahp/channels.js +423 -0
- package/dist/src/ahp/connection.d.ts +656 -0
- package/dist/src/ahp/connection.js +1 -0
- package/dist/src/ahp/fake.d.ts +69 -0
- package/dist/src/ahp/fake.js +1996 -0
- package/dist/src/ahp/live.d.ts +160 -0
- package/dist/src/ahp/live.js +2641 -0
- package/dist/src/ahp/operate.d.ts +36 -0
- package/dist/src/ahp/operate.js +56 -0
- package/dist/src/ahp/publish.d.ts +39 -0
- package/dist/src/ahp/publish.js +370 -0
- package/dist/src/ahp/status.d.ts +23 -0
- package/dist/src/ahp/status.js +23 -0
- package/dist/src/ahp/types.d.ts +745 -0
- package/dist/src/ahp/types.js +27 -0
- package/dist/src/app.d.ts +54 -0
- package/dist/src/app.js +369 -0
- package/dist/src/blocks.d.ts +88 -0
- package/dist/src/blocks.js +102 -0
- package/dist/src/cli/main.d.ts +13 -0
- package/dist/src/cli/main.js +1537 -0
- package/dist/src/cli/render.d.ts +43 -0
- package/dist/src/cli/render.js +91 -0
- package/dist/src/config.d.ts +63 -0
- package/dist/src/config.js +45 -0
- package/dist/src/connect.d.ts +53 -0
- package/dist/src/connect.js +69 -0
- package/dist/src/control.d.ts +189 -0
- package/dist/src/control.js +2241 -0
- package/dist/src/diff.d.ts +44 -0
- package/dist/src/diff.js +111 -0
- package/dist/src/flags.d.ts +28 -0
- package/dist/src/flags.js +76 -0
- package/dist/src/main.d.ts +10 -0
- package/dist/src/main.js +54 -0
- package/dist/src/schedule.d.ts +46 -0
- package/dist/src/schedule.js +140 -0
- package/dist/src/screens.d.ts +79 -0
- package/dist/src/screens.js +1322 -0
- package/dist/src/state.d.ts +355 -0
- package/dist/src/state.js +531 -0
- package/dist/src/terminal.d.ts +44 -0
- package/dist/src/terminal.js +101 -0
- package/dist/src/tui.d.ts +92 -0
- package/dist/src/tui.js +372 -0
- package/dist/src/view/automations.d.ts +31 -0
- package/dist/src/view/automations.js +76 -0
- package/dist/src/view/bood/art.d.ts +90 -0
- package/dist/src/view/bood/art.js +152 -0
- package/dist/src/view/bood/beetle.d.ts +7 -0
- package/dist/src/view/bood/beetle.js +123 -0
- package/dist/src/view/bood/bunny.d.ts +8 -0
- package/dist/src/view/bood/bunny.js +228 -0
- package/dist/src/view/bood/cat.d.ts +9 -0
- package/dist/src/view/bood/cat.js +358 -0
- package/dist/src/view/bood/crab.d.ts +7 -0
- package/dist/src/view/bood/crab.js +121 -0
- package/dist/src/view/bood/index.d.ts +29 -0
- package/dist/src/view/bood/index.js +25 -0
- package/dist/src/view/bood/motion.d.ts +178 -0
- package/dist/src/view/bood/motion.js +298 -0
- package/dist/src/view/bood/owl.d.ts +9 -0
- package/dist/src/view/bood/owl.js +285 -0
- package/dist/src/view/bood/registry.d.ts +36 -0
- package/dist/src/view/bood/registry.js +218 -0
- package/dist/src/view/bood/render.d.ts +31 -0
- package/dist/src/view/bood/render.js +196 -0
- package/dist/src/view/bood/sprite.d.ts +18 -0
- package/dist/src/view/bood/sprite.js +130 -0
- package/dist/src/view/bood/sprout.d.ts +8 -0
- package/dist/src/view/bood/sprout.js +121 -0
- package/dist/src/view/bood/types.d.ts +158 -0
- package/dist/src/view/bood/types.js +57 -0
- package/dist/src/view/bubble.d.ts +75 -0
- package/dist/src/view/bubble.js +86 -0
- package/dist/src/view/changes.d.ts +35 -0
- package/dist/src/view/changes.js +22 -0
- package/dist/src/view/composer.d.ts +64 -0
- package/dist/src/view/composer.js +192 -0
- package/dist/src/view/controls.d.ts +44 -0
- package/dist/src/view/controls.js +49 -0
- package/dist/src/view/creature.d.ts +69 -0
- package/dist/src/view/creature.js +83 -0
- package/dist/src/view/customizations.d.ts +28 -0
- package/dist/src/view/customizations.js +53 -0
- package/dist/src/view/details.d.ts +65 -0
- package/dist/src/view/details.js +65 -0
- package/dist/src/view/filediff.d.ts +29 -0
- package/dist/src/view/filediff.js +24 -0
- package/dist/src/view/files.d.ts +31 -0
- package/dist/src/view/files.js +39 -0
- package/dist/src/view/hitl.d.ts +43 -0
- package/dist/src/view/hitl.js +171 -0
- package/dist/src/view/icons.d.ts +13 -0
- package/dist/src/view/icons.js +71 -0
- package/dist/src/view/picker.d.ts +42 -0
- package/dist/src/view/picker.js +71 -0
- package/dist/src/view/sessionhead.d.ts +41 -0
- package/dist/src/view/sessionhead.js +60 -0
- package/dist/src/view/sessions.d.ts +34 -0
- package/dist/src/view/sessions.js +61 -0
- package/dist/src/view/terminal.d.ts +30 -0
- package/dist/src/view/terminal.js +32 -0
- package/dist/src/view/toolcall.d.ts +27 -0
- package/dist/src/view/toolcall.js +48 -0
- package/dist/src/view/transcript.d.ts +50 -0
- package/dist/src/view/transcript.js +60 -0
- package/package.json +72 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { alternate, art, blink } from './art.js';
|
|
2
|
+
import { defineCreature } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* An owl. A comma for a beak, which is the only reason the face fits in three
|
|
5
|
+
* cells at every size.
|
|
6
|
+
*
|
|
7
|
+
* Angry is the one figure here that changes shape rather than expression - it
|
|
8
|
+
* opens its wings - and it is worth the extra rows, because an owl that is
|
|
9
|
+
* only scowling reads as an owl that is concentrating.
|
|
10
|
+
*/
|
|
11
|
+
export const owl = defineCreature({
|
|
12
|
+
name: 'owl',
|
|
13
|
+
label: 'Owl',
|
|
14
|
+
about: 'Has read the logs already',
|
|
15
|
+
draw: {
|
|
16
|
+
happy: [art `
|
|
17
|
+
,___,
|
|
18
|
+
(^,^)
|
|
19
|
+
/)_)
|
|
20
|
+
" "
|
|
21
|
+
`, art `
|
|
22
|
+
,___,
|
|
23
|
+
(-,-)
|
|
24
|
+
/)_)
|
|
25
|
+
" "
|
|
26
|
+
`, art `
|
|
27
|
+
,___,
|
|
28
|
+
(^,^)
|
|
29
|
+
(_(\
|
|
30
|
+
" "
|
|
31
|
+
`, art `
|
|
32
|
+
,___,
|
|
33
|
+
(-,-)
|
|
34
|
+
(_(\
|
|
35
|
+
" "
|
|
36
|
+
`],
|
|
37
|
+
sad: art `
|
|
38
|
+
,___,
|
|
39
|
+
(T,T)
|
|
40
|
+
/)_)
|
|
41
|
+
" "
|
|
42
|
+
`,
|
|
43
|
+
thinking: alternate(art `
|
|
44
|
+
,___, o
|
|
45
|
+
(o,o)
|
|
46
|
+
/)_)
|
|
47
|
+
" "
|
|
48
|
+
`, art `
|
|
49
|
+
,___, .
|
|
50
|
+
(o,o)
|
|
51
|
+
/)_)
|
|
52
|
+
" "
|
|
53
|
+
`),
|
|
54
|
+
executing: [art `
|
|
55
|
+
,___,
|
|
56
|
+
(o,o)
|
|
57
|
+
/)_)
|
|
58
|
+
" "
|
|
59
|
+
`, art `
|
|
60
|
+
,___,
|
|
61
|
+
(o,-)
|
|
62
|
+
/)_)
|
|
63
|
+
" "
|
|
64
|
+
`, art `
|
|
65
|
+
,___,
|
|
66
|
+
(-,o)
|
|
67
|
+
/)_)
|
|
68
|
+
" "
|
|
69
|
+
`],
|
|
70
|
+
error: art `
|
|
71
|
+
,___,
|
|
72
|
+
(>,<)
|
|
73
|
+
/(___)\
|
|
74
|
+
v v
|
|
75
|
+
`,
|
|
76
|
+
},
|
|
77
|
+
block: {
|
|
78
|
+
happy: blink(art `
|
|
79
|
+
,___,
|
|
80
|
+
(^,^)
|
|
81
|
+
)_)
|
|
82
|
+
`, art `
|
|
83
|
+
,___,
|
|
84
|
+
(-,-)
|
|
85
|
+
)_)
|
|
86
|
+
`),
|
|
87
|
+
sad: art `
|
|
88
|
+
,___,
|
|
89
|
+
(T,T)
|
|
90
|
+
)_)
|
|
91
|
+
`,
|
|
92
|
+
thinking: art `
|
|
93
|
+
,___,
|
|
94
|
+
(-,o)
|
|
95
|
+
)_)
|
|
96
|
+
`,
|
|
97
|
+
executing: [art `
|
|
98
|
+
,___,
|
|
99
|
+
(o,o)
|
|
100
|
+
)_)
|
|
101
|
+
`, art `
|
|
102
|
+
,___,
|
|
103
|
+
(o,-)
|
|
104
|
+
)_)
|
|
105
|
+
`, art `
|
|
106
|
+
,___,
|
|
107
|
+
(-,o)
|
|
108
|
+
)_)
|
|
109
|
+
`],
|
|
110
|
+
error: art `
|
|
111
|
+
,___,
|
|
112
|
+
(>,<)
|
|
113
|
+
/(_)\
|
|
114
|
+
`,
|
|
115
|
+
},
|
|
116
|
+
inline: {
|
|
117
|
+
happy: blink(art `,(^,^),`, art `,(-,-),`),
|
|
118
|
+
sad: art `v(T,T)v`,
|
|
119
|
+
thinking: art `,(-,o),`,
|
|
120
|
+
executing: [art `,(o,o),`, art `,(o,-),`, art `,(-,o),`],
|
|
121
|
+
error: art `\(>,<)/`,
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* Moving, and it is the one that gets to choose.
|
|
125
|
+
*
|
|
126
|
+
* Flight is not a pose here, it is a suspended gravity term - and something
|
|
127
|
+
* the creature decides once per trip, at intent time, never per tick. A roll
|
|
128
|
+
* against the mood's `fly` dial, plus a lean toward flying when the
|
|
129
|
+
* destination is far enough to be worth the take-off. Decided per tick it
|
|
130
|
+
* would flicker between walking and flying inside one journey.
|
|
131
|
+
*
|
|
132
|
+
* The flap is three frames on a clock rather than two derived from `vy`,
|
|
133
|
+
* because a hover controller keeps `vy` near zero and would pin a circling
|
|
134
|
+
* owl to one wing position. The glide has no left or right view on purpose:
|
|
135
|
+
* a bird planing is not pointed anywhere in particular, and the lookup chain
|
|
136
|
+
* falls through to the front one.
|
|
137
|
+
*/
|
|
138
|
+
motion: {
|
|
139
|
+
gait: 'hop',
|
|
140
|
+
flies: true,
|
|
141
|
+
sits: true,
|
|
142
|
+
bounce: 0,
|
|
143
|
+
fallSpeed: 0.25,
|
|
144
|
+
hop: 0.45,
|
|
145
|
+
blink: '-,-',
|
|
146
|
+
faces: { happy: '^,^', sad: 'T,T', thinking: '-,o', executing: 'o,o', error: '>,<' },
|
|
147
|
+
tells: { happy: ',___,', sad: 'v___v', thinking: ',___,', executing: ',___,', error: '/___\\' },
|
|
148
|
+
poses: {
|
|
149
|
+
'idle.front': art `
|
|
150
|
+
#####
|
|
151
|
+
(%%%)
|
|
152
|
+
/)_)
|
|
153
|
+
" "
|
|
154
|
+
`,
|
|
155
|
+
'idle.right': art `
|
|
156
|
+
#####
|
|
157
|
+
(%%%)
|
|
158
|
+
/)_)
|
|
159
|
+
" "
|
|
160
|
+
`,
|
|
161
|
+
'idle.left': art `
|
|
162
|
+
#####
|
|
163
|
+
(%%%)
|
|
164
|
+
(_(\
|
|
165
|
+
" "
|
|
166
|
+
`,
|
|
167
|
+
'sit.front': art `
|
|
168
|
+
#####
|
|
169
|
+
(%%%)
|
|
170
|
+
/)_)
|
|
171
|
+
" "
|
|
172
|
+
`,
|
|
173
|
+
'walk.right': art `
|
|
174
|
+
#####
|
|
175
|
+
(%%%)
|
|
176
|
+
/)_)
|
|
177
|
+
^ ^
|
|
178
|
+
`,
|
|
179
|
+
'walk.left': art `
|
|
180
|
+
#####
|
|
181
|
+
(%%%)
|
|
182
|
+
(_(\
|
|
183
|
+
^ ^
|
|
184
|
+
`,
|
|
185
|
+
'fly.front': [art `
|
|
186
|
+
#####
|
|
187
|
+
\(%%%)/
|
|
188
|
+
( )
|
|
189
|
+
" "
|
|
190
|
+
`, art `
|
|
191
|
+
#####
|
|
192
|
+
__(%%%)__
|
|
193
|
+
( )
|
|
194
|
+
" "
|
|
195
|
+
`, art `
|
|
196
|
+
#####
|
|
197
|
+
(%%%)
|
|
198
|
+
/( )\
|
|
199
|
+
" "
|
|
200
|
+
`],
|
|
201
|
+
'fly.left': [art `
|
|
202
|
+
#####
|
|
203
|
+
\%%% )///
|
|
204
|
+
( ---
|
|
205
|
+
" "
|
|
206
|
+
`, art `
|
|
207
|
+
#####
|
|
208
|
+
_%%% )___
|
|
209
|
+
( --
|
|
210
|
+
" "
|
|
211
|
+
`, art `
|
|
212
|
+
#####
|
|
213
|
+
%%% )_
|
|
214
|
+
/( \\
|
|
215
|
+
" "
|
|
216
|
+
`],
|
|
217
|
+
'fly.right': [art `
|
|
218
|
+
#####
|
|
219
|
+
\\\( %%%/
|
|
220
|
+
\-- )
|
|
221
|
+
" "
|
|
222
|
+
`, art `
|
|
223
|
+
#####
|
|
224
|
+
__( %%%_
|
|
225
|
+
-- )
|
|
226
|
+
" "
|
|
227
|
+
`, art `
|
|
228
|
+
#####
|
|
229
|
+
_( %%%
|
|
230
|
+
/// )\
|
|
231
|
+
" "
|
|
232
|
+
`],
|
|
233
|
+
jump: art `
|
|
234
|
+
#####
|
|
235
|
+
\(%%%)/
|
|
236
|
+
)w(
|
|
237
|
+
`,
|
|
238
|
+
// The glide, which is also what a dropped owl does instead of falling.
|
|
239
|
+
fall: art `
|
|
240
|
+
#####
|
|
241
|
+
---(%%%)---
|
|
242
|
+
)w(
|
|
243
|
+
`,
|
|
244
|
+
land: art `
|
|
245
|
+
#####
|
|
246
|
+
(%%%)
|
|
247
|
+
/)_)
|
|
248
|
+
`,
|
|
249
|
+
held: art `
|
|
250
|
+
#####
|
|
251
|
+
(%%%)
|
|
252
|
+
/)_(\
|
|
253
|
+
" "
|
|
254
|
+
`,
|
|
255
|
+
sleep: art `
|
|
256
|
+
,___,
|
|
257
|
+
(-,-)
|
|
258
|
+
/)_)
|
|
259
|
+
" "
|
|
260
|
+
`,
|
|
261
|
+
// The one figure that changes shape rather than expression: an owl that
|
|
262
|
+
// is only scowling reads as an owl that is concentrating.
|
|
263
|
+
alarm: [
|
|
264
|
+
art `
|
|
265
|
+
#####
|
|
266
|
+
\\(%%%)//
|
|
267
|
+
(___)
|
|
268
|
+
"w"
|
|
269
|
+
`,
|
|
270
|
+
art `
|
|
271
|
+
#####
|
|
272
|
+
__(%%%)__
|
|
273
|
+
(___)
|
|
274
|
+
"w"
|
|
275
|
+
`,
|
|
276
|
+
art `
|
|
277
|
+
#####
|
|
278
|
+
(%%%)
|
|
279
|
+
//(___)\\
|
|
280
|
+
"w"
|
|
281
|
+
`
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Creature, CreatureSpec, Facing, Form, Mood, Pose } from './types.js';
|
|
2
|
+
export declare function registerCreature(spec: CreatureSpec): Creature;
|
|
3
|
+
export declare function getCreature(name: string): Creature | undefined;
|
|
4
|
+
/** Every one registered, in registration order. */
|
|
5
|
+
export declare function listCreatures(): readonly Creature[];
|
|
6
|
+
export declare function creatureNames(): readonly string[];
|
|
7
|
+
export interface DrawOptions {
|
|
8
|
+
form?: Form;
|
|
9
|
+
/** Which frame of the cycle. Wraps, so a frame counter can be handed straight in. */
|
|
10
|
+
frame?: number;
|
|
11
|
+
}
|
|
12
|
+
/** The rows of one creature, in one mood, at one size. */
|
|
13
|
+
export declare function drawCreature(name: string, mood?: Mood, options?: DrawOptions): string[];
|
|
14
|
+
/** Every frame of one cell. One long, where the drawing does not move. */
|
|
15
|
+
export declare function creatureFrames(name: string, mood?: Mood, form?: Form): string[][];
|
|
16
|
+
/** What a form costs for one creature, whatever mood it is in. */
|
|
17
|
+
export declare function creatureSize(name: string, form?: Form): {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
/** The tallest anyone in the bood is, for a caller deciding whether there is room. */
|
|
22
|
+
export declare function boodHeight(form?: Form): number;
|
|
23
|
+
/**
|
|
24
|
+
* The chain, and the chain is the design rather than the first hit.
|
|
25
|
+
*
|
|
26
|
+
* A species that has no left view of a pose falls through to its front one and
|
|
27
|
+
* still reads right; the owl's glide is deliberately front-only, because a
|
|
28
|
+
* bird planing is not pointed anywhere in particular. Overrides come first at
|
|
29
|
+
* every step, so a mood that changes the outline rather than the expression -
|
|
30
|
+
* the cat at a keyboard while a turn is running - wins before a facing does.
|
|
31
|
+
*/
|
|
32
|
+
export declare function poseFrames(name: string, pose: Pose, facing: Facing, mood: Mood): string[][] | undefined;
|
|
33
|
+
/** The moving half of one creature, or nothing if it was never drawn moving. */
|
|
34
|
+
export declare function creatureMotion(name: string): Creature['motion'];
|
|
35
|
+
/** Which of the bood have been drawn moving, for a picker that offers it. */
|
|
36
|
+
export declare function livelyNames(): readonly string[];
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { anchorOf, framesOf, metric, square } from './art.js';
|
|
2
|
+
import { BOUNDS, FORMS, MOODS } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The bood.
|
|
5
|
+
*
|
|
6
|
+
* Late-binding, by name, the way every other registry in this runtime works:
|
|
7
|
+
* a creature is looked up when it is drawn, not linked when it is imported.
|
|
8
|
+
* Which means a consumer can `registerCreature` their own and it is on the
|
|
9
|
+
* same footing as the six that ship - there is no built-in list to be on.
|
|
10
|
+
*/
|
|
11
|
+
const BOOD = new Map();
|
|
12
|
+
/** What went wrong, and which drawing it was. */
|
|
13
|
+
class BadDrawing extends Error {
|
|
14
|
+
constructor(name, form, mood, said) {
|
|
15
|
+
super(`creature "${name}" ${form}/${mood}: ${said}`);
|
|
16
|
+
this.name = 'BadDrawing';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** What went wrong in the moving half, and which pose it was. */
|
|
20
|
+
class BadMotion extends Error {
|
|
21
|
+
constructor(name, key, said) {
|
|
22
|
+
super(`creature "${name}" motion/${key}: ${said}`);
|
|
23
|
+
this.name = 'BadMotion';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Plain ASCII, every character, and it is checked rather than claimed.
|
|
28
|
+
*
|
|
29
|
+
* Not one glyph in a creature is one whose width the terminal gets to decide.
|
|
30
|
+
* Box drawing and the half-block set are the ones that get eaten by a CJK font
|
|
31
|
+
* setting, and art that is one cell wider on somebody else's machine does not
|
|
32
|
+
* look narrow, it looks broken. A `block` that renders six cells wide has also
|
|
33
|
+
* quietly broken whatever was laid out beside it.
|
|
34
|
+
*/
|
|
35
|
+
const PRINTABLE = /^[\x20-\x7e]*$/;
|
|
36
|
+
export function registerCreature(spec) {
|
|
37
|
+
const art = {};
|
|
38
|
+
const size = {};
|
|
39
|
+
for (const form of FORMS) {
|
|
40
|
+
const bound = BOUNDS[form];
|
|
41
|
+
const byMood = {};
|
|
42
|
+
for (const mood of MOODS) {
|
|
43
|
+
const frames = framesOf(spec[form][mood]).map((rows) => [...rows]);
|
|
44
|
+
if (frames.length === 0)
|
|
45
|
+
throw new BadDrawing(spec.name, form, mood, 'no frames');
|
|
46
|
+
for (const rows of frames) {
|
|
47
|
+
if (rows.length === 0)
|
|
48
|
+
throw new BadDrawing(spec.name, form, mood, 'an empty frame');
|
|
49
|
+
if (rows.length > bound.rows) {
|
|
50
|
+
throw new BadDrawing(spec.name, form, mood, `${rows.length} rows, and ${form} allows ${bound.rows}`);
|
|
51
|
+
}
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
if (row.length > bound.cols) {
|
|
54
|
+
throw new BadDrawing(spec.name, form, mood, `a row ${row.length} cells wide, and ${form} allows ${bound.cols}`);
|
|
55
|
+
}
|
|
56
|
+
if (!PRINTABLE.test(row)) {
|
|
57
|
+
throw new BadDrawing(spec.name, form, mood, `"${row}" uses a glyph whose width a terminal gets to decide`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
byMood[mood] = frames;
|
|
62
|
+
}
|
|
63
|
+
// Squared per form, not per creature: `draw` being sixteen cells wide is
|
|
64
|
+
// no reason for `inline` to be, and a mood change must not move what is
|
|
65
|
+
// under the figure.
|
|
66
|
+
art[form] = byMood;
|
|
67
|
+
size[form] = square(MOODS.map((mood) => byMood[mood]));
|
|
68
|
+
}
|
|
69
|
+
const creature = {
|
|
70
|
+
name: spec.name, label: spec.label, about: spec.about ?? '', art, size,
|
|
71
|
+
motion: spec.motion ? registerMotion(spec.name, spec.motion) : undefined,
|
|
72
|
+
};
|
|
73
|
+
BOOD.set(creature.name, creature);
|
|
74
|
+
return creature;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The moving half, framed and slot-checked.
|
|
78
|
+
*
|
|
79
|
+
* The check that matters is the slot width. A `%` run one cell wider than the
|
|
80
|
+
* face that fills it does not fail, it draws a rabbit with a column of its own
|
|
81
|
+
* head missing - and only in the moods whose token is short. That is a bug
|
|
82
|
+
* nobody finds by looking at the happy one, so it is settled here, once, for
|
|
83
|
+
* every mood against every pose.
|
|
84
|
+
*/
|
|
85
|
+
function registerMotion(name, motion) {
|
|
86
|
+
const faces = MOODS.map((mood) => motion.faces[mood]);
|
|
87
|
+
const tells = MOODS.map((mood) => motion.tells[mood]);
|
|
88
|
+
const oneWidth = (tokens, what) => {
|
|
89
|
+
const widths = new Set(tokens.map((token) => token.length));
|
|
90
|
+
if (widths.size !== 1)
|
|
91
|
+
throw new BadMotion(name, what, `${widths.size} different widths, and a slot has one`);
|
|
92
|
+
return [...widths][0];
|
|
93
|
+
};
|
|
94
|
+
const faceWidth = oneWidth([...faces, motion.blink], 'faces');
|
|
95
|
+
const tellWidth = oneWidth(tells, 'tells');
|
|
96
|
+
const frame = (key, cell) => {
|
|
97
|
+
const frames = framesOf(cell).map((rows) => [...rows]);
|
|
98
|
+
if (frames.length === 0)
|
|
99
|
+
throw new BadMotion(name, key, 'no frames');
|
|
100
|
+
for (const rows of frames) {
|
|
101
|
+
if (rows.length === 0)
|
|
102
|
+
throw new BadMotion(name, key, 'an empty frame');
|
|
103
|
+
if (rows.length > BOUNDS.draw.rows) {
|
|
104
|
+
throw new BadMotion(name, key, `${rows.length} rows, and a figure allows ${BOUNDS.draw.rows}`);
|
|
105
|
+
}
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
if (!PRINTABLE.test(row)) {
|
|
108
|
+
throw new BadMotion(name, key, `"${row}" uses a glyph whose width a terminal gets to decide`);
|
|
109
|
+
}
|
|
110
|
+
for (const run of row.match(/%+/g) ?? []) {
|
|
111
|
+
if (run.length !== faceWidth)
|
|
112
|
+
throw new BadMotion(name, key, `a ${run.length}-cell face slot, and the faces are ${faceWidth}`);
|
|
113
|
+
}
|
|
114
|
+
for (const run of row.match(/#+/g) ?? []) {
|
|
115
|
+
if (run.length !== tellWidth)
|
|
116
|
+
throw new BadMotion(name, key, `a ${run.length}-cell tell slot, and the tells are ${tellWidth}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return frames;
|
|
121
|
+
};
|
|
122
|
+
const poses = {};
|
|
123
|
+
for (const [key, cell] of Object.entries(motion.poses))
|
|
124
|
+
poses[key] = frame(key, cell);
|
|
125
|
+
if (!poses['idle.front'] && !poses.idle)
|
|
126
|
+
throw new BadMotion(name, 'idle', 'no idle to fall back to');
|
|
127
|
+
const overrides = {};
|
|
128
|
+
for (const [mood, byPose] of Object.entries(motion.overrides ?? {})) {
|
|
129
|
+
const framed = {};
|
|
130
|
+
for (const [key, cell] of Object.entries(byPose))
|
|
131
|
+
framed[key] = frame(`${mood}/${key}`, cell);
|
|
132
|
+
overrides[mood] = framed;
|
|
133
|
+
}
|
|
134
|
+
const every = [...Object.values(poses), ...Object.values(overrides).flatMap((byPose) => Object.values(byPose))];
|
|
135
|
+
const reach = { left: 0, right: 0 };
|
|
136
|
+
for (const frames of every) {
|
|
137
|
+
const anchor = anchorOf(frames);
|
|
138
|
+
const { width } = metric(frames[0]);
|
|
139
|
+
reach.left = Math.max(reach.left, anchor);
|
|
140
|
+
reach.right = Math.max(reach.right, width - 1 - anchor);
|
|
141
|
+
}
|
|
142
|
+
return { ...motion, poses, overrides, reach };
|
|
143
|
+
}
|
|
144
|
+
export function getCreature(name) {
|
|
145
|
+
return BOOD.get(name);
|
|
146
|
+
}
|
|
147
|
+
/** Every one registered, in registration order. */
|
|
148
|
+
export function listCreatures() {
|
|
149
|
+
return [...BOOD.values()];
|
|
150
|
+
}
|
|
151
|
+
export function creatureNames() {
|
|
152
|
+
return [...BOOD.keys()];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A miss is drawn, not thrown.
|
|
156
|
+
*
|
|
157
|
+
* A name that is not registered is a runtime miss - the same thing a missing
|
|
158
|
+
* component registration is - and the answer is the first creature registered
|
|
159
|
+
* rather than a blank space, because a blank space in the middle of an empty
|
|
160
|
+
* screen looks like the screen is broken.
|
|
161
|
+
*/
|
|
162
|
+
function resolve(name) {
|
|
163
|
+
return (name === undefined ? undefined : BOOD.get(name)) ?? BOOD.values().next().value;
|
|
164
|
+
}
|
|
165
|
+
/** The rows of one creature, in one mood, at one size. */
|
|
166
|
+
export function drawCreature(name, mood = 'happy', options = {}) {
|
|
167
|
+
const frames = creatureFrames(name, mood, options.form);
|
|
168
|
+
const at = ((options.frame ?? 0) % frames.length + frames.length) % frames.length;
|
|
169
|
+
return frames[at];
|
|
170
|
+
}
|
|
171
|
+
/** Every frame of one cell. One long, where the drawing does not move. */
|
|
172
|
+
export function creatureFrames(name, mood = 'happy', form = 'draw') {
|
|
173
|
+
const creature = resolve(name);
|
|
174
|
+
if (!creature)
|
|
175
|
+
return [[]];
|
|
176
|
+
return creature.art[form][mood];
|
|
177
|
+
}
|
|
178
|
+
/** What a form costs for one creature, whatever mood it is in. */
|
|
179
|
+
export function creatureSize(name, form = 'draw') {
|
|
180
|
+
return resolve(name)?.size[form] ?? { width: 0, height: 0 };
|
|
181
|
+
}
|
|
182
|
+
/** The tallest anyone in the bood is, for a caller deciding whether there is room. */
|
|
183
|
+
export function boodHeight(form = 'draw') {
|
|
184
|
+
return Math.max(0, ...listCreatures().map((creature) => creature.size[form].height));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The chain, and the chain is the design rather than the first hit.
|
|
188
|
+
*
|
|
189
|
+
* A species that has no left view of a pose falls through to its front one and
|
|
190
|
+
* still reads right; the owl's glide is deliberately front-only, because a
|
|
191
|
+
* bird planing is not pointed anywhere in particular. Overrides come first at
|
|
192
|
+
* every step, so a mood that changes the outline rather than the expression -
|
|
193
|
+
* the cat at a keyboard while a turn is running - wins before a facing does.
|
|
194
|
+
*/
|
|
195
|
+
export function poseFrames(name, pose, facing, mood) {
|
|
196
|
+
const motion = resolve(name)?.motion;
|
|
197
|
+
if (!motion)
|
|
198
|
+
return undefined;
|
|
199
|
+
const chain = [
|
|
200
|
+
`${pose}.${facing}`, `${pose}.front`, `${pose}.right`, pose,
|
|
201
|
+
`idle.${facing}`, 'idle.front', 'idle',
|
|
202
|
+
];
|
|
203
|
+
const override = motion.overrides[mood];
|
|
204
|
+
for (const key of chain) {
|
|
205
|
+
const hit = override?.[key] ?? motion.poses[key];
|
|
206
|
+
if (hit)
|
|
207
|
+
return hit;
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
/** The moving half of one creature, or nothing if it was never drawn moving. */
|
|
212
|
+
export function creatureMotion(name) {
|
|
213
|
+
return resolve(name)?.motion;
|
|
214
|
+
}
|
|
215
|
+
/** Which of the bood have been drawn moving, for a picker that offers it. */
|
|
216
|
+
export function livelyNames() {
|
|
217
|
+
return listCreatures().filter((creature) => creature.motion).map((creature) => creature.name);
|
|
218
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BoxProps, RenderOutput, SemanticVariant } from '@textui/core';
|
|
2
|
+
import type { Form, Mood } from './types.js';
|
|
3
|
+
export interface CreatureProps extends BoxProps {
|
|
4
|
+
/** Which one. Left out, one is picked at random and kept for this mount. */
|
|
5
|
+
name?: string;
|
|
6
|
+
mood?: Mood;
|
|
7
|
+
/** How much room it gets: the whole figure, a 3x5 portrait, or one line of 5. */
|
|
8
|
+
form?: Form;
|
|
9
|
+
/**
|
|
10
|
+
* Off pins frame zero - the still.
|
|
11
|
+
*
|
|
12
|
+
* Also pinned when the runtime has animation off, without asking: `useFrame`
|
|
13
|
+
* returns zero there, which is the whole reason frame zero is the still.
|
|
14
|
+
*/
|
|
15
|
+
animated?: boolean;
|
|
16
|
+
/** Override the mood's own tone. */
|
|
17
|
+
tone?: SemanticVariant;
|
|
18
|
+
/**
|
|
19
|
+
* Gravity, and a will of its own.
|
|
20
|
+
*
|
|
21
|
+
* Opt-in, and it needs a creature that has been drawn moving - three of the
|
|
22
|
+
* six have been. Anything else keeps the still it always had rather than
|
|
23
|
+
* jittering in place, because a creature with no walk cycle standing in a
|
|
24
|
+
* ten-row box is worse than a creature in one row.
|
|
25
|
+
*/
|
|
26
|
+
lively?: boolean;
|
|
27
|
+
/** The field it lives in, in cells. Only read when `lively`. */
|
|
28
|
+
fieldWidth?: number;
|
|
29
|
+
fieldHeight?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare const Creature: (props: CreatureProps) => RenderOutput;
|