diff --git a/.pql/changelog/ticket_history/2026-09.sql b/.pql/changelog/ticket_history/2026-09.sql index 1345874b7..abd1cc77f 100644 --- a/.pql/changelog/ticket_history/2026-09.sql +++ b/.pql/changelog/ticket_history/2026-09.sql @@ -120,3 +120,38 @@ worktree refusals — none of them performed. Every assertion was mutation-check Gate: `ruff check tooling/` clean, `make test-tooling` PASS (12 checks).', NULL, '2026-09-02 14:54:35', '2026-09-02 14:54:35.578', '2026-09-02 14:54:35.578', NULL, 'e0bd8f1d16b2dc7c7d1b8b1511633cc3', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G64ASTXBBHMWMBQSVPM9MK1W', 'status', 'backlog', 'done', NULL, '2026-09-02 14:54:37', '2026-09-02 14:54:37.762', '2026-09-02 14:54:37.762', NULL, '5f9240c61aa2bf03924e4edaf4dd6f7c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65N2W393K7A4B8JA8FGP22R', 'description', NULL, '`generate-character-manifest` (now `reach generate character-manifest`) emits +only `{"slot": ...}` per clothing entry. The committed +`client/assets/characters/manifest.json` also carries `style` and +`logo_capable`, added to the schema by T-1089 — the generator was never taught +to produce them. + +So the artifact is AHEAD of its generator, and `make manifest` silently reverts +the T-1089 extension. Confirmed on 2026-09-02: a regen produced the same 24 +garments with those two fields dropped, and `test_garment_engine_t1089.gd > +test_manifest_has_tshirt_with_style_and_logo_flag` failed on the push gate. +Reverted in 6024045fc. + +This is the exact failure mode `.claude/rules/asset-pipeline.md` exists to +prevent, one directory over: an artifact that cannot be reproduced from its +source. The manifest is not covered by that rule (no meta stamp, not +systems.db), which is why it drifted unnoticed. + +WHAT TO DECIDE FIRST — where do `style` and `logo_capable` come from? They are +not derivable from a filename. Either: + (a) a sidecar the generator reads (e.g. the clothing `coverage.json` already + beside the assets, or per-garment metadata), or + (b) the manifest is genuinely hand-maintained, in which case the generator + should MERGE rather than overwrite, and say so. + +(a) is the asset-pipeline-consistent answer. (b) needs the generator to stop +claiming to be the source of truth. + +ALSO: `test_garment_engine_t1089.gd:108` reports `Expecting ''modern'' but was +''modern''` — the strings print identically, so the assertion is comparing +something other than what it displays (String vs StringName, or a trailing +character). Worth a look while in there; a failure message that shows two +identical values costs whoever hits it real time. + +Acceptance: `reach generate character-manifest` on a clean tree leaves +manifest.json byte-identical, and the T-1089 test still passes after it runs.', NULL, '2026-09-02 15:54:41', '2026-09-02 15:54:41.911', '2026-09-02 15:54:41.911', NULL, '1a36359b2986139da3157d2429351f74', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_idmap/2026-09.sql b/.pql/changelog/ticket_idmap/2026-09.sql index b34be2f2c..5c57f34fa 100644 --- a/.pql/changelog/ticket_idmap/2026-09.sql +++ b/.pql/changelog/ticket_idmap/2026-09.sql @@ -1,3 +1,4 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G63FEA51J4JKE3A7473D3QZ0', 'T-1284', '2026-09-02 10:50:23.145', '2026-09-02 10:50:23.145', NULL, '74113524e3a280bd06875305a547349a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at; INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G642RKW8PNW7AP9BY9203SPG', 'T-1285', '2026-09-02 12:14:48.290', '2026-09-02 12:14:48.290', NULL, '020f491d1ae8fbde83d2aae0f8325011', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at; INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G64ASTXBBHMWMBQSVPM9MK1W', 'T-1286', '2026-09-02 12:49:55.434', '2026-09-02 12:49:55.434', NULL, '7c65442cc261176c9c472fdbd635cc91', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at; +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65N2W393K7A4B8JA8FGP22R', 'T-1287', '2026-09-02 15:54:39.514', '2026-09-02 15:54:39.514', NULL, 'de690eafa611669d722b0ffb850a1d7b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at; diff --git a/.pql/changelog/tickets/2026-09.sql b/.pql/changelog/tickets/2026-09.sql index d67af1523..c124d16d1 100644 --- a/.pql/changelog/tickets/2026-09.sql +++ b/.pql/changelog/tickets/2026-09.sql @@ -229,3 +229,39 @@ worktree refusals — none of them performed. Every assertion was mutation-check unrecognised verdict and blocked the push. Gate: `ruff check tooling/` clean, `make test-tooling` PASS (12 checks).', 'done', 'medium', NULL, NULL, 'D-263', '2026-09-02 12:49:55.434', '2026-09-02 14:54:37.762', NULL, '678c0d0578c2255438d2e51829d87a64', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65N2W393K7A4B8JA8FGP22R', 'bug', '06FBPPMZNNEV052DBYYY3A897C', 'make manifest silently drops the T-1089 garment schema fields', NULL, 'backlog', 'medium', NULL, 'client', NULL, '2026-09-02 15:54:39.514', '2026-09-02 15:54:39.514', NULL, 'ddb9c65aa4c4537924de591c4c017051', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65N2W393K7A4B8JA8FGP22R', 'bug', '06FBPPMZNNEV052DBYYY3A897C', 'make manifest silently drops the T-1089 garment schema fields', '`generate-character-manifest` (now `reach generate character-manifest`) emits +only `{"slot": ...}` per clothing entry. The committed +`client/assets/characters/manifest.json` also carries `style` and +`logo_capable`, added to the schema by T-1089 — the generator was never taught +to produce them. + +So the artifact is AHEAD of its generator, and `make manifest` silently reverts +the T-1089 extension. Confirmed on 2026-09-02: a regen produced the same 24 +garments with those two fields dropped, and `test_garment_engine_t1089.gd > +test_manifest_has_tshirt_with_style_and_logo_flag` failed on the push gate. +Reverted in 6024045fc. + +This is the exact failure mode `.claude/rules/asset-pipeline.md` exists to +prevent, one directory over: an artifact that cannot be reproduced from its +source. The manifest is not covered by that rule (no meta stamp, not +systems.db), which is why it drifted unnoticed. + +WHAT TO DECIDE FIRST — where do `style` and `logo_capable` come from? They are +not derivable from a filename. Either: + (a) a sidecar the generator reads (e.g. the clothing `coverage.json` already + beside the assets, or per-garment metadata), or + (b) the manifest is genuinely hand-maintained, in which case the generator + should MERGE rather than overwrite, and say so. + +(a) is the asset-pipeline-consistent answer. (b) needs the generator to stop +claiming to be the source of truth. + +ALSO: `test_garment_engine_t1089.gd:108` reports `Expecting ''modern'' but was +''modern''` — the strings print identically, so the assertion is comparing +something other than what it displays (String vs StringName, or a trailing +character). Worth a look while in there; a failure message that shows two +identical values costs whoever hits it real time. + +Acceptance: `reach generate character-manifest` on a clean tree leaves +manifest.json byte-identical, and the T-1089 test still passes after it runs.', 'backlog', 'medium', NULL, 'client', NULL, '2026-09-02 15:54:39.514', '2026-09-02 15:54:41.911', NULL, '2f6393f44525977bfeba1200e8f79561', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; diff --git a/client/assets/characters/manifest.json b/client/assets/characters/manifest.json index 6de3c8955..08f8e7004 100644 --- a/client/assets/characters/manifest.json +++ b/client/assets/characters/manifest.json @@ -46,33 +46,6 @@ "thick" ], "clothing": { - "boots_modern": { - "slot": "feet" - }, - "buttondown_modern": { - "slot": "torso" - }, - "cargo_pants": { - "slot": "legs" - }, - "hoodie_modern": { - "slot": "torso" - }, - "jacket_modern": { - "slot": "torso" - }, - "jeans_modern": { - "slot": "torso" - }, - "joggers_modern": { - "slot": "torso" - }, - "pants_formal": { - "slot": "legs" - }, - "parka_thrds": { - "slot": "torso" - }, "peasant_pants": { "slot": "legs" }, @@ -82,41 +55,289 @@ "peasant_tunic": { "slot": "torso" }, - "shoes_formal": { - "slot": "feet" + "tshirt_modern": { + "slot": "torso", + "style": "modern", + "logo_capable": true + }, + "hoodie_modern": { + "slot": "torso", + "style": "modern", + "logo_capable": true + }, + "buttondown_modern": { + "slot": "torso", + "style": "modern", + "logo_capable": false }, "shorts_modern": { - "slot": "legs" + "slot": "legs", + "style": "modern", + "logo_capable": false }, - "slides": { - "slot": "torso" + "jeans_modern": { + "slot": "legs", + "style": "modern", + "logo_capable": false }, - "sneakers_modern": { - "slot": "torso" + "pants_formal": { + "slot": "legs", + "style": "formal", + "logo_capable": false + }, + "jacket_modern": { + "slot": "torso", + "style": "modern", + "logo_capable": true }, "suit_jacket_black": { - "slot": "torso" - }, - "sweater_modern": { - "slot": "torso" - }, - "swim_trunks": { - "slot": "torso" - }, - "swimsuit_onepiece": { - "slot": "torso" - }, - "tank_top": { - "slot": "torso" - }, - "track_jacket": { - "slot": "torso" - }, - "tshirt_modern": { - "slot": "torso" + "slot": "torso", + "style": "formal", + "logo_capable": false, + "regions": { + "R": "lapels_collar", + "G": "jacket_body", + "B": "sleeves", + "A": "shirt_triangle" + }, + "default_tints": [ + [ + 0.13, + 0.13, + 0.16, + 1 + ], + [ + 0.09, + 0.09, + 0.11, + 1 + ], + [ + 0.09, + 0.09, + 0.11, + 1 + ], + [ + 0.93, + 0.93, + 0.96, + 1 + ] + ] }, "uniform_utility": { - "slot": "torso" + "slot": "full_body", + "style": "utility", + "logo_capable": true, + "default_tints": [ + "#3b474f", + "#6b8280", + "#d18c2e", + "#d9dee0" + ] + }, + "tank_top": { + "slot": "torso", + "style": "modern", + "logo_capable": true, + "regions": { + "R": "collar_band", + "G": "body", + "B": "armhole_trim" + }, + "default_tints": [ + "#f5f2eb", + "#e8693b", + "#f5f2eb", + "#ffffff" + ] + }, + "sweater_modern": { + "slot": "torso", + "style": "modern", + "logo_capable": false, + "regions": { + "R": "collar", + "G": "body", + "B": "cuffs_hem" + }, + "default_tints": [ + "#946b4f", + "#b8916e", + "#946b4f", + "#ffffff" + ] + }, + "track_jacket": { + "slot": "torso", + "style": "sport", + "logo_capable": true, + "multi_region": true, + "regions": { + "R": "stand_collar", + "G": "body", + "B": "sleeve_stripes", + "A": "cuffs_hem_band" + }, + "default_tints": [ + "#ebedf2", + "#294d9e", + "#f0f2f7", + "#1a2652" + ] + }, + "joggers_modern": { + "slot": "legs", + "style": "modern", + "logo_capable": false + }, + "cargo_pants": { + "slot": "legs", + "style": "modern", + "logo_capable": false + }, + "swim_trunks": { + "slot": "legs", + "style": "modern", + "logo_capable": false + }, + "swimsuit_onepiece": { + "slot": "full_body", + "style": "modern", + "logo_capable": false, + "regions": { + "R": "straps_trim", + "G": "body", + "B": "side_blocks" + }, + "default_tints": [ + "#f2f0e8", + "#e34a33", + "#1f7a8c", + "#ffffff" + ] + }, + "sneakers_modern": { + "slot": "feet", + "style": "modern", + "logo_capable": false, + "multi_region": true, + "regions": { + "R": "sole", + "G": "upper", + "B": "laces_trim" + }, + "default_tints": [ + "#bdc0c4", + "#ebebee", + "#e1e1e6" + ] + }, + "shoes_formal": { + "slot": "feet", + "style": "formal", + "logo_capable": false, + "regions": { + "R": "sole", + "G": "upper", + "B": "toe_cap" + }, + "default_tints": [ + [ + 0.1, + 0.1, + 0.11, + 1 + ], + [ + 0.07, + 0.07, + 0.08, + 1 + ], + [ + 0.07, + 0.07, + 0.08, + 1 + ], + [ + 1, + 1, + 1, + 1 + ] + ] + }, + "boots_modern": { + "slot": "feet", + "style": "modern", + "logo_capable": false, + "regions": { + "R": "sole", + "G": "upper_shaft", + "B": "laces_eyelets" + }, + "default_tints": [ + "#292624", + "#704a2e", + "#382e26", + "#ffffff" + ] + }, + "slides": { + "slot": "feet", + "style": "modern", + "logo_capable": false, + "regions": { + "R": "sole", + "G": "strap" + }, + "default_tints": [ + [ + 0.82, + 0.83, + 0.85, + 1 + ], + [ + 0.93, + 0.35, + 0.2, + 1 + ], + [ + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1 + ] + ] + }, + "parka_thrds": { + "slot": "torso", + "style": "modern", + "logo_capable": true, + "regions": { + "R": "hood_roll_collar", + "G": "body", + "B": "sleeves", + "A": "pocket_quilt_trim" + }, + "default_tints": [ + "#384257", + "#4d5973", + "#455269", + "#788599" + ] } }, "accessories": []