@nodaro/prompts 1.20.0 → 1.21.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/dist/index.cjs +8181 -7229
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -61
- package/dist/index.d.ts +120 -61
- package/dist/index.js +8082 -7133
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/adult-only-ratchet.test.ts +9 -1
- package/src/__tests__/character-motion-audit.test.ts +77 -0
- package/src/__tests__/character-motion-exit-visibility.test.ts +183 -0
- package/src/__tests__/character-motion-partner-referent.test.ts +225 -0
- package/src/__tests__/character-motion.test.ts +11 -4
- package/src/__tests__/fixtures/parameter-hint-golden.json +3 -3
- package/src/__tests__/frame-delivery.test.ts +93 -0
- package/src/__tests__/graph-composed-unwired-identity.test.ts +166 -0
- package/src/__tests__/parameter-prompt-hint.test.ts +9 -3
- package/src/character-motion/animals-pets.ts +112 -85
- package/src/character-motion/athletic-stunts.ts +155 -214
- package/src/character-motion/camera-interaction.ts +92 -133
- package/src/character-motion/combat-weapons.ts +143 -202
- package/src/character-motion/dance.ts +121 -169
- package/src/character-motion/entrances-exits.ts +88 -109
- package/src/character-motion/evasive-falls.ts +88 -130
- package/src/character-motion/everyday-actions.ts +233 -274
- package/src/character-motion/face-expression.ts +125 -178
- package/src/character-motion/gestures.ts +211 -304
- package/src/character-motion/head-gestures.ts +66 -94
- package/src/character-motion/idle-ambient.ts +85 -124
- package/src/character-motion/posture-shifts.ts +116 -139
- package/src/character-motion/runway.ts +73 -97
- package/src/character-motion/stage-performance.ts +92 -127
- package/src/character-motion/turns-looks.ts +86 -112
- package/src/character-motion/two-person.ts +241 -229
- package/src/character-motion/types.ts +4 -1
- package/src/character-motion/unnatural-horror.ts +53 -91
- package/src/character-motion/vehicles-mounts.ts +101 -97
- package/src/character-motion/walks-runs.ts +93 -121
- package/src/character-motion-diagnostics.ts +49 -0
- package/src/character-motion.ts +89 -13
- package/src/frame-delivery.ts +106 -0
- package/src/index.ts +4 -0
- package/src/parameter-prompt-hint.ts +23 -17
- package/src/picker-catalogs.ts +10 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { planFrameDelivery } from "../frame-delivery.js"
|
|
3
|
+
|
|
4
|
+
const START = "https://cdn.example/start.png"
|
|
5
|
+
const END = "https://cdn.example/end.png"
|
|
6
|
+
|
|
7
|
+
describe("planFrameDelivery", () => {
|
|
8
|
+
it("keeps a frame as a frame on a model whose frame mode measured fine", () => {
|
|
9
|
+
const plan = planFrameDelivery({
|
|
10
|
+
provider: "seedance-2-5", supportsReferenceImages: true, startFrameUrl: START, prompt: "a walk",
|
|
11
|
+
})
|
|
12
|
+
expect(plan).toEqual({ delivery: "frame", referenceImageUrls: [], promptSuffix: "" })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("moves the frame into the reference list on the Seedance 2.0 family", () => {
|
|
16
|
+
const plan = planFrameDelivery({
|
|
17
|
+
provider: "seedance-2-fast", supportsReferenceImages: true, startFrameUrl: START,
|
|
18
|
+
})
|
|
19
|
+
expect(plan.delivery).toBe("reference")
|
|
20
|
+
expect(plan.referenceImageUrls).toEqual([START])
|
|
21
|
+
expect(plan.promptSuffix).toBe("Use @image_1 as the opening (first) frame of the video.")
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("appends frames after the user's own images so their ordinals hold", () => {
|
|
25
|
+
const plan = planFrameDelivery({
|
|
26
|
+
provider: "seedance-2-fast",
|
|
27
|
+
supportsReferenceImages: true,
|
|
28
|
+
startFrameUrl: START,
|
|
29
|
+
endFrameUrl: END,
|
|
30
|
+
userReferenceUrls: ["https://cdn.example/a.png", "https://cdn.example/b.png"],
|
|
31
|
+
})
|
|
32
|
+
expect(plan.referenceImageUrls).toEqual(["https://cdn.example/a.png", "https://cdn.example/b.png", START, END])
|
|
33
|
+
expect(plan.promptSuffix).toContain("@image_3 as the opening (first) frame")
|
|
34
|
+
expect(plan.promptSuffix).toContain("@image_4 as the closing (last) frame")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("adds no second opening sentence when the prompt already binds one", () => {
|
|
38
|
+
const plan = planFrameDelivery({
|
|
39
|
+
provider: "seedance-2-fast",
|
|
40
|
+
supportsReferenceImages: true,
|
|
41
|
+
startFrameUrl: START,
|
|
42
|
+
prompt: "Use @image_1 as the first frame, it is the last keyframe of @video_1",
|
|
43
|
+
})
|
|
44
|
+
expect(plan.delivery).toBe("reference")
|
|
45
|
+
expect(plan.promptSuffix).toBe("")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("honours an explicit choice in both directions", () => {
|
|
49
|
+
expect(planFrameDelivery({
|
|
50
|
+
provider: "seedance-2-fast", requested: "frame", supportsReferenceImages: true, startFrameUrl: START,
|
|
51
|
+
}).delivery).toBe("frame")
|
|
52
|
+
expect(planFrameDelivery({
|
|
53
|
+
provider: "veo3.1", requested: "reference", supportsReferenceImages: true, startFrameUrl: START,
|
|
54
|
+
}).delivery).toBe("reference")
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("refuses, with a reason, on a model that takes no reference images", () => {
|
|
58
|
+
const plan = planFrameDelivery({
|
|
59
|
+
provider: "wan-i2v", requested: "reference", supportsReferenceImages: false, startFrameUrl: START,
|
|
60
|
+
})
|
|
61
|
+
expect(plan).toMatchObject({ delivery: "frame", refusedReason: "no-reference-support" })
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("refuses rather than displace a user's reference image at the cap", () => {
|
|
65
|
+
const refs = Array.from({ length: 9 }, (_, i) => `https://cdn.example/ref-${i}.png`)
|
|
66
|
+
const plan = planFrameDelivery({
|
|
67
|
+
provider: "seedance-2-fast", supportsReferenceImages: true, startFrameUrl: START, userReferenceUrls: refs,
|
|
68
|
+
})
|
|
69
|
+
expect(plan).toMatchObject({ delivery: "frame", refusedReason: "image-cap" })
|
|
70
|
+
expect(plan.referenceImageUrls).toEqual(refs)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("is a no-op when no frame is wired", () => {
|
|
74
|
+
const plan = planFrameDelivery({ provider: "seedance-2-fast", supportsReferenceImages: true })
|
|
75
|
+
expect(plan).toEqual({ delivery: "frame", referenceImageUrls: [], promptSuffix: "" })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("plans the same shape whether it is asked with urls or with stand-ins", () => {
|
|
79
|
+
// The config panel knows a frame is wired before it knows its url, so it
|
|
80
|
+
// passes placeholders. Ordering and sentences must not depend on the string.
|
|
81
|
+
const real = planFrameDelivery({
|
|
82
|
+
provider: "seedance-2-mini", supportsReferenceImages: true, startFrameUrl: START, endFrameUrl: END,
|
|
83
|
+
userReferenceUrls: ["https://cdn.example/a.png"],
|
|
84
|
+
})
|
|
85
|
+
const preview = planFrameDelivery({
|
|
86
|
+
provider: "seedance-2-mini", supportsReferenceImages: true, startFrameUrl: "frame:start", endFrameUrl: "frame:end",
|
|
87
|
+
userReferenceUrls: ["ref:0"],
|
|
88
|
+
})
|
|
89
|
+
expect(preview.delivery).toBe(real.delivery)
|
|
90
|
+
expect(preview.promptSuffix).toBe(real.promptSuffix)
|
|
91
|
+
expect(preview.referenceImageUrls).toHaveLength(real.referenceImageUrls.length)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { getParameterPromptHint } from "../parameter-prompt-hint.js"
|
|
4
|
+
import { getPickerCatalog, type PickerOption } from "../picker-catalogs.js"
|
|
5
|
+
import { composeTransitionHintFromConnections } from "../transitions.js"
|
|
6
|
+
import { composeCharacterFxHintFromConnections } from "../character-fx.js"
|
|
7
|
+
import type { HintEdgeLike, HintGraphContext, HintNodeLike } from "@nodaro/shared"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* THE BOUND ON "compose transition and character-fx from the graph on every
|
|
11
|
+
* path", as a proof rather than a claim.
|
|
12
|
+
*
|
|
13
|
+
* Transition and character-fx joined `EXECUTION_GRAPH_COMPOSED_PARAMETER_TYPES`
|
|
14
|
+
* (@nodaro/shared) and `LABEL_REF_GRAPH_COMPOSED_PARAMETER_TYPES` (backend
|
|
15
|
+
* `services/workflow-engine/label-ref-hint-context.ts`), so every executor now
|
|
16
|
+
* hands them the graph. That deliberately changes the prompt of a workflow that
|
|
17
|
+
* WIRES them — signed off, and the new text is the text the picker's own
|
|
18
|
+
* injection preview has always shown.
|
|
19
|
+
*
|
|
20
|
+
* What was signed off with it is the BOUND: a picker with nothing wired to its
|
|
21
|
+
* own composing handles (`startState` / `endState` for transition, `target` for
|
|
22
|
+
* character-fx) must emit BYTE-IDENTICAL text with and without the graph, so a
|
|
23
|
+
* workflow that wires nothing cannot move at all. This file walks every entry
|
|
24
|
+
* of both catalogs, in both hint modes, under two unwired graph shapes, and
|
|
25
|
+
* asserts that equality — then proves it is not vacuous with wired positive
|
|
26
|
+
* controls that MUST differ.
|
|
27
|
+
*
|
|
28
|
+
* Why two unwired shapes: "unwired" in a real workflow does not mean an
|
|
29
|
+
* isolated node. The picker is normally wired INTO a consumer (an outgoing
|
|
30
|
+
* `cinematography` edge) while nothing feeds its own handles — the walker in
|
|
31
|
+
* `resolveParameterHint` only ever looks at edges whose TARGET is the picker,
|
|
32
|
+
* and this pins that.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const MODES = ["full", "compact"] as const
|
|
36
|
+
|
|
37
|
+
/** The two pickers this change admitted, with the data field each reads. */
|
|
38
|
+
const SUBJECTS = [
|
|
39
|
+
{ type: "transition", valueField: "transition" },
|
|
40
|
+
{ type: "character-fx", valueField: "characterFx" },
|
|
41
|
+
] as const
|
|
42
|
+
|
|
43
|
+
function optionsFor(type: string): readonly PickerOption[] {
|
|
44
|
+
const catalog = getPickerCatalog(type)
|
|
45
|
+
if (!catalog?.options?.length) throw new Error(`no registered picker catalog options for ${type}`)
|
|
46
|
+
return catalog.options
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function pickerNode(type: string, valueField: string, id: string, mode: string): HintNodeLike {
|
|
50
|
+
return { id: "picker", type, data: { [valueField]: id, hintMode: mode } }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Nothing at all around the picker. */
|
|
54
|
+
function isolated(node: HintNodeLike): HintGraphContext {
|
|
55
|
+
return { nodes: [node], edges: [] }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The realistic unwired shape: the picker FEEDS a consumer, but nothing feeds it. */
|
|
59
|
+
function wiredIntoConsumer(node: HintNodeLike): HintGraphContext {
|
|
60
|
+
const consumer: HintNodeLike = { id: "consumer", type: "generate-video", data: { prompt: "a man walks forward" } }
|
|
61
|
+
const edges: HintEdgeLike[] = [
|
|
62
|
+
{ source: node.id, target: consumer.id, sourceHandle: null, targetHandle: "cinematography" },
|
|
63
|
+
]
|
|
64
|
+
return { nodes: [node, consumer], edges }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const UNWIRED_SHAPES = [
|
|
68
|
+
{ name: "isolated (no edges at all)", build: isolated },
|
|
69
|
+
{ name: "wired INTO a consumer, nothing wired into it", build: wiredIntoConsumer },
|
|
70
|
+
] as const
|
|
71
|
+
|
|
72
|
+
describe("unwired transition / character-fx pickers are byte-identical with and without the graph", () => {
|
|
73
|
+
for (const { type, valueField } of SUBJECTS) {
|
|
74
|
+
for (const { name, build } of UNWIRED_SHAPES) {
|
|
75
|
+
it(`${type}: every catalog entry × both hint modes, ${name}`, () => {
|
|
76
|
+
const options = optionsFor(type)
|
|
77
|
+
const differences: string[] = []
|
|
78
|
+
let compared = 0
|
|
79
|
+
|
|
80
|
+
for (const option of options) {
|
|
81
|
+
for (const mode of MODES) {
|
|
82
|
+
const node = pickerNode(type, valueField, option.id, mode)
|
|
83
|
+
const withoutGraph = getParameterPromptHint(node)
|
|
84
|
+
const withGraph = getParameterPromptHint(node, build(node))
|
|
85
|
+
compared += 1
|
|
86
|
+
if (withGraph !== withoutGraph) {
|
|
87
|
+
differences.push(`${type}/${option.id}/${mode}: ${JSON.stringify(withoutGraph)} -> ${JSON.stringify(withGraph)}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Non-vacuity of the WALK itself: a catalog that somehow arrived empty
|
|
93
|
+
// would pass a zero-difference assertion trivially.
|
|
94
|
+
expect(compared).toBe(options.length * MODES.length)
|
|
95
|
+
expect(compared).toBeGreaterThan(20)
|
|
96
|
+
expect(differences).toEqual([])
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
it("the catalogs really do carry injecting entries (the comparison is over real text, not all empty strings)", () => {
|
|
102
|
+
// `auto` / `none` inject "" by design and are legitimately compared; this
|
|
103
|
+
// asserts they are not ALL of it, so the identity above is meaningful.
|
|
104
|
+
for (const { type, valueField } of SUBJECTS) {
|
|
105
|
+
const injecting = optionsFor(type).filter(
|
|
106
|
+
(o) => getParameterPromptHint(pickerNode(type, valueField, o.id, "full")).trim().length > 0,
|
|
107
|
+
)
|
|
108
|
+
expect(injecting.length, type).toBeGreaterThan(20)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe("positive control — a WIRED picker must differ (or the bound above proves nothing)", () => {
|
|
114
|
+
/** A tone node: free text, so its fragment is identical in both hint modes,
|
|
115
|
+
* which keeps the control honest without re-deriving inherited-mode text. */
|
|
116
|
+
const tone = (id: string, text: string): HintNodeLike => ({ id, type: "tone", data: { tone: text } })
|
|
117
|
+
|
|
118
|
+
it("transition: a startState + endState wire changes the text, in both modes", () => {
|
|
119
|
+
const id = optionsFor("transition").find(
|
|
120
|
+
(o) => getParameterPromptHint(pickerNode("transition", "transition", o.id, "full")).trim().length > 0,
|
|
121
|
+
)!.id
|
|
122
|
+
const start = tone("start", "warm golden morning light")
|
|
123
|
+
const end = tone("end", "cold blue dusk")
|
|
124
|
+
|
|
125
|
+
for (const mode of MODES) {
|
|
126
|
+
const node = pickerNode("transition", "transition", id, mode)
|
|
127
|
+
const ctx: HintGraphContext = {
|
|
128
|
+
nodes: [node, start, end],
|
|
129
|
+
edges: [
|
|
130
|
+
{ source: "start", target: node.id, sourceHandle: null, targetHandle: "startState" },
|
|
131
|
+
{ source: "end", target: node.id, sourceHandle: null, targetHandle: "endState" },
|
|
132
|
+
],
|
|
133
|
+
}
|
|
134
|
+
const composed = getParameterPromptHint(node, ctx)
|
|
135
|
+
expect(composed).not.toBe(getParameterPromptHint(node))
|
|
136
|
+
// Derived from the catalog composer, never a hand-written sentence.
|
|
137
|
+
expect(composed).toBe(
|
|
138
|
+
composeTransitionHintFromConnections(
|
|
139
|
+
id,
|
|
140
|
+
[getParameterPromptHint(start)],
|
|
141
|
+
[getParameterPromptHint(end)],
|
|
142
|
+
undefined,
|
|
143
|
+
mode,
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it("character-fx: a target ref wire changes the text, in both modes", () => {
|
|
150
|
+
const id = optionsFor("character-fx").find(
|
|
151
|
+
(o) => getParameterPromptHint(pickerNode("character-fx", "characterFx", o.id, "full")).trim().length > 0,
|
|
152
|
+
)!.id
|
|
153
|
+
const mira: HintNodeLike = { id: "mira", type: "character", data: { characterName: "Mira" } }
|
|
154
|
+
|
|
155
|
+
for (const mode of MODES) {
|
|
156
|
+
const node = pickerNode("character-fx", "characterFx", id, mode)
|
|
157
|
+
const ctx: HintGraphContext = {
|
|
158
|
+
nodes: [node, mira],
|
|
159
|
+
edges: [{ source: "mira", target: node.id, sourceHandle: null, targetHandle: "target" }],
|
|
160
|
+
}
|
|
161
|
+
const composed = getParameterPromptHint(node, ctx)
|
|
162
|
+
expect(composed).not.toBe(getParameterPromptHint(node))
|
|
163
|
+
expect(composed).toBe(composeCharacterFxHintFromConnections(id, ["Mira"], undefined, mode))
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
})
|
|
@@ -330,8 +330,14 @@ describe("getParameterPromptHint — character-motion minor-age floor", () => {
|
|
|
330
330
|
const ADULT = { age: "age-30s" }
|
|
331
331
|
const motion = { id: "n1", type: "character-motion", data: { characterMotion: ["wave-hello", "kiss-partner"] } }
|
|
332
332
|
const wave = (target: string) => getCharacterMotionPromptHint("wave-hello").replace(/\bthe subject\b/g, target)
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
// kiss-partner names the partner twice. A wired name repeats; the unwired
|
|
334
|
+
// fallback is introduced once and referred back to after that.
|
|
335
|
+
const kiss = (target: string, ...partner: readonly string[]) => {
|
|
336
|
+
let i = 0
|
|
337
|
+
return getCharacterMotionPromptHint("kiss-partner")
|
|
338
|
+
.replace(/\bthe subject\b/g, target)
|
|
339
|
+
.replace(/\bthe partner\b/g, () => partner[Math.min(i++, partner.length - 1)]!)
|
|
340
|
+
}
|
|
335
341
|
|
|
336
342
|
it("a minor wired to target drops the adult-only move and keeps the neutral one", () => {
|
|
337
343
|
const out = getParameterPromptHint(motion, {
|
|
@@ -347,7 +353,7 @@ describe("getParameterPromptHint — character-motion minor-age floor", () => {
|
|
|
347
353
|
nodes: [{ id: "c1", type: "character", data: { characterName: "Mira", person: ADULT } }],
|
|
348
354
|
edges: [{ source: "c1", target: "n1", targetHandle: "target" }],
|
|
349
355
|
})
|
|
350
|
-
expect(out).toBe(`${wave("Mira")}, then ${kiss("Mira", "another person")}`)
|
|
356
|
+
expect(out).toBe(`${wave("Mira")}, then ${kiss("Mira", "another person", "that same person")}`)
|
|
351
357
|
expect(out).toMatch(/kiss/)
|
|
352
358
|
})
|
|
353
359
|
|
|
@@ -1,89 +1,116 @@
|
|
|
1
1
|
import type { CharacterMotion } from "./types.js"
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** animals-pets — authored motion fragments. Stable ids survive copy revisions. */
|
|
4
4
|
export const ANIMALS_PETS_MOTIONS: ReadonlyArray<CharacterMotion> = [
|
|
5
|
-
{ id: "pet-a-dog", label: "Pet a
|
|
6
|
-
promptHint: "the subject lowers an open hand onto
|
|
7
|
-
|
|
8
|
-
{ id: "scratch-behind-ears", label: "Scratch
|
|
9
|
-
promptHint: "the subject reaches to the side of
|
|
10
|
-
|
|
11
|
-
{ id: "ruffle-dog-fur", label: "Ruffle
|
|
12
|
-
promptHint: "the subject sinks the fingers deep into the thick fur at
|
|
13
|
-
|
|
14
|
-
{ id: "scoop-up-pet", label: "Scoop
|
|
15
|
-
promptHint: "the subject bends at the knees, slides one hand under
|
|
16
|
-
|
|
17
|
-
{ id: "offer-hand-to-sniff", label: "Offer
|
|
18
|
-
promptHint: "the subject extends one arm forward at waist height with the fingers curled and the knuckles down, stops the hand short of the
|
|
19
|
-
|
|
20
|
-
{ id: "offer-a-treat", label: "Offer
|
|
21
|
-
promptHint: "the subject
|
|
22
|
-
|
|
23
|
-
{ id: "get-pulled-by-leash", label: "Get
|
|
24
|
-
promptHint: "the subject is yanked forward as the leash snaps taut and stumbles a step with one shoulder dragged ahead of the torso, then leans the weight back and digs the heels in",
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
term: "
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
term: "
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
term: "
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
term: "
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
term: "
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
term: "
|
|
77
|
-
{ id: "
|
|
78
|
-
promptHint: "the subject
|
|
79
|
-
|
|
80
|
-
{ id: "
|
|
81
|
-
promptHint: "the subject
|
|
82
|
-
|
|
83
|
-
{ id: "
|
|
84
|
-
promptHint: "the subject
|
|
85
|
-
|
|
86
|
-
{ id: "
|
|
87
|
-
|
|
88
|
-
|
|
5
|
+
{ "id": "pet-a-dog", "label": "Pet a dog", "category": "animals-pets", "description": "Flat hand strokes over the dog's skull and neck",
|
|
6
|
+
"promptHint": "the subject lowers an open hand onto the counterpart's head, draws the flat palm back over the skull to the base of the neck, lifts the hand away and repeats the stroke", "term": "pets the counterpart on the head",
|
|
7
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
8
|
+
{ "id": "scratch-behind-ears", "label": "Scratch behind ears", "category": "animals-pets", "description": "Fingertips work in small circles behind an ear",
|
|
9
|
+
"promptHint": "the subject reaches to the side of the counterpart's head and works curled fingertips in small circles behind one ear, the wrist rocking, the other hand resting under the muzzle", "term": "scratches behind the ears with the counterpart",
|
|
10
|
+
"counterpart": "an animal", "requires": ["animal"] },
|
|
11
|
+
{ "id": "ruffle-dog-fur", "label": "Ruffle dog fur", "category": "animals-pets", "description": "Fingers scrub through the thick fur at the neck",
|
|
12
|
+
"promptHint": "the subject sinks the fingers deep into the thick fur at the counterpart's neck and scrubs them briskly back and forth through the coat, elbows pumping, then opens the fingers and lifts the hands away", "term": "ruffles the counterpart's fur with both hands",
|
|
13
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
14
|
+
{ "id": "scoop-up-pet", "label": "Scoop up pet", "category": "animals-pets", "description": "Bends, slides both hands under, lifts to the chest",
|
|
15
|
+
"promptHint": "the subject bends at the knees, slides one hand under the counterpart's chest and the other under its hindquarters, and straightens upright, drawing the animal in against the subject's chest", "term": "scoops up the counterpart",
|
|
16
|
+
"counterpart": "a small pet", "requires": ["pet"] },
|
|
17
|
+
{ "id": "offer-hand-to-sniff", "label": "Offer hand to sniff", "category": "animals-pets", "description": "Arm extends, knuckles down, hand held still",
|
|
18
|
+
"promptHint": "the subject extends one arm forward at waist height with the fingers curled and the knuckles down, stops the hand short of the counterpart and holds it motionless, shoulders loose", "term": "offers knuckles for the counterpart to sniff",
|
|
19
|
+
"counterpart": "the animal", "requires": ["animal"] },
|
|
20
|
+
{ "id": "offer-a-treat", "label": "Offer treat to dog", "category": "animals-pets", "description": "Holds a treat out for the dog without requiring it to take it.",
|
|
21
|
+
"promptHint": "the subject extends an open palm holding a treat toward the counterpart and holds it steady, waiting for a response", "term": "offers the counterpart a treat on open palm",
|
|
22
|
+
"counterpart": "a dog", "requires": ["dog", "treat"], "aliases": ["Offer a Treat"] },
|
|
23
|
+
{ "id": "get-pulled-by-leash", "label": "Get pulled by leash", "category": "animals-pets", "description": "Leash snaps taut and drags the arm forward",
|
|
24
|
+
"promptHint": "the subject is yanked forward as the leash snaps taut and stumbles a step with one shoulder dragged ahead of the torso, then leans the weight back and digs the heels in", "term": "gets pulled along by the leash" },
|
|
25
|
+
{ "id": "reel-in-leash", "label": "Reel in leash", "category": "animals-pets", "description": "Hand over hand shortens the leash",
|
|
26
|
+
"promptHint": "the subject gathers the leash hand over hand, each fist closing on the line and drawing it back to the hip until the slack is gone, the elbow tucked tight against the ribs", "term": "reels in the leash hand over hand" },
|
|
27
|
+
{ "id": "throw-ball-for-dog", "label": "Throw ball for dog", "category": "animals-pets", "description": "Throws a ball ahead of the dog without adding a fetch return.",
|
|
28
|
+
"promptHint": "the subject faces the same direction as the counterpart, draws the throwing arm back and throws a ball forward, following through with the arm", "term": "throws a ball ahead of the counterpart",
|
|
29
|
+
"counterpart": "a dog", "requires": ["dog", "ball"] },
|
|
30
|
+
{ "id": "kneel-to-greet", "label": "Kneel to greet", "category": "animals-pets", "description": "Drops to one knee and opens both arms",
|
|
31
|
+
"promptHint": "the subject drops onto one knee, spreads both arms wide and leans the chest forward, closing both hands around the counterpart's shoulders and rocking back slightly under the weight", "term": "kneels down to greet the counterpart",
|
|
32
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
33
|
+
{ "id": "signal-dog-to-sit", "label": "Signal dog to sit", "category": "animals-pets", "description": "Gives the dog a downward hand signal without requiring a response.",
|
|
34
|
+
"promptHint": "the subject faces the counterpart, raises a flat palm to chest height and lowers it in a deliberate downward signal, then waits", "term": "gives the counterpart a downward sit signal",
|
|
35
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
36
|
+
{ "id": "call-dog-over", "label": "Call dog over", "category": "animals-pets", "description": "Crouches and beckons the dog, then waits.",
|
|
37
|
+
"promptHint": "the subject faces the counterpart, lowers into a crouch, pats one thigh and opens both hands in an inviting gesture, then waits", "term": "crouches and beckons the counterpart",
|
|
38
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
39
|
+
{ "id": "cuddle-pet-on-lap", "label": "Cuddle pet on lap", "category": "animals-pets", "description": "Curls over a pet on the lap and strokes its back",
|
|
40
|
+
"promptHint": "the subject curls the shoulders forward over the counterpart settled across the lap, runs one hand in long strokes down its back and lets the chin drop toward it", "term": "cuddles the counterpart on the lap",
|
|
41
|
+
"counterpart": "a pet", "requires": ["pet"] },
|
|
42
|
+
{ "id": "hold-cat-to-chest", "label": "Hold cat to chest", "category": "animals-pets", "description": "Gathers a cat up against the sternum",
|
|
43
|
+
"promptHint": "the subject gathers the counterpart up against the chest, one forearm braced under its hindquarters and the other hand flat along its back, and lowers the chin toward its head", "term": "holds the counterpart against the chest",
|
|
44
|
+
"counterpart": "a cat", "requires": ["cat"] },
|
|
45
|
+
{ "id": "lift-cat-up", "label": "Lift cat up", "category": "animals-pets", "description": "Chest and hindquarters supported, lifted to eye level",
|
|
46
|
+
"promptHint": "the subject slides one hand under the counterpart's chest just behind the front legs and the other under its hindquarters, lifts it up to eye level as the arms extend, holds it there a beat before drawing it back in against the chest", "term": "lifts the counterpart up to eye level",
|
|
47
|
+
"counterpart": "a cat", "requires": ["cat"] },
|
|
48
|
+
{ "id": "lift-puppy-to-face", "label": "Lift puppy to face", "category": "animals-pets", "description": "Raises a puppy up nose to nose",
|
|
49
|
+
"promptHint": "the subject raises the counterpart in both hands from lap height up to eye level, lifts the chin and leans in until the two are nose to nose, then lowers the counterpart back down against the subject's chest", "term": "holds the counterpart up to the face",
|
|
50
|
+
"counterpart": "a puppy", "requires": ["puppy"] },
|
|
51
|
+
{ "id": "let-bird-perch", "label": "Let bird perch", "category": "animals-pets", "description": "Index finger held out level and steady",
|
|
52
|
+
"promptHint": "the subject holds one index finger out level in front of the chest and locks the elbow against the ribs to keep the hand from shaking, lifting the whole arm upward as the counterpart steps onto the finger", "term": "holds out a finger for the counterpart",
|
|
53
|
+
"counterpart": "a bird", "requires": ["bird"] },
|
|
54
|
+
{ "id": "feed-the-ducks", "label": "Feed ducks", "category": "animals-pets", "description": "Tosses small pieces of food toward the ducks.",
|
|
55
|
+
"promptHint": "the subject takes small pieces of food from a bag and tosses them underhand toward the counterpart", "term": "tosses small pieces of food toward the counterpart",
|
|
56
|
+
"counterpart": "the ducks", "requires": ["ducks", "food", "bag"], "aliases": ["Feed the Ducks"] },
|
|
57
|
+
{ "id": "stroke-horse-neck", "label": "Stroke horse neck", "category": "animals-pets", "description": "Long flat-palm sweeps down the neck",
|
|
58
|
+
"promptHint": "the subject lays a flat palm high on the counterpart's neck and sweeps it down the length of the crest in long full strokes, the other hand resting at the counterpart's shoulder", "term": "strokes the counterpart's neck",
|
|
59
|
+
"counterpart": "a horse", "requires": ["horse"] },
|
|
60
|
+
{ "id": "brush-the-horse", "label": "Brush the horse", "category": "animals-pets", "description": "Long brush strokes along the lie of the coat",
|
|
61
|
+
"promptHint": "the subject presses a brush to the counterpart's flank and pulls it in long strokes along the lie of the coat, the free hand steadying the counterpart ahead of each pass, the shoulder swinging with the motion", "term": "brushes the counterpart's coat",
|
|
62
|
+
"counterpart": "a horse", "requires": ["horse"] },
|
|
63
|
+
{ "id": "pat-horse-flank", "label": "Pat horse flank", "category": "animals-pets", "description": "Two firm open-palm pats, then rests the hand",
|
|
64
|
+
"promptHint": "the subject brings a flat hand in to pat the counterpart's flank twice with a firm open palm, then leaves the hand resting there and leans a little weight onto it", "term": "pats the counterpart on the flank",
|
|
65
|
+
"counterpart": "a horse", "requires": ["horse"] },
|
|
66
|
+
{ "id": "mount-the-horse", "label": "Mount the horse", "category": "animals-pets", "description": "Foot in the stirrup, swings the leg over",
|
|
67
|
+
"promptHint": "the subject sets one foot into the stirrup, grips the saddle with both hands, springs off the ground and swings the trailing leg over, then settles down and finds the other stirrup", "term": "mounts a horse",
|
|
68
|
+
"deprecated": true, "replacementId": "mount-horse", "aliases": ["mount horse"] },
|
|
69
|
+
{ "id": "back-away-slowly", "label": "Back away slowly from animal", "category": "animals-pets", "description": "Takes small cautious backward steps while facing the animal.",
|
|
70
|
+
"promptHint": "the subject faces the counterpart and takes small cautious backward steps, keeping the upper body steady and the gaze forward", "term": "takes cautious backward steps away from the counterpart",
|
|
71
|
+
"counterpart": "an animal", "requires": ["animal"], "fixedPace": true, "startPose": "standing", "endPose": "standing", "aliases": ["Back Away Slowly", "retreat"] },
|
|
72
|
+
{ "id": "let-it-approach", "label": "Let it approach", "category": "animals-pets", "description": "Goes completely still and waits it out",
|
|
73
|
+
"promptHint": "the subject stops mid-step and goes completely still, arms hanging loose at the sides, the breathing shallow so the chest barely moves, only the eyes tracking the counterpart", "term": "freezes as the counterpart comes near",
|
|
74
|
+
"counterpart": "the animal", "requires": ["animal"] },
|
|
75
|
+
{ "id": "crouch-to-photograph", "label": "Crouch to photograph", "category": "animals-pets", "description": "Drops low and frames the animal with a phone",
|
|
76
|
+
"promptHint": "the subject drops into a low crouch, braces the elbows on the knees and lifts a phone in both hands up in front of the face, holding dead still before tapping the screen with a thumb", "term": "crouches to photograph an animal" },
|
|
77
|
+
{ "id": "tug-the-toy", "label": "Tug toy with dog", "category": "animals-pets", "description": "Pulls one end of a rope toy while the dog holds the other.",
|
|
78
|
+
"promptHint": "the subject holds one end of a rope toy while the counterpart holds the other, leans back with a gentle pull and then steps forward to loosen the tension", "term": "tugs a rope toy held by the counterpart",
|
|
79
|
+
"counterpart": "a dog", "requires": ["dog", "rope toy"], "aliases": ["Tug the Toy"] },
|
|
80
|
+
{ "id": "wipe-muddy-paws", "label": "Wipe muddy paws", "category": "animals-pets", "description": "Kneels and towels one paw at a time",
|
|
81
|
+
"promptHint": "the subject kneels on one knee, lifts the counterpart's paw into a cupped hand, wraps a towel around it and rubs it clean, sets it back down and reaches for the next one", "term": "wipes the counterpart's muddy paws",
|
|
82
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
83
|
+
{ "id": "brace-for-jump", "label": "Brace for jump", "category": "animals-pets", "description": "Plants the back foot to absorb a dog jumping up",
|
|
84
|
+
"promptHint": "the subject plants the back foot and drops the pelvis to take the counterpart's weight against the chest, both hands rising to catch its shoulders, the torso rocking back then steadying", "term": "braces as the counterpart jumps up",
|
|
85
|
+
"counterpart": "a dog", "requires": ["dog"] },
|
|
86
|
+
{ "id": "dog-subject-sit", "label": "Dog sits", "category": "animals-pets", "description": "Dog sits.",
|
|
87
|
+
"term": "sits down on the hindquarters", "promptHint": "the subject is a dog that lowers its hindquarters to the ground while keeping the front legs upright",
|
|
88
|
+
"requires": ["dog as target subject"], "aliases": ["animal subject"] },
|
|
89
|
+
{ "id": "dog-subject-lie-down", "label": "Dog lies down", "category": "animals-pets", "description": "Dog lies down.",
|
|
90
|
+
"term": "lies down with all four limbs resting", "promptHint": "the subject is a dog that lowers the chest and hindquarters to rest on the ground",
|
|
91
|
+
"requires": ["dog as target subject"], "aliases": ["animal subject"] },
|
|
92
|
+
{ "id": "dog-subject-wag-tail", "label": "Dog wags tail", "category": "animals-pets", "description": "Dog wags tail.",
|
|
93
|
+
"term": "wags the tail", "promptHint": "the subject is a dog that wags its tail from side to side while keeping the body in place",
|
|
94
|
+
"requires": ["dog as target subject"], "aliases": ["animal subject"] },
|
|
95
|
+
{ "id": "dog-subject-shake", "label": "Dog shakes body", "category": "animals-pets", "description": "Dog shakes body.",
|
|
96
|
+
"term": "shakes the body from head to tail", "promptHint": "the subject is a dog that shakes its body from head through torso to tail and then becomes still",
|
|
97
|
+
"requires": ["dog as target subject"], "aliases": ["animal subject"] },
|
|
98
|
+
{ "id": "cat-subject-stretch", "label": "Cat stretches", "category": "animals-pets", "description": "Cat stretches.",
|
|
99
|
+
"term": "stretches the front legs and back", "promptHint": "the subject is a cat that extends both front legs, lowers the chest and lengthens the back before returning upright",
|
|
100
|
+
"requires": ["cat as target subject"], "aliases": ["animal subject"] },
|
|
101
|
+
{ "id": "horse-subject-walk", "label": "Horse walks", "category": "animals-pets", "description": "Horse walks.",
|
|
102
|
+
"term": "walks with a steady four-beat gait", "promptHint": "the subject is a horse that walks steadily forward in a four-beat gait",
|
|
103
|
+
"requires": ["horse as target subject"], "aliases": ["animal subject"] },
|
|
104
|
+
{ "id": "horse-subject-trot", "label": "Horse trots", "category": "animals-pets", "description": "Horse trots.",
|
|
105
|
+
"term": "trots with alternating diagonal leg pairs", "promptHint": "the subject is a horse that trots forward using alternating diagonal pairs of legs",
|
|
106
|
+
"requires": ["horse as target subject"], "aliases": ["animal subject"] },
|
|
107
|
+
{ "id": "horse-subject-canter", "label": "Horse canters", "category": "animals-pets", "description": "Horse canters.",
|
|
108
|
+
"term": "canters in a rhythmic three-beat gait", "promptHint": "the subject is a horse that canters forward in a rhythmic three-beat gait",
|
|
109
|
+
"requires": ["horse as target subject"], "aliases": ["animal subject"] },
|
|
110
|
+
{ "id": "bird-subject-hop", "label": "Bird hops", "category": "animals-pets", "description": "Bird hops.",
|
|
111
|
+
"term": "hops forward and settles", "promptHint": "the subject is a bird that makes a small hop forward and settles on both feet",
|
|
112
|
+
"requires": ["bird as target subject"], "aliases": ["animal subject"] },
|
|
113
|
+
{ "id": "bird-subject-takeoff", "label": "Bird takes flight", "category": "animals-pets", "description": "Bird takes flight.",
|
|
114
|
+
"term": "pushes off, beating wings to take flight", "promptHint": "the subject is a bird that pushes off its perch and beats its wings to take flight",
|
|
115
|
+
"requires": ["bird as target subject"], "aliases": ["animal subject"] },
|
|
89
116
|
]
|