Skip to content

Commit 91079fa

Browse files
mcp-tool-shopclaude
andcommitted
feat(c3): the scene descriptor crosses — timeOfDay closes two gaps (forge half)
C3/P4 forge half. `zones[].scene` — the descriptor the client''s diorama binds to. STABLE KEYS ONLY, and the exclusions are the design. RG-C: HD-2D''s look is wholly client-side, so the sim owes a DESCRIPTOR not a layout (Takahashi/Miyauchi 2018), and state flags swap lighting and dressing variants, NEVER layout (Triangle Strategy). So `parallaxLayers`, `skylineRef`, `collisionType`, the physics fields and the sky/light hints are deliberately NOT here and stay `no-channel` — a descriptor carrying them would be a renderer config crossing a sim boundary. Nothing new is invented: `biome` comes from the existing `biome:` tag convention (the same idiom as `mode:` and `faction:`), `timeOfDay` is already authored, and `dressingDensity` is a coarse three-bucket read of interactable count — ordinal because the client decides what density means and a continuous number would imply precision the author never had. ⚠ ONE FIELD CLOSED TWO GAPS. C0 filed `zones[].timeOfDay` as `no-channel` with the note "no channel — even though the SpawnCondition grammar has a `time:` operand the engine could gate on." That parenthetical named the second gap without knowing it: C3/P2 measured `time-of-day` as an UNEVALUABLE gate operand for exactly this reason. The descriptor gives the field its channel, and that channel is the missing input the operand needed. THE RECOUNT: no-channel 170 -> 169, carried-lossless 189 -> 190, total 377, all verified. Suite 2431, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent cbeac96 commit 91079fa

6 files changed

Lines changed: 92 additions & 18 deletions

File tree

docs/c0-alignment/export-table.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
"tally": {
1616
"total": 377,
1717
"byClass": {
18-
"no-channel": 170,
19-
"carried-lossless": 189,
18+
"no-channel": 169,
19+
"carried-lossless": 190,
2020
"carried-approximated": 18
2121
},
2222
"byChannel": {
23-
"none": 170,
23+
"none": 169,
2424
"assetPacks": 11,
2525
"assets": 11,
26-
"contentPack": 169,
26+
"contentPack": 170,
2727
"packMeta": 6,
2828
"assetBindings": 7,
2929
"manifest": 3
@@ -4181,14 +4181,14 @@
41814181
},
41824182
{
41834183
"path": "zones[].timeOfDay",
4184-
"class": "no-channel",
4185-
"channel": "none",
4186-
"packPath": null,
4187-
"transform": null,
4188-
"absenceProof": "key-absent",
4184+
"class": "carried-lossless",
4185+
"channel": "contentPack",
4186+
"packPath": "zones[].scene.timeOfDay",
4187+
"transform": "moved-into-scene-descriptor",
4188+
"absenceProof": null,
41894189
"verified": true,
4190-
"evidence": "key \"timeOfDay\" absent from all six export artifacts",
4191-
"note": "Time-of-day key: no channel — even though the SpawnCondition grammar has a `time:` operand the engine could gate on."
4190+
"evidence": "transform \"moved-into-scene-descriptor\" → contentPack:zones[].scene.timeOfDay (3 values); semantics asserted separately",
4191+
"note": "CLOSED BY C3/P4. Lands on the scene descriptor the client's diorama binds to — a STABLE KEY, not prose. Also supplies the input the `time-of-day` gate operand was measured missing."
41924192
}
41934193
]
41944194
}

docs/c0-alignment/export-table.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Generated by `packages/export-ai-rpg/src/__tests__/c0-export-table.test.ts`. Do not hand-edit.
44

5-
Schema version 4.5.0. Tally: {"no-channel":170,"carried-lossless":189,"carried-approximated":18}.
5+
Schema version 4.5.0. Tally: {"no-channel":169,"carried-lossless":190,"carried-approximated":18}.
66

77
| Field | Class | Channel | Image | Note |
88
|---|---|---|---|---|
@@ -382,4 +382,4 @@ Schema version 4.5.0. Tally: {"no-channel":170,"carried-lossless":189,"carried-a
382382
| `zones[].stratumId` | no-channel | none || The zone→stratum membership link, dropped along with the strata themselves. |
383383
| `zones[].tags[]` | carried-lossless | contentPack | `zones[].tags[]` | The single richest carried zone field — the engine reads tags as spawn, chokepoint, ambush and safe-zone gates. |
384384
| `zones[].tilesetId` | carried-lossless | assetBindings | `zones{}.tilesetId` | Same channel caveat as backgroundId. |
385-
| `zones[].timeOfDay` | no-channel | none | | Time-of-day key: no channel — even though the SpawnCondition grammar has a `time:` operand the engine could gate on. |
385+
| `zones[].timeOfDay` | carried-lossless | contentPack | `zones[].scene.timeOfDay` *(moved-into-scene-descriptor)* | CLOSED BY C3/P4. Lands on the scene descriptor the client's diorama binds to — a STABLE KEY, not prose. Also supplies the input the `time-of-day` gate operand was measured missing. |

docs/c0-alignment/fixture-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
"contentPacks": [
2222
"c0-vocabulary-coverage"
2323
],
24-
"contentHash": "sha256:63fde4f7eaaec7320548b3ac0a05fc2cf758336e3a7c11d1c6d3ef6f4768ea25"
24+
"contentHash": "sha256:375ffb5c4afc51ad65216902234051c702e364018c8c6a70285b6bd8aafce283"
2525
}

docs/c0-alignment/fixture-pack.json

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,11 @@
167167
},
168168
"hazardRefs": [
169169
"hazard-scalding-steam"
170-
]
170+
],
171+
"scene": {
172+
"timeOfDay": "dusk",
173+
"dressingDensity": "normal"
174+
}
171175
},
172176
{
173177
"id": "zone-under-vault",
@@ -243,7 +247,11 @@
243247
"hazardRefs": [
244248
"hazard-black-water",
245249
"hazard-void-drop"
246-
]
250+
],
251+
"scene": {
252+
"timeOfDay": "night",
253+
"dressingDensity": "normal"
254+
}
247255
},
248256
{
249257
"id": "zone-sky-gantry",
@@ -304,7 +312,11 @@
304312
},
305313
"hazardRefs": [
306314
"hazard-void-drop"
307-
]
315+
],
316+
"scene": {
317+
"timeOfDay": "day",
318+
"dressingDensity": "normal"
319+
}
308320
}
309321
],
310322
"districts": [

packages/export-ai-rpg/src/__tests__/c0/export-table-data.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,13 @@ export const EXPLICIT_ROWS: ExportRow[] = [
271271
{ path: 'zones[].directionalLightYaw', class: 'no-channel', absence: { kind: 'key-absent', key: 'directionalLightYaw' }, note: 'Lighting hint: no channel.' },
272272
{ path: 'zones[].directionalLightPitch', class: 'no-channel', absence: { kind: 'key-absent', key: 'directionalLightPitch' }, note: 'Lighting hint: no channel.' },
273273
{ path: 'zones[].skyLightIntensity', class: 'no-channel', absence: { kind: 'key-absent', key: 'skyLightIntensity' }, note: 'Lighting hint: no channel.' },
274-
{ path: 'zones[].timeOfDay', class: 'no-channel', absence: { kind: 'key-absent', key: 'timeOfDay' }, note: 'Time-of-day key: no channel — even though the SpawnCondition grammar has a `time:` operand the engine could gate on.' },
274+
// ⚠ FLIPPED BY C3/P4, and this row closed TWO gaps with one field. C0's note —
275+
// "no channel — even though the SpawnCondition grammar has a `time:` operand the
276+
// engine could gate on" — named the second one without knowing it: C3/P2
277+
// measured `time-of-day` as an UNEVALUABLE gate operand precisely because
278+
// nothing tracked time of day. The scene descriptor gives `timeOfDay` its
279+
// channel, and that channel is the missing input the operand needed.
280+
{ path: 'zones[].timeOfDay', class: 'carried-lossless', channel: 'contentPack', packPath: 'zones[].scene.timeOfDay', transform: 'moved-into-scene-descriptor', note: 'CLOSED BY C3/P4. Lands on the scene descriptor the client\'s diorama binds to — a STABLE KEY, not prose. Also supplies the input the `time-of-day` gate operand was measured missing.' },
275281
{ path: 'zones[].collisionType', class: 'no-channel', absence: { kind: 'key-absent', key: 'collisionType' }, note: 'Collision channel hint: no channel.' },
276282

277283
// ── Districts ───────────────────────────────────────────────────────

packages/export-ai-rpg/src/convert-zones.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,67 @@ export type ExportedEntryGate = {
2828
reason?: string;
2929
};
3030

31+
/** The scene descriptor the client's diorama binds to (C3/P4). Stable keys only. */
32+
export type ExportedScene = {
33+
biome?: string;
34+
timeOfDay?: string;
35+
dressingDensity?: 'sparse' | 'normal' | 'dense';
36+
variantTags?: string[];
37+
};
38+
3139
/** The engine's `ZoneDefinition` plus the unpublished C3 fields. See {@link ExportedEntryGate}. */
3240
export type ExportedZone = ZoneDefinition & {
3341
entryGate?: ExportedEntryGate;
3442
/** C3/P3 — ids into the pack's `hazardDefinitions`. */
3543
hazardRefs?: string[];
44+
/** C3/P4 — the scene descriptor. */
45+
scene?: ExportedScene;
3646
};
3747

48+
/**
49+
* Build a zone's scene descriptor from the keys the schema ALREADY authors
50+
* (C3/P4).
51+
*
52+
* ⚠ NOTHING NEW IS INVENTED HERE, which is the point. The forge has carried
53+
* `timeOfDay` and `tags` since before this cycle; C0 filed `zones[].timeOfDay` as
54+
* `no-channel` with the note "no channel — even though the SpawnCondition grammar
55+
* has a `time:` operand the engine could gate on." This closes both at once: the
56+
* descriptor gives `timeOfDay` its channel, and that channel is the missing input
57+
* the `time-of-day` gate operand needed (C3/P2 measured it unevaluable for exactly
58+
* this reason).
59+
*
60+
* ⚠ AND WHAT IS DELIBERATELY EXCLUDED. `parallaxLayers`, `skylineRef`,
61+
* `collisionType`, the physics fields and the sky/light hints are NOT here. They
62+
* are client-owned per the charter and stay `no-channel` in the recount. A
63+
* descriptor that carried them would be a renderer config crossing a simulation
64+
* boundary — the exact thing Triangle Strategy's "swap dressing, never layout"
65+
* rule forbids.
66+
*
67+
* `biome` is derived from the zone's authored tags rather than invented: the first
68+
* `biome:`-prefixed tag, if any. Tag conventions are how this schema already
69+
* expresses `mode:` and `faction:`, so this adds no new authoring surface.
70+
*/
71+
function buildScene(z: {
72+
tags: string[];
73+
timeOfDay?: string;
74+
interactables: unknown[];
75+
propPlacements?: unknown[];
76+
}): ExportedScene | undefined {
77+
const biomeTag = z.tags.find((t) => t.startsWith('biome:'));
78+
// Coarse and ORDINAL — three buckets, because the client decides what density
79+
// means and a continuous number would imply a precision the author never had.
80+
const propCount = z.interactables.length;
81+
const dressingDensity: ExportedScene['dressingDensity'] =
82+
propCount === 0 ? 'sparse' : propCount >= 3 ? 'dense' : 'normal';
83+
84+
const scene: ExportedScene = {
85+
...(biomeTag ? { biome: biomeTag.slice('biome:'.length) } : {}),
86+
...(z.timeOfDay !== undefined ? { timeOfDay: z.timeOfDay } : {}),
87+
dressingDensity,
88+
};
89+
return Object.keys(scene).length > 0 ? scene : undefined;
90+
}
91+
3892
/**
3993
* Convert project zones → engine `ZoneDefinition[]`.
4094
*
@@ -193,6 +247,8 @@ export function convertZones(project: WorldProject, warnings?: string[]): Export
193247
// mean anything without pack code. C0 measured that difference across twelve
194248
// worlds and the contrast is preserved deliberately.
195249
hazardRefs: z.hazardRefs && z.hazardRefs.length > 0 ? [...z.hazardRefs] : undefined,
250+
// C3/P4 — the scene descriptor, built from keys already authored.
251+
scene: buildScene(z),
196252
};
197253
});
198254
}

0 commit comments

Comments
 (0)