@kvmet/beefwife 0.0.0-stage → 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/CHANGELOG.md +23 -0
- package/LICENSE +373 -0
- package/README.md +509 -2
- package/beefwife.example.json +260 -0
- package/beefwife.js +3182 -0
- package/beefwife.min.js +4 -0
- package/package.json +72 -4
- package/samples/bounding.json +260 -0
- package/samples/chevron-guy.json +270 -0
- package/samples/long-girl.json +233 -0
- package/samples/peristaltic.json +260 -0
- package/samples/reticulating.json +260 -0
- package/samples/slow-guy.json +233 -0
- package/samples/undulating.json +260 -0
- package/src/atlas.mjs +285 -0
- package/src/beefwife.mjs +540 -0
- package/src/bend.mjs +172 -0
- package/src/body.mjs +501 -0
- package/src/carry.mjs +60 -0
- package/src/chain.mjs +83 -0
- package/src/descriptor.mjs +426 -0
- package/src/display.mjs +65 -0
- package/src/drive.mjs +77 -0
- package/src/geometry.mjs +240 -0
- package/src/global.mjs +14 -0
- package/src/graphics.mjs +715 -0
- package/src/legs.mjs +223 -0
- package/src/limits.mjs +19 -0
- package/src/model.mjs +267 -0
- package/src/pixi.mjs +11 -0
- package/src/schema.mjs +243 -0
- package/src/skin.mjs +313 -0
- package/src/tables.mjs +109 -0
package/README.md
CHANGED
|
@@ -1,3 +1,510 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Beefwife
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`src/schema.mjs` holds the schema-v1 tree, the authority on a beefwife's
|
|
4
|
+
portable JSON contract, and `src/descriptor.mjs` reads, resizes, and reports
|
|
5
|
+
bounds against it. Version 1 deliberately accepts no older descriptor shapes or
|
|
6
|
+
field aliases. The other modules build, step, and render beefwives; Beefwife Lab
|
|
7
|
+
is a consumer of this directory.
|
|
8
|
+
|
|
9
|
+
`name` is the portable identity and JSON filename stem: a lowercase slug of up
|
|
10
|
+
to 64 letters, digits, and hyphens. Schema v1 does not mix display copy into the
|
|
11
|
+
runtime descriptor.
|
|
12
|
+
|
|
13
|
+
The runtime modules consume only schema v1. Callers use `Beefwife`; the model,
|
|
14
|
+
gait, body, legs, and skin modules are implementation boundaries.
|
|
15
|
+
|
|
16
|
+
`beefwife.js` and `beefwife.min.js` are generated from `src` by `bb
|
|
17
|
+
beefwife-build`; edit the sources, never the artifacts.
|
|
18
|
+
|
|
19
|
+
## Installing
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @kvmet/beefwife pixi.js
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Pixi is a peer dependency. The module entry imports it, so a bundler resolves
|
|
26
|
+
one copy for the page and the library shares it:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { Beefwife, Descriptor } from "@kvmet/beefwife";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Renderer support
|
|
33
|
+
|
|
34
|
+
Beefwife supports headless simulation and both Pixi renderers, WebGL and
|
|
35
|
+
WebGPU. The pixi.js peer dependency requires at least 8.21.0: in earlier 8.x
|
|
36
|
+
releases, destroying one WebGPU renderer can break another.
|
|
37
|
+
|
|
38
|
+
The classic-script build reads `window.PIXI` instead and adds one global,
|
|
39
|
+
`Beefwife`, with the JSON half hanging off it as `Beefwife.Descriptor`. Where
|
|
40
|
+
that global is missing a beefwife still simulates: it builds no display children
|
|
41
|
+
and leaves `onRender` null, so a headless page or a server gets the pose without
|
|
42
|
+
a renderer.
|
|
43
|
+
|
|
44
|
+
In Node, `require` loads the classic-script build and `import` loads the module
|
|
45
|
+
entry. The two are separate copies with separate `Beefwife` classes, so
|
|
46
|
+
`instanceof` fails across them. Use one form throughout a program.
|
|
47
|
+
|
|
48
|
+
## Public runtime contract
|
|
49
|
+
|
|
50
|
+
The library exposes `Beefwife` for one live Pixi display object and `Descriptor`
|
|
51
|
+
for plain JSON. Physics, gait, legs, skin, render snapshots, caches, and
|
|
52
|
+
fixed-step state remain private.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { Beefwife, Descriptor } from "@kvmet/beefwife";
|
|
56
|
+
|
|
57
|
+
const chevronGuy = Descriptor.parse(text);
|
|
58
|
+
const beefwife = new Beefwife(chevronGuy, {
|
|
59
|
+
position: { x: 200, y: 150 }, // world px at the head
|
|
60
|
+
direction: { x: 1, y: 0 },
|
|
61
|
+
phase: 0, // gait phase in radians
|
|
62
|
+
random: Math.random,
|
|
63
|
+
render: {
|
|
64
|
+
roundVertices: false,
|
|
65
|
+
pixelResolution: 1,
|
|
66
|
+
kneeProjection: {
|
|
67
|
+
centerX: 0,
|
|
68
|
+
centerY: 0,
|
|
69
|
+
perspective: 0.002,
|
|
70
|
+
maxOffset: 256,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
beefwife.step(dt, { throttle: 1, direction }); // dt is seconds
|
|
76
|
+
app.stage.addChild(beefwife); // Pixi refreshes it when the stage renders
|
|
77
|
+
beefwife.setDescriptor(nextDescriptor);
|
|
78
|
+
beefwife.reset({ position, direction, phase });
|
|
79
|
+
beefwife.translate({ x: 20, y: 0 });
|
|
80
|
+
const pose = beefwife.getPose();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The constructor options object and each of its fields are optional. Position
|
|
84
|
+
defaults to `{ x: 0, y: 0 }`, direction to `{ x: 1, y: 0 }`, phase to zero, and
|
|
85
|
+
random to `Math.random`. Coordinates and phase must be finite. Direction must be
|
|
86
|
+
a nonzero finite vector and is normalized by the library. `random` must be a
|
|
87
|
+
function whose samples are finite numbers from zero inclusive to one exclusive;
|
|
88
|
+
it seeds the breathing starting phase and leg variation. Caller-supplied
|
|
89
|
+
positions and translations are limited to
|
|
90
|
+
`±Beefwife.MAX_WORLD_COORDINATE` (one billion px). Placement and translation are
|
|
91
|
+
rejected atomically if any body chunk would leave that supported range. If
|
|
92
|
+
simulation reaches the numeric boundary, the whole beefwife is rebased just
|
|
93
|
+
inside it; current, previous, leg, and ornament positions move together, so
|
|
94
|
+
velocity and secondary motion are preserved.
|
|
95
|
+
|
|
96
|
+
The optional `render` object is a live rendering policy with exactly three
|
|
97
|
+
fields. `roundVertices` is boolean pixel snapping. `pixelResolution` is the
|
|
98
|
+
positive number of output pixels per world pixel from `1e-6` to `1e6`, and
|
|
99
|
+
defaults to one; snapping uses its output-pixel grid, and a host should update
|
|
100
|
+
it when renderer resolution changes. A live value that is not a finite number
|
|
101
|
+
keeps drawing at the last valid one and is reported once, after the render
|
|
102
|
+
pass, the way texture rendering failures are. `kneeProjection` is `null` or an object
|
|
103
|
+
containing `centerX` and `centerY` within `±Beefwife.MAX_WORLD_COORDINATE` and
|
|
104
|
+
a `perspective` from zero to `1e6`, plus an optional `maxOffset` from zero to
|
|
105
|
+
`Beefwife.MAX_WORLD_COORDINATE`. The ranges are wide enough for any real
|
|
106
|
+
viewport and narrow enough that the projection arithmetic stays finite; a
|
|
107
|
+
retained object edited past them is capped rather than rejected, since nothing
|
|
108
|
+
revalidates it. Projection changes only
|
|
109
|
+
the drawn knee shared by the two segments of each limb; simulated hips and
|
|
110
|
+
planted feet do not move. The object is retained so a host may update its center when
|
|
111
|
+
its viewport changes. Unknown render or projection fields are rejected.
|
|
112
|
+
|
|
113
|
+
`step` owns fixed simulation substeps but never owns a clock or animation loop.
|
|
114
|
+
`dt` must be finite and nonnegative seconds, and all of it is simulated: a
|
|
115
|
+
`dt` of one second costs sixty substeps. Only the host can tell a resumed
|
|
116
|
+
background tab from a deliberate fast-forward, so the host bounds `dt` before
|
|
117
|
+
passing it, usually to one frame of its own rate. The controls
|
|
118
|
+
object is optional. Omitted direction retains the last requested direction;
|
|
119
|
+
omitted throttle means full throttle. Throttle must be finite from zero to one;
|
|
120
|
+
inputs are rejected rather than clamped. Throttle linearly scales gait phase
|
|
121
|
+
rate, channel amplitude, and steering. At zero, existing velocity settles under
|
|
122
|
+
full contact but the beefwife produces no new drive.
|
|
123
|
+
|
|
124
|
+
`Beefwife` extends `PIXI.Container`. Its retained display children refresh
|
|
125
|
+
through Pixi's render lifecycle, so hosts only add the beefwife to a stage and
|
|
126
|
+
render that stage. The creature's own parts hold a container of their own one
|
|
127
|
+
level down, so a child a host adds to a beefwife keeps the place the host gave
|
|
128
|
+
it however the creature is edited. Feet sit below the limb mesh. Under-layer
|
|
129
|
+
ornaments retain descriptor order, followed by the ribbon, plates from tail to
|
|
130
|
+
head, and over-layer ornaments in descriptor order. Headless platforms may
|
|
131
|
+
simulate beefwives without creating display children. With Pixi loaded,
|
|
132
|
+
construction and descriptor replacement reject SVG paths and colors that Pixi
|
|
133
|
+
cannot parse.
|
|
134
|
+
|
|
135
|
+
Two inherited container properties belong to the library. `label` carries the
|
|
136
|
+
descriptor's `name` and is rewritten on every `setDescriptor`. `onRender` is
|
|
137
|
+
how a beefwife draws itself, and `destroy` clears it. A host that wants its own
|
|
138
|
+
label or its own per-frame callback puts them on a parent or a sibling.
|
|
139
|
+
|
|
140
|
+
`setDescriptor` validates replacement resources before changing the instance.
|
|
141
|
+
Invalid input changes nothing about the object definition; a rejected call may
|
|
142
|
+
still have drawn from `random`, which seeds state rather than definition.
|
|
143
|
+
Replacement keeps as much of the live creature as the new descriptor allows.
|
|
144
|
+
Chunk positions, previous positions, steering state, fixed-step remainder, and
|
|
145
|
+
gait and breathing phases survive every edit. Changing a section's chunk count
|
|
146
|
+
carries the chain rather than re-placing it: a chunk the descriptor still names
|
|
147
|
+
keeps its exact position and velocity, and an added one is seeded between its
|
|
148
|
+
neighbours, so the creature settles from where it stood. The head tangent then
|
|
149
|
+
follows whichever chunk sits behind the head, which is a real turn when the
|
|
150
|
+
chunk that defined it was removed.
|
|
151
|
+
|
|
152
|
+
Only the number of leg pairs rebuilds foot state, and only a change to the
|
|
153
|
+
expanded ornament list rebuilds ornament swing. Everything else those parts
|
|
154
|
+
read comes from the model each step, so a foot stays planted and a swing stays
|
|
155
|
+
mid-flight while its stance, anchor, colour, or spring settings change under
|
|
156
|
+
it. Enabling breathing samples a starting phase. A change in breathing strength
|
|
157
|
+
eases in over at most 0.2 s rather than resizing the trunk at once.
|
|
158
|
+
|
|
159
|
+
Display objects are retained on the same terms, one at a time rather than all
|
|
160
|
+
together. A replacement repaints the children it can and changes only what the
|
|
161
|
+
new descriptor disagrees with. Feet, plates, and ornaments draw from baked
|
|
162
|
+
frames, so they are rebuilt only when a frame changes (a shape, paint, or
|
|
163
|
+
scale) or a placement does (a count or a layer); until the new frames are
|
|
164
|
+
baked, the old shapes keep moving with the body. A limb or ribbon mesh is
|
|
165
|
+
rebuilt only when its vertex count moves or its paint gains or loses a fill or
|
|
166
|
+
a visible stroke. Adding a leg pair rebuilds the limb mesh; adding a chunk
|
|
167
|
+
rebuilds the ribbon mesh. Nothing else in the scene is discarded, so no edit
|
|
168
|
+
restarts the creature.
|
|
169
|
+
|
|
170
|
+
`reset(options)` deliberately clears physical and secondary state and samples a
|
|
171
|
+
new breathing phase when breathing is enabled. Its optional position defaults
|
|
172
|
+
to the current head position, phase to the current gait phase, and direction to
|
|
173
|
+
the current head tangent. Omitting direction preserves the last requested
|
|
174
|
+
control direction; providing direction uses it for both body placement and
|
|
175
|
+
future omitted-direction steps. Values follow the constructor's validation
|
|
176
|
+
rules.
|
|
177
|
+
|
|
178
|
+
`translate({ x, y })` shifts all current and previous world positions by a
|
|
179
|
+
finite offset. It preserves velocity, gait phase, steering, and secondary
|
|
180
|
+
motion, making it suitable for world wrapping. It does not change the last
|
|
181
|
+
requested direction.
|
|
182
|
+
|
|
183
|
+
`destroy()` takes down every display object the instance owns and detaches it
|
|
184
|
+
from its parent. It is required, not optional: a host running many beefwives
|
|
185
|
+
must call it, because the scene is retained and nothing else frees it.
|
|
186
|
+
Afterwards `step`, `setDescriptor`, `reset`, and `translate` throw. The
|
|
187
|
+
descriptor and the last pose remain readable.
|
|
188
|
+
|
|
189
|
+
`getPose()` returns a stable runtime-owned snapshot in world pixels. The same
|
|
190
|
+
object is refreshed by `step`, `setDescriptor`, `reset`, and `translate`, so a
|
|
191
|
+
host may retain the reference instead of requesting a new object each frame.
|
|
192
|
+
Treat the snapshot as read-only. `head` is chunk zero, `center` is the
|
|
193
|
+
arithmetic centroid of all chain chunks, and `direction` is the normalized head
|
|
194
|
+
tangent pointing headward. Each is `{ x, y }`. It never exposes mutable chunks
|
|
195
|
+
or renderer state, and changing the snapshot cannot change the simulation.
|
|
196
|
+
Render bounds and collision geometry are not part of schema v1. Constructor
|
|
197
|
+
input and the `descriptor` getter cannot be used to mutate the instance. Random
|
|
198
|
+
variation comes only from the injected random function.
|
|
199
|
+
|
|
200
|
+
A descriptor is a value. One model is compiled per descriptor object and
|
|
201
|
+
shared by every creature given that object, so a cast of a thousand pays one
|
|
202
|
+
compile. The object is deep-frozen when it is compiled, which is what makes
|
|
203
|
+
that safe: an edit in place would otherwise leave every creature drawing the
|
|
204
|
+
model built before it, silently. Pass a copy to change a descriptor, and pass
|
|
205
|
+
the copy to `setDescriptor`. A host that loads its creatures once and never
|
|
206
|
+
edits them, which is the ordinary case, need do nothing.
|
|
207
|
+
|
|
208
|
+
`restLength` is the chain's resting arc length in world pixels, summed over
|
|
209
|
+
every link and averaged across section boundaries. It follows `setDescriptor`.
|
|
210
|
+
A host that compares `getPose()` against its own bounds needs it: the centroid
|
|
211
|
+
trails the head by up to this distance, so a viewport test without it reads a
|
|
212
|
+
long creature as gone while it is still on screen.
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.23.0/dist/pixi.min.js"></script>
|
|
216
|
+
<script src="beefwife.js"></script>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
const chevronGuy = Beefwife.Descriptor.parse(text);
|
|
221
|
+
const textAgain = Beefwife.Descriptor.stringify(chevronGuy);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`read(value)` validates an already parsed value. It returns a deep-owned plain
|
|
225
|
+
JSON value with fixed fields and definition keys in canonical order. Unknown
|
|
226
|
+
keys, omitted fields, non-plain objects, invalid references, and values outside
|
|
227
|
+
their declared bounds are errors.
|
|
228
|
+
|
|
229
|
+
Feet, plates, and ornaments share one texture sheet per distinct appearance
|
|
230
|
+
and renderer. The sheet is capped at 2048 × 2048 texels, with each frame capped
|
|
231
|
+
at 1024 × 1024. When the frames together exceed the sheet budget, the frame cap
|
|
232
|
+
is reduced until they fit. Shapes below that cap retain their requested detail.
|
|
233
|
+
Large or complex creatures can therefore look coarser while keeping their world
|
|
234
|
+
size, attachment positions, drawing order, and particle batches. Repeated shapes
|
|
235
|
+
with the same paint share a frame.
|
|
236
|
+
|
|
237
|
+
With Pixi loaded, construction and `setDescriptor` also validate shape bounds.
|
|
238
|
+
A rejected edit leaves the current creature intact. Texture rendering failures
|
|
239
|
+
emit `error` on the Beefwife; without a listener, the error is thrown. A failed bake is retried
|
|
240
|
+
only after an edit, a resolution change, or a renderer change.
|
|
241
|
+
|
|
242
|
+
Descriptor px are world pixels, and `scale(descriptor, factor)` is the one way
|
|
243
|
+
to resize a creature. It returns a new descriptor with every
|
|
244
|
+
length-dimensioned field transformed (px multiply, radians-per-px divide), so
|
|
245
|
+
the pose trace scales by the factor with timing and feel unchanged. A product
|
|
246
|
+
outside its field's bounds is an error, not a clamp. Shape outlines grow with
|
|
247
|
+
the placement scale. Ribbon and limb outline widths grow directly; a paint
|
|
248
|
+
shared with a shape receives a separate shape entry when scaled.
|
|
249
|
+
|
|
250
|
+
`bounds(path)` reports what the schema enforces for one field, so an editor can
|
|
251
|
+
read a range from the schema that checks it instead of keeping a second copy.
|
|
252
|
+
Paths name fields with dots, an array item with `[]`, and a record entry with
|
|
253
|
+
`*`:
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
Descriptor.bounds("legs.pairs");
|
|
257
|
+
// { kind: "number", min: 0, max: 128, integer: true, nullable: false }
|
|
258
|
+
Descriptor.bounds("chain.skin.ornaments[].side");
|
|
259
|
+
// { kind: "choice", values: ["left", "right", "both"], nullable: false }
|
|
260
|
+
Descriptor.bounds("definitions.paints.*.stroke.width");
|
|
261
|
+
// { kind: "number", min: 0, max: 1000, integer: false, nullable: false }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Every kind reports `nullable`. Numbers add `min`, `max`, and `integer`; strings
|
|
265
|
+
add `minLength`, `maxLength`, `pattern`, and `blankAllowed`; choices add
|
|
266
|
+
`values`; objects add `fields`; records add `minEntries`, `maxEntries`, and
|
|
267
|
+
`keyPattern`; arrays add `maxLength`. Each call returns fresh `pattern` and
|
|
268
|
+
`keyPattern` objects, so editing one cannot change what `read` enforces.
|
|
269
|
+
|
|
270
|
+
A few fields answer to more than themselves, and no per-field bound can say so:
|
|
271
|
+
the three section chunk counts must total from 2 to 256, a placement's anchor
|
|
272
|
+
must land on a chunk in scope that no other plate claims, the contact and
|
|
273
|
+
gather amplitudes are rejected where their section scale and the gait channel
|
|
274
|
+
together would drive a link or a footfall negative, and each gait channel's
|
|
275
|
+
harmonic times `cyclesPerSecond` must be at most 15. A path the schema does
|
|
276
|
+
not define is an error. A consumer may narrow a range to suit its own users,
|
|
277
|
+
but a value outside the reported bound is one `read` rejects.
|
|
278
|
+
|
|
279
|
+
See [beefwife.example.json](beefwife.example.json) for the complete shape.
|
|
280
|
+
|
|
281
|
+
## Definitions and links
|
|
282
|
+
|
|
283
|
+
Every reference stays inside one descriptor:
|
|
284
|
+
|
|
285
|
+
- A material is physical chain response: velocity retention per second, joint
|
|
286
|
+
and link correction, and directional grip.
|
|
287
|
+
- A shape is one SVG path in chunk-local pixels. Positive x points toward the
|
|
288
|
+
head and positive y points outward.
|
|
289
|
+
- A paint is a nullable fill colour and a nullable `{ colour, width }` stroke.
|
|
290
|
+
At least one must be visible. Stroke width uses local shape units for feet,
|
|
291
|
+
plates, and ornaments, and world pixels for ribbons and limbs.
|
|
292
|
+
|
|
293
|
+
Each chain section names one complete material. Sharing a material id links the
|
|
294
|
+
sections; giving the tail different physics means defining another material and
|
|
295
|
+
changing its reference. There is no material inheritance or partial override.
|
|
296
|
+
|
|
297
|
+
Material response belongs to chunks. The link joining two chunks averages their
|
|
298
|
+
`linkCorrection`, including links across section boundaries. Boundary resting
|
|
299
|
+
spacing and gather response are likewise the averages of their adjacent chunks.
|
|
300
|
+
Link correction is always positive, and every link is held to at most three
|
|
301
|
+
times its current target length however soft its material, so separation cannot
|
|
302
|
+
grow without bound. The ceiling sits well above ordinary motion and engages
|
|
303
|
+
only on a chain that would otherwise run away.
|
|
304
|
+
|
|
305
|
+
Visual placements name one shape and one paint. Geometry and paint never refer
|
|
306
|
+
to each other, and definitions cannot refer outside their descriptor.
|
|
307
|
+
|
|
308
|
+
## Chain regions
|
|
309
|
+
|
|
310
|
+
The chain has exactly `head`, `trunk`, and `tail` sections. Head and trunk must
|
|
311
|
+
contain at least one chunk; the tail may be empty. Together they contain 2 to
|
|
312
|
+
256 chunks.
|
|
313
|
+
|
|
314
|
+
Each section declares:
|
|
315
|
+
|
|
316
|
+
- `chunks`: number of points in the section;
|
|
317
|
+
- `spacing`: resting point spacing in px;
|
|
318
|
+
- `material`: physical material id;
|
|
319
|
+
- `motionScale`: response to each gait channel;
|
|
320
|
+
- `profile.ribbonWidth`: half-width in local px at both section ends;
|
|
321
|
+
- `profile.plateScale`: plate scale at both section ends.
|
|
322
|
+
|
|
323
|
+
Adjacent section spacing is resolved at build time. Profiles may meet smoothly
|
|
324
|
+
or make an intentional discontinuity.
|
|
325
|
+
|
|
326
|
+
Profile endpoints are sampled at the first and last chunk of a section. A
|
|
327
|
+
one-chunk section uses the average of its two endpoint values.
|
|
328
|
+
|
|
329
|
+
`chain.breathing` is a normalized intensity for subtle, simultaneous expansion
|
|
330
|
+
and contraction of internal trunk links. At full intensity it changes their
|
|
331
|
+
target length by at most 10%. Its independent cycle continues at zero throttle;
|
|
332
|
+
trunks with more chunks breathe more slowly. Head, tail, and section-boundary
|
|
333
|
+
links retain their ordinary target lengths.
|
|
334
|
+
|
|
335
|
+
## Chain physics
|
|
336
|
+
|
|
337
|
+
`chain.physics` holds the two responses that are not gait channels.
|
|
338
|
+
|
|
339
|
+
`autoLift` is adaptive ground lift. Each substep it ranks chunks by how far
|
|
340
|
+
their grip pushed the creature along its travel axis, picks up the `share`
|
|
341
|
+
fraction that pushed the least, and eases every chunk toward its lifted or
|
|
342
|
+
planted target at `rate` per second. `amount` is the contact a fully lifted
|
|
343
|
+
chunk loses: `0` disables the mechanism outright and `1` frees the chunk. Lift
|
|
344
|
+
multiplies the contact rhythm rather than replacing it.
|
|
345
|
+
|
|
346
|
+
`steering` turns the head toward the requested direction. The angle between
|
|
347
|
+
them times `gain` is the wanted bend bias in radians, clamped to `limit`; the
|
|
348
|
+
applied bias eases toward that at `rate` per second. The bias adds to the bend
|
|
349
|
+
channel along the whole chain, so a turning beefwife curves through its body
|
|
350
|
+
instead of pivoting at the neck. Throttle scales it.
|
|
351
|
+
|
|
352
|
+
## Gait
|
|
353
|
+
|
|
354
|
+
All channels read one phase clock and one spatial lag:
|
|
355
|
+
|
|
356
|
+
- `cyclesPerSecond` advances the shared clock. The gait is sampled once per
|
|
357
|
+
1/60 s physics step, so each channel's rate, `cyclesPerSecond * harmonic`,
|
|
358
|
+
must be at most 15: four steps a cycle. Faster waves alias into slower or
|
|
359
|
+
reversed ones.
|
|
360
|
+
- `phaseLagRadiansPerPixel` controls wavelength and direction along resting
|
|
361
|
+
chain distance. Positive values travel from head to tail.
|
|
362
|
+
- Bend is a signed sine. Its amplitude is radians of curvature at the trunk's
|
|
363
|
+
resting spacing.
|
|
364
|
+
- Thrust is a positive pulse whose acceleration is px/s^2.
|
|
365
|
+
- Gather is a signed cosine and changes resting spacing by a fraction.
|
|
366
|
+
- Contact is a positive pulse whose amplitude removes a fraction of contact.
|
|
367
|
+
|
|
368
|
+
Each channel declares an integer harmonic. Bend is the phase reference; the
|
|
369
|
+
other channels declare a phase offset in radians relative to it. Thrust and
|
|
370
|
+
contact also declare a duty cycle. Section motion scales may attenuate or
|
|
371
|
+
amplify channels, but validation prevents non-positive gathered lengths and
|
|
372
|
+
negative contact.
|
|
373
|
+
|
|
374
|
+
The grip a chunk feels is its contact rhythm times what the adaptive ground
|
|
375
|
+
lift leaves, gated by its material's directional grip; contact declares the
|
|
376
|
+
rhythm, while ground lift picks up whichever chunks push the least.
|
|
377
|
+
|
|
378
|
+
A channel reads `harmonic * (phase - distance * lag) + phaseOffset`, with
|
|
379
|
+
bend's offset fixed at zero. Thrust and
|
|
380
|
+
contact use a half-sine pulse inside the declared fraction of that channel's
|
|
381
|
+
cycle and zero outside it. A duty cycle of `0.5` therefore matches the positive
|
|
382
|
+
half of a sine.
|
|
383
|
+
|
|
384
|
+
Solver timing, relaxation pass count, mass, and waveform implementations are
|
|
385
|
+
library rules rather than descriptor parameters.
|
|
386
|
+
|
|
387
|
+
## Placements
|
|
388
|
+
|
|
389
|
+
An anchor addresses either the whole chain or one section:
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
{ section: null, from: "head", offset: 1 } // whole chain
|
|
393
|
+
{ section: "tail", from: "tail", offset: 0 }
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Offsets are zero-based from the named end. Repetition walks away from that end:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
{ count: 4, step: 1 }
|
|
400
|
+
{ count: null, step: 1 } // through the rest of this anchor's scope
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Placements that leave their scope are invalid; they are never clamped.
|
|
404
|
+
|
|
405
|
+
Only one plate may resolve onto a chunk. `chain.skin.loadScale` then grows
|
|
406
|
+
plates with grip: a plate's drawn scale is multiplied by
|
|
407
|
+
`1 + loadScale * contact`, so a chunk under full contact draws `1 + loadScale`
|
|
408
|
+
times its resting size and a negative value shrinks it instead. `-1` is the
|
|
409
|
+
floor because below it a fully gripped plate would draw mirrored.
|
|
410
|
+
|
|
411
|
+
Ornaments accumulate, may use the same anchor, and retain descriptor order.
|
|
412
|
+
Drawing order is:
|
|
413
|
+
|
|
414
|
+
1. `under` ornaments in descriptor order;
|
|
415
|
+
2. ribbon and plates;
|
|
416
|
+
3. `over` ornaments in descriptor order.
|
|
417
|
+
|
|
418
|
+
Mirrored and repeated ornaments share shape and paint definitions, but every
|
|
419
|
+
expanded ornament owns independent runtime motion. Schema v1 admits at most 512
|
|
420
|
+
expanded ornaments per beefwife.
|
|
421
|
+
|
|
422
|
+
## Legs and secondary motion
|
|
423
|
+
|
|
424
|
+
Leg pairs are distributed over their named section by resting distance.
|
|
425
|
+
`reach` is px; `spread` and `swingArc` are fractions of `reach`.
|
|
426
|
+
`swingCycles` is airborne time per step in gait cycles, so feet keep step with
|
|
427
|
+
the shared clock; a stopped clock resolves against a floor of 0.01 cycles per
|
|
428
|
+
second. Other leg timing and geometry values are bounded ratios.
|
|
429
|
+
|
|
430
|
+
Two or more pairs include the first and last chunks of their section and choose
|
|
431
|
+
nearest chunks at even resting-distance intervals. One pair uses the section
|
|
432
|
+
midpoint. More pairs than chunks may share anchors. `reach` controls the
|
|
433
|
+
forward stance window, `spread` the lateral foot offset, `lead` shifts the next
|
|
434
|
+
plant forward, and `fold` controls the drawn two-segment limb bend. `swingArc`
|
|
435
|
+
bows an airborne foot farther outward in the body plane.
|
|
436
|
+
|
|
437
|
+
Three signed ratios place the drawn joint. `jointBend` scales how far the joint
|
|
438
|
+
leaves the line between hip and foot: `1` bows it behind and outward, `0` puts
|
|
439
|
+
it on the line, and `-1` mirrors the bow ahead and inward. `jointLean` then
|
|
440
|
+
slides the joint longitudinally by that ratio of one limb length: positive
|
|
441
|
+
values lean joints toward the middle of the leg section, negative values away,
|
|
442
|
+
and zero preserves the bowed position. The lean grows from nothing at its
|
|
443
|
+
center to its full travel at the ends of the leg section, taken from where each
|
|
444
|
+
anchor sits, so pairs sharing an anchor lean alike and no body length scales
|
|
445
|
+
it. `jointLeanCenter` moves that center from the middle of the leg section
|
|
446
|
+
(`0`) to its head end (`-1`) or tail end (`1`); the far end then travels up to
|
|
447
|
+
twice one limb length.
|
|
448
|
+
|
|
449
|
+
`sidePhase` offsets right-foot contact by that fraction of `Math.PI`; `1` makes
|
|
450
|
+
the two sides opposite. `liftThreshold` releases a planted foot when its contact
|
|
451
|
+
falls below the threshold. Jitter samples permanent proportions and per-step
|
|
452
|
+
timing from the instance's injected random function. A jitter of zero makes each
|
|
453
|
+
pair exactly mirrored and consumes randomness without changing its geometry.
|
|
454
|
+
All positional jitter scales with the larger of `reach` and the resolved
|
|
455
|
+
spread; it never introduces a fixed world-pixel displacement.
|
|
456
|
+
|
|
457
|
+
`legs.skin.foot.scale` is the drawn foot size in px. `plantedScale` multiplies
|
|
458
|
+
it while the foot is on the ground, so feet may swell as they take weight and
|
|
459
|
+
shrink through the swing. It is a ratio, so a resized creature keeps it.
|
|
460
|
+
|
|
461
|
+
A limb is one closed outline, not a stroked line: hip, knee, foot down one side
|
|
462
|
+
and back up the other, with both sides sharing each knee vertex so a bend leaves
|
|
463
|
+
no gap. `legs.skin.limbWidth` sets its thickness in px. A fill-only `limbPaint`
|
|
464
|
+
draws it as a tinted mesh; any paint that also asks for a stroke moves the limbs
|
|
465
|
+
to a Graphics path and draws both passes, as the ribbon does. A `limbWidth` of
|
|
466
|
+
zero draws no limbs at all, leaving the feet on their own.
|
|
467
|
+
|
|
468
|
+
Ornament offsets are px, `angleDegrees` is degrees, and `recover` is per
|
|
469
|
+
second. Rates are time-based so visual motion does not depend on frame rate.
|
|
470
|
+
|
|
471
|
+
An ornament swings about its root, pivoting at its shape path's origin, so an
|
|
472
|
+
off-center path pivots off-center. Two root motions can drive the swing, and
|
|
473
|
+
`source` fades between them: at `0` the root's turning, which carries the body
|
|
474
|
+
wave; at `1` its sideways travel through the world, which carries speed,
|
|
475
|
+
thrust, and steering; between them a linear blend. Sideways travel is measured
|
|
476
|
+
against the creature's own body length, so a resized creature keeps the same
|
|
477
|
+
ornament feel.
|
|
478
|
+
|
|
479
|
+
Each remaining control owns one quality of the swing. `react` is its size:
|
|
480
|
+
`0` rides the root rigidly, positive trails the drive, negative leads it.
|
|
481
|
+
`recover` is its tempo: how fast the deflection chases the drive and returns
|
|
482
|
+
to rest, changing speed without changing size. `wobble` is its shape: `0`
|
|
483
|
+
settles without overshoot, `1` overshoots and rings. The deflection is
|
|
484
|
+
clamped to a quarter turn either side of rest after every fixed simulation
|
|
485
|
+
step.
|
|
486
|
+
|
|
487
|
+
## Rendering extensions
|
|
488
|
+
|
|
489
|
+
Schema v1 has no generic shader, effect, or extension-options object. Pixel
|
|
490
|
+
filters are renderer post-processing; particles are stateful anchored emitters.
|
|
491
|
+
Both need concrete runtime contracts before entering the descriptor. The local
|
|
492
|
+
shape, paint, and anchor rules are reusable when a typed effect is introduced
|
|
493
|
+
in a later schema version.
|
|
494
|
+
|
|
495
|
+
## Check
|
|
496
|
+
|
|
497
|
+
Run the permanent adversarial tests:
|
|
498
|
+
|
|
499
|
+
```sh
|
|
500
|
+
bb descriptor
|
|
501
|
+
bb beefwife-model
|
|
502
|
+
bb beefwife-api
|
|
503
|
+
bb canvas-runtime
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Run the complete headless suite with `bb test`.
|
|
507
|
+
|
|
508
|
+
Together they mutate every descriptor field and nesting level, check references,
|
|
509
|
+
scope bounds, ownership, lifecycle replacement, solver invariants, rendering
|
|
510
|
+
composition, hostile JavaScript values, canonical JSON, and the canvas boundary.
|