@things-factory/figure-service 10.1.20 → 10.1.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/figure-service",
3
- "version": "10.1.20",
3
+ "version": "10.1.24",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "@things-factory:registry": "https://registry.npmjs.org"
@@ -23,15 +23,15 @@
23
23
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
24
24
  },
25
25
  "dependencies": {
26
- "@hatiolab/figure-model": "^0.1.20",
26
+ "@hatiolab/figure-model": "^0.1.21",
27
27
  "@operato/typeorm-history": "^10.0.0",
28
28
  "@things-factory/ai-client-base": "^10.1.20",
29
- "@things-factory/auth-base": "^10.1.20",
29
+ "@things-factory/auth-base": "^10.1.24",
30
30
  "@things-factory/env": "^10.1.20",
31
31
  "@things-factory/shell": "^10.1.20",
32
32
  "graphql-scalars": "^1.22.4",
33
33
  "graphql-upload": "^15.0.2",
34
34
  "koa-router": "^7.4.0"
35
35
  },
36
- "gitHead": "6bd01660a4b28554bbe6ae4ee58f3ff844c5af69"
36
+ "gitHead": "0d88a504e0ffa63cd91279b88c6998d4bf1b3d86"
37
37
  }
@@ -13,7 +13,9 @@
13
13
  import {
14
14
  ANCHOR_RULES,
15
15
  DETAIL_LEVELS,
16
+ CAPABILITY_NEEDS,
16
17
  CHANNEL_PATHS,
18
+ FIGURE_CAPABILITIES,
17
19
  FIGURE_PLACEMENTS,
18
20
  INTERPOLATIONS,
19
21
  LABEL_WHENS,
@@ -570,6 +572,44 @@ describe('정책을 넘은 후보는 막지 않고 알린다', () => {
570
572
  * These guard the wording, because the wording is the defect. A prompt that hides the one door
571
573
  * leaves the model to invent a door.
572
574
  */
575
+ /*
576
+ * The capability vocabulary, which the prompt used to leave open.
577
+ *
578
+ * It said "as plain names the scene knows" -- an instruction to guess. The model guessed
579
+ * `capabilities: ['hoist']`, and because the list lived only in things-scene, figure-model's
580
+ * validate could not refuse it: the asset passed validation, passed publication, and threw at
581
+ * render. ADR-0051 (4) moved the list into the format, so the prompt can now state it.
582
+ */
583
+ describe('능력 이름을 형식에서 받아 적는다 — 「씬이 아는 이름」은 지어내라는 말이다', () => {
584
+ const prompt = () => rulesPrompt(PALETTE)
585
+
586
+ it('닫힌 목록이라고 못 박는다', () => {
587
+ const text = prompt()
588
+ expect(text).toMatch(/capabilities \(top level\)[\s\S]*CLOSED LIST/)
589
+ expect(text).not.toMatch(/plain names the scene knows/)
590
+ })
591
+
592
+ it('목록을 형식에서 읽는다 — 손으로 적지 않는다', () => {
593
+ /* 이름 하나를 여기 적어 두면 형식에 능력이 늘 때 이 시험이 조용히 통과한다. */
594
+ const text = prompt()
595
+ for (const name of FIGURE_CAPABILITIES) {
596
+ expect(text).toContain(name)
597
+ }
598
+ })
599
+
600
+ it('혼자 못 서는 능력도 함께 적는다 — 그것도 형식의 선언이다', () => {
601
+ const text = prompt()
602
+ for (const [name, needs] of Object.entries(CAPABILITY_NEEDS)) {
603
+ expect(text).toContain(`${name} cannot stand alone`)
604
+ for (const one of needs) expect(text).toContain(one)
605
+ }
606
+ })
607
+
608
+ it('움직임을 능력으로 답하지 말라고 한다 — 실제로 나왔던 답이다', () => {
609
+ expect(prompt()).toMatch(/Do NOT answer it with a capability/)
610
+ })
611
+ })
612
+
573
613
  describe('값이 만드는 자세를 parameters 로 가르친다', () => {
574
614
  const prompt = () => rulesPrompt(PALETTE)
575
615
 
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ANCHOR_RULES,
3
3
  AXES,
4
+ CAPABILITY_NEEDS,
4
5
  CHANNEL_PATHS,
6
+ FIGURE_CAPABILITIES,
5
7
  DETAIL_LEVELS,
6
8
  INTERPOLATIONS,
7
9
  LABEL_WHENS,
@@ -144,7 +146,8 @@ export function rulesPrompt(palette: string[]): string {
144
146
  'Answer with a single JSON object and nothing else — no prose, no code fence.',
145
147
  '',
146
148
  'The JSON is a FigureSource:',
147
- ' { type, base: {x,y,z}, placement?, detailLevel, parts: [...], animations?: [...], capabilities?: [...] }',
149
+ ' { type, base: {x,y,z}, placement?, detailLevel, parts: [...],',
150
+ ' animations?: [...], parameters?: [...], capabilities?: [...] }',
148
151
  '',
149
152
  'A part is:',
150
153
  ' { name, primitive, transform: { position: {x,y,z}, size: {x,y,z}, rotation?: {x,y,z} },',
@@ -162,6 +165,10 @@ export function rulesPrompt(palette: string[]): string {
162
165
  ` anchor.<axis> ${ANCHOR_RULES.join(' | ')} (axis is one of ${AXES.join(', ')})`,
163
166
  ` repeat.axis ${AXES.join(' | ')}`,
164
167
  ` capability.roles ${PART_ROLES.join(' | ')}`,
168
+ ` capabilities ${FIGURE_CAPABILITIES.join(' | ')}`,
169
+ ...Object.entries(CAPABILITY_NEEDS).map(
170
+ ([name, needs]) => ` ${name} cannot stand alone; give it ${(needs as readonly string[]).join(' or ')} as well`
171
+ ),
165
172
  ` label.when ${LABEL_WHENS.join(' | ')}`,
166
173
  ` animation.path ${CHANNEL_PATHS.join(' | ')}`,
167
174
  ` animation.interpolation ${INTERPOLATIONS.join(' | ')}`,
@@ -224,7 +231,8 @@ export function rulesPrompt(palette: string[]): string {
224
231
  ' A part may hold several roles: an AGV deck is a slot that things also enter and leave.',
225
232
  ' Omit capacity when the part repeats — the count then follows the instance size.',
226
233
  ' direction is the way a port faces, in the part\'s own axes; default is +z.',
227
- ' capabilities (top level) is what the whole figure can do, as plain names the scene knows.',
234
+ ' capabilities (top level) is what the whole figure can do. The names are a CLOSED LIST',
235
+ ` (below) -- anything else is refused, so do not invent one for a motion or a shape.`,
228
236
  ' materialSlot + material.emissive make a status lamp: the token is the OFF look and',
229
237
  ' emissive { token, intensity, on? } is the ON look. A dark lamp is not a red lamp dimmed.',
230
238
  ' label is a place a number is shown on the figure: { name, source, when }. The figure says',