lootforge 0.3.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.
Files changed (243) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +764 -0
  3. package/bin/lootforge.js +28 -0
  4. package/dist/benchmarks/coarseToFineCost.d.ts +21 -0
  5. package/dist/benchmarks/coarseToFineCost.js +49 -0
  6. package/dist/benchmarks/coarseToFineCost.js.map +1 -0
  7. package/dist/checks/boundaryMetrics.d.ts +12 -0
  8. package/dist/checks/boundaryMetrics.js +102 -0
  9. package/dist/checks/boundaryMetrics.js.map +1 -0
  10. package/dist/checks/candidateScore.d.ts +11 -0
  11. package/dist/checks/candidateScore.js +462 -0
  12. package/dist/checks/candidateScore.js.map +1 -0
  13. package/dist/checks/commandParser.d.ts +5 -0
  14. package/dist/checks/commandParser.js +99 -0
  15. package/dist/checks/commandParser.js.map +1 -0
  16. package/dist/checks/consistencyOutliers.d.ts +42 -0
  17. package/dist/checks/consistencyOutliers.js +156 -0
  18. package/dist/checks/consistencyOutliers.js.map +1 -0
  19. package/dist/checks/imageAcceptance.d.ts +67 -0
  20. package/dist/checks/imageAcceptance.js +967 -0
  21. package/dist/checks/imageAcceptance.js.map +1 -0
  22. package/dist/checks/packInvariants.d.ts +56 -0
  23. package/dist/checks/packInvariants.js +1064 -0
  24. package/dist/checks/packInvariants.js.map +1 -0
  25. package/dist/checks/softAdapters.d.ts +25 -0
  26. package/dist/checks/softAdapters.js +275 -0
  27. package/dist/checks/softAdapters.js.map +1 -0
  28. package/dist/checks/vlmGate.d.ts +8 -0
  29. package/dist/checks/vlmGate.js +200 -0
  30. package/dist/checks/vlmGate.js.map +1 -0
  31. package/dist/cli/commands/atlas.d.ts +5 -0
  32. package/dist/cli/commands/atlas.js +18 -0
  33. package/dist/cli/commands/atlas.js.map +1 -0
  34. package/dist/cli/commands/eval.d.ts +6 -0
  35. package/dist/cli/commands/eval.js +23 -0
  36. package/dist/cli/commands/eval.js.map +1 -0
  37. package/dist/cli/commands/generate.d.ts +18 -0
  38. package/dist/cli/commands/generate.js +66 -0
  39. package/dist/cli/commands/generate.js.map +1 -0
  40. package/dist/cli/commands/init.d.ts +15 -0
  41. package/dist/cli/commands/init.js +146 -0
  42. package/dist/cli/commands/init.js.map +1 -0
  43. package/dist/cli/commands/package.d.ts +6 -0
  44. package/dist/cli/commands/package.js +27 -0
  45. package/dist/cli/commands/package.js.map +1 -0
  46. package/dist/cli/commands/plan.d.ts +16 -0
  47. package/dist/cli/commands/plan.js +49 -0
  48. package/dist/cli/commands/plan.js.map +1 -0
  49. package/dist/cli/commands/process.d.ts +14 -0
  50. package/dist/cli/commands/process.js +29 -0
  51. package/dist/cli/commands/process.js.map +1 -0
  52. package/dist/cli/commands/regenerate.d.ts +29 -0
  53. package/dist/cli/commands/regenerate.js +244 -0
  54. package/dist/cli/commands/regenerate.js.map +1 -0
  55. package/dist/cli/commands/review.d.ts +5 -0
  56. package/dist/cli/commands/review.js +18 -0
  57. package/dist/cli/commands/review.js.map +1 -0
  58. package/dist/cli/commands/select.d.ts +6 -0
  59. package/dist/cli/commands/select.js +21 -0
  60. package/dist/cli/commands/select.js.map +1 -0
  61. package/dist/cli/commands/serve.d.ts +16 -0
  62. package/dist/cli/commands/serve.js +100 -0
  63. package/dist/cli/commands/serve.js.map +1 -0
  64. package/dist/cli/commands/validate.d.ts +17 -0
  65. package/dist/cli/commands/validate.js +108 -0
  66. package/dist/cli/commands/validate.js.map +1 -0
  67. package/dist/cli/index.d.ts +1 -0
  68. package/dist/cli/index.js +157 -0
  69. package/dist/cli/index.js.map +1 -0
  70. package/dist/cli/parseArgs.d.ts +3 -0
  71. package/dist/cli/parseArgs.js +37 -0
  72. package/dist/cli/parseArgs.js.map +1 -0
  73. package/dist/contracts/stageArtifacts.d.ts +4031 -0
  74. package/dist/contracts/stageArtifacts.js +663 -0
  75. package/dist/contracts/stageArtifacts.js.map +1 -0
  76. package/dist/manifest/load.d.ts +3 -0
  77. package/dist/manifest/load.js +50 -0
  78. package/dist/manifest/load.js.map +1 -0
  79. package/dist/manifest/normalize-palette.d.ts +17 -0
  80. package/dist/manifest/normalize-palette.js +235 -0
  81. package/dist/manifest/normalize-palette.js.map +1 -0
  82. package/dist/manifest/normalize-policy.d.ts +48 -0
  83. package/dist/manifest/normalize-policy.js +239 -0
  84. package/dist/manifest/normalize-policy.js.map +1 -0
  85. package/dist/manifest/normalize-prompt.d.ts +14 -0
  86. package/dist/manifest/normalize-prompt.js +73 -0
  87. package/dist/manifest/normalize-prompt.js.map +1 -0
  88. package/dist/manifest/normalize-target.d.ts +49 -0
  89. package/dist/manifest/normalize-target.js +542 -0
  90. package/dist/manifest/normalize-target.js.map +1 -0
  91. package/dist/manifest/schema.d.ts +7570 -0
  92. package/dist/manifest/schema.js +373 -0
  93. package/dist/manifest/schema.js.map +1 -0
  94. package/dist/manifest/semantic-validation.d.ts +4 -0
  95. package/dist/manifest/semantic-validation.js +526 -0
  96. package/dist/manifest/semantic-validation.js.map +1 -0
  97. package/dist/manifest/types.d.ts +263 -0
  98. package/dist/manifest/types.js +2 -0
  99. package/dist/manifest/types.js.map +1 -0
  100. package/dist/manifest/validate.d.ts +12 -0
  101. package/dist/manifest/validate.js +221 -0
  102. package/dist/manifest/validate.js.map +1 -0
  103. package/dist/output/assetPackManifest.d.ts +19 -0
  104. package/dist/output/assetPackManifest.js +20 -0
  105. package/dist/output/assetPackManifest.js.map +1 -0
  106. package/dist/output/catalog.d.ts +60 -0
  107. package/dist/output/catalog.js +107 -0
  108. package/dist/output/catalog.js.map +1 -0
  109. package/dist/output/contactSheet.d.ts +13 -0
  110. package/dist/output/contactSheet.js +124 -0
  111. package/dist/output/contactSheet.js.map +1 -0
  112. package/dist/output/phaserManifest.d.ts +8 -0
  113. package/dist/output/phaserManifest.js +25 -0
  114. package/dist/output/phaserManifest.js.map +1 -0
  115. package/dist/output/pixiManifest.d.ts +8 -0
  116. package/dist/output/pixiManifest.js +37 -0
  117. package/dist/output/pixiManifest.js.map +1 -0
  118. package/dist/output/provenance.d.ts +121 -0
  119. package/dist/output/provenance.js +10 -0
  120. package/dist/output/provenance.js.map +1 -0
  121. package/dist/output/runtimeManifests.d.ts +21 -0
  122. package/dist/output/runtimeManifests.js +82 -0
  123. package/dist/output/runtimeManifests.js.map +1 -0
  124. package/dist/output/unityImportManifest.d.ts +10 -0
  125. package/dist/output/unityImportManifest.js +58 -0
  126. package/dist/output/unityImportManifest.js.map +1 -0
  127. package/dist/output/zip.d.ts +5 -0
  128. package/dist/output/zip.js +68 -0
  129. package/dist/output/zip.js.map +1 -0
  130. package/dist/pipeline/atlas.d.ts +33 -0
  131. package/dist/pipeline/atlas.js +286 -0
  132. package/dist/pipeline/atlas.js.map +1 -0
  133. package/dist/pipeline/eval.d.ts +104 -0
  134. package/dist/pipeline/eval.js +246 -0
  135. package/dist/pipeline/eval.js.map +1 -0
  136. package/dist/pipeline/generate.d.ts +44 -0
  137. package/dist/pipeline/generate.js +1088 -0
  138. package/dist/pipeline/generate.js.map +1 -0
  139. package/dist/pipeline/package.d.ts +18 -0
  140. package/dist/pipeline/package.js +218 -0
  141. package/dist/pipeline/package.js.map +1 -0
  142. package/dist/pipeline/process.d.ts +15 -0
  143. package/dist/pipeline/process.js +776 -0
  144. package/dist/pipeline/process.js.map +1 -0
  145. package/dist/pipeline/review.d.ts +10 -0
  146. package/dist/pipeline/review.js +341 -0
  147. package/dist/pipeline/review.js.map +1 -0
  148. package/dist/pipeline/seamHeal.d.ts +2 -0
  149. package/dist/pipeline/seamHeal.js +70 -0
  150. package/dist/pipeline/seamHeal.js.map +1 -0
  151. package/dist/pipeline/select.d.ts +39 -0
  152. package/dist/pipeline/select.js +79 -0
  153. package/dist/pipeline/select.js.map +1 -0
  154. package/dist/providers/job.d.ts +29 -0
  155. package/dist/providers/job.js +113 -0
  156. package/dist/providers/job.js.map +1 -0
  157. package/dist/providers/localDiffusion.d.ts +28 -0
  158. package/dist/providers/localDiffusion.js +235 -0
  159. package/dist/providers/localDiffusion.js.map +1 -0
  160. package/dist/providers/nano.d.ts +36 -0
  161. package/dist/providers/nano.js +402 -0
  162. package/dist/providers/nano.js.map +1 -0
  163. package/dist/providers/openai.d.ts +37 -0
  164. package/dist/providers/openai.js +378 -0
  165. package/dist/providers/openai.js.map +1 -0
  166. package/dist/providers/policy.d.ts +9 -0
  167. package/dist/providers/policy.js +192 -0
  168. package/dist/providers/policy.js.map +1 -0
  169. package/dist/providers/prompt.d.ts +3 -0
  170. package/dist/providers/prompt.js +63 -0
  171. package/dist/providers/prompt.js.map +1 -0
  172. package/dist/providers/registry.d.ts +24 -0
  173. package/dist/providers/registry.js +92 -0
  174. package/dist/providers/registry.js.map +1 -0
  175. package/dist/providers/runtime.d.ts +15 -0
  176. package/dist/providers/runtime.js +101 -0
  177. package/dist/providers/runtime.js.map +1 -0
  178. package/dist/providers/runtimeConfig.d.ts +20 -0
  179. package/dist/providers/runtimeConfig.js +146 -0
  180. package/dist/providers/runtimeConfig.js.map +1 -0
  181. package/dist/providers/types-core.d.ts +514 -0
  182. package/dist/providers/types-core.js +60 -0
  183. package/dist/providers/types-core.js.map +1 -0
  184. package/dist/providers/types.d.ts +4 -0
  185. package/dist/providers/types.js +5 -0
  186. package/dist/providers/types.js.map +1 -0
  187. package/dist/service/generationRequest.d.ts +58 -0
  188. package/dist/service/generationRequest.js +203 -0
  189. package/dist/service/generationRequest.js.map +1 -0
  190. package/dist/service/providerCapabilities.d.ts +40 -0
  191. package/dist/service/providerCapabilities.js +114 -0
  192. package/dist/service/providerCapabilities.js.map +1 -0
  193. package/dist/service/server.d.ts +31 -0
  194. package/dist/service/server.js +774 -0
  195. package/dist/service/server.js.map +1 -0
  196. package/dist/shared/errors.d.ts +13 -0
  197. package/dist/shared/errors.js +24 -0
  198. package/dist/shared/errors.js.map +1 -0
  199. package/dist/shared/fs.d.ts +6 -0
  200. package/dist/shared/fs.js +30 -0
  201. package/dist/shared/fs.js.map +1 -0
  202. package/dist/shared/image.d.ts +25 -0
  203. package/dist/shared/image.js +136 -0
  204. package/dist/shared/image.js.map +1 -0
  205. package/dist/shared/paths.d.ts +30 -0
  206. package/dist/shared/paths.js +103 -0
  207. package/dist/shared/paths.js.map +1 -0
  208. package/dist/shared/schemas.d.ts +209 -0
  209. package/dist/shared/schemas.js +93 -0
  210. package/dist/shared/schemas.js.map +1 -0
  211. package/dist/shared/typeGuards.d.ts +1 -0
  212. package/dist/shared/typeGuards.js +4 -0
  213. package/dist/shared/typeGuards.js.map +1 -0
  214. package/dist/shared/zod.d.ts +1 -0
  215. package/dist/shared/zod.js +14 -0
  216. package/dist/shared/zod.js.map +1 -0
  217. package/dist/showcase/format.d.ts +9 -0
  218. package/dist/showcase/format.js +61 -0
  219. package/dist/showcase/format.js.map +1 -0
  220. package/dist/showcase/panelRenderer.d.ts +59 -0
  221. package/dist/showcase/panelRenderer.js +294 -0
  222. package/dist/showcase/panelRenderer.js.map +1 -0
  223. package/dist/showcase/releaseConfig.d.ts +233 -0
  224. package/dist/showcase/releaseConfig.js +75 -0
  225. package/dist/showcase/releaseConfig.js.map +1 -0
  226. package/dist/showcase/releaseEvidence.d.ts +25 -0
  227. package/dist/showcase/releaseEvidence.js +540 -0
  228. package/dist/showcase/releaseEvidence.js.map +1 -0
  229. package/dist/showcase/releaseEvidenceSchema.d.ts +1611 -0
  230. package/dist/showcase/releaseEvidenceSchema.js +165 -0
  231. package/dist/showcase/releaseEvidenceSchema.js.map +1 -0
  232. package/dist/showcase/scenarioRenderer.d.ts +19 -0
  233. package/dist/showcase/scenarioRenderer.js +488 -0
  234. package/dist/showcase/scenarioRenderer.js.map +1 -0
  235. package/docs/ADAPTER_CONTRACT.md +141 -0
  236. package/docs/ENGINE_TARGETING.md +86 -0
  237. package/docs/MANIFEST_POLICY_COVERAGE.md +130 -0
  238. package/docs/RELEASE_WORKFLOW.md +117 -0
  239. package/docs/ROADMAP.md +411 -0
  240. package/docs/ROADMAP_ISSUES.md +244 -0
  241. package/docs/SERVICE_MODE.md +137 -0
  242. package/docs/manifest-schema.md +254 -0
  243. package/package.json +70 -0
@@ -0,0 +1,165 @@
1
+ import { z } from "zod";
2
+ import { nonEmptyString } from "../shared/schemas.js";
3
+ export const numberOrNullSchema = z.number().nullable();
4
+ export const providerQualitySchema = z.object({
5
+ targetCount: z.number().int().min(0),
6
+ passedHardGates: z.number().int().min(0),
7
+ failedHardGates: z.number().int().min(0),
8
+ hardErrors: z.number().int().min(0),
9
+ passRate: numberOrNullSchema,
10
+ finalScoreMean: numberOrNullSchema,
11
+ finalScoreMedian: numberOrNullSchema,
12
+ candidateScoreMean: numberOrNullSchema,
13
+ });
14
+ export const providerCoarseToFineSchema = z.object({
15
+ enabledJobs: z.number().int().min(0),
16
+ promotedCandidates: z.number().int().min(0),
17
+ discardedCandidates: z.number().int().min(0),
18
+ skippedJobs: z.number().int().min(0),
19
+ });
20
+ export const providerAgenticRetrySchema = z.object({
21
+ enabledJobs: z.number().int().min(0),
22
+ attemptedJobs: z.number().int().min(0),
23
+ attemptedTotal: z.number().int().min(0),
24
+ succeededJobs: z.number().int().min(0),
25
+ triggerCodes: z.record(z.number().int().min(0)),
26
+ });
27
+ export const providerPackInvariantsSchema = z.object({
28
+ errors: z.number().int().min(0),
29
+ warnings: z.number().int().min(0),
30
+ issueCodes: z.array(nonEmptyString),
31
+ });
32
+ export const providerConsistencySchema = z.object({
33
+ groups: z.number().int().min(0),
34
+ warningTargets: z.number().int().min(0),
35
+ outlierTargets: z.number().int().min(0),
36
+ totalPenalty: z.number().int().min(0),
37
+ maxScore: numberOrNullSchema,
38
+ });
39
+ export const providerStylePolicySchema = z.object({
40
+ errorCount: z.number().int().min(0),
41
+ warningCount: z.number().int().min(0),
42
+ codes: z.array(nonEmptyString),
43
+ });
44
+ export const providerSpritesheetSchema = z.object({
45
+ issueCount: z.number().int().min(0),
46
+ animationsMeasured: z.number().int().min(0),
47
+ maxSilhouetteDrift: numberOrNullSchema,
48
+ maxAnchorDrift: numberOrNullSchema,
49
+ maxIdentityDrift: numberOrNullSchema,
50
+ maxPoseDrift: numberOrNullSchema,
51
+ });
52
+ export const providerFeatureSchema = z.object({
53
+ coarseToFine: providerCoarseToFineSchema,
54
+ agenticRetry: providerAgenticRetrySchema,
55
+ packInvariants: providerPackInvariantsSchema,
56
+ consistencyOutliers: providerConsistencySchema,
57
+ stylePolicy: providerStylePolicySchema,
58
+ spritesheetDrift: providerSpritesheetSchema,
59
+ });
60
+ export const providerEvidenceSchema = z.object({
61
+ id: nonEmptyString,
62
+ label: nonEmptyString,
63
+ provider: nonEmptyString,
64
+ manifestPath: nonEmptyString,
65
+ outDir: nonEmptyString,
66
+ generatedAt: nonEmptyString,
67
+ quality: providerQualitySchema,
68
+ features: providerFeatureSchema,
69
+ });
70
+ export const aggregateQualitySchema = z.object({
71
+ providerCount: z.number().int().min(0),
72
+ targetCount: z.number().int().min(0),
73
+ passedHardGates: z.number().int().min(0),
74
+ failedHardGates: z.number().int().min(0),
75
+ hardErrors: z.number().int().min(0),
76
+ passRate: numberOrNullSchema,
77
+ finalScoreMean: numberOrNullSchema,
78
+ finalScoreMedian: numberOrNullSchema,
79
+ });
80
+ export const aggregateFeaturesSchema = z.object({
81
+ coarseToFineEnabledJobs: z.number().int().min(0),
82
+ coarseToFinePromotedCandidates: z.number().int().min(0),
83
+ coarseToFineDiscardedCandidates: z.number().int().min(0),
84
+ agenticRetryAttemptedJobs: z.number().int().min(0),
85
+ agenticRetryAttempts: z.number().int().min(0),
86
+ packInvariantErrors: z.number().int().min(0),
87
+ packInvariantWarnings: z.number().int().min(0),
88
+ consistencyOutlierTargets: z.number().int().min(0),
89
+ stylePolicyErrors: z.number().int().min(0),
90
+ spritesheetIssueCount: z.number().int().min(0),
91
+ });
92
+ export const scenarioValueSchema = z.object({
93
+ label: nonEmptyString,
94
+ value: nonEmptyString,
95
+ });
96
+ export const scenarioEvidenceSchema = z.object({
97
+ id: nonEmptyString,
98
+ title: nonEmptyString,
99
+ panelFile: nonEmptyString,
100
+ summary: nonEmptyString,
101
+ values: z.array(scenarioValueSchema),
102
+ });
103
+ export const providerComparisonSchema = z.object({
104
+ id: nonEmptyString,
105
+ leftId: nonEmptyString,
106
+ rightId: nonEmptyString,
107
+ finalScoreMeanDelta: numberOrNullSchema,
108
+ passRateDelta: numberOrNullSchema,
109
+ hardErrorDelta: z.number().int(),
110
+ });
111
+ export const baselineDeltaSchema = z.object({
112
+ baselineVersion: nonEmptyString,
113
+ finalScoreMeanDelta: numberOrNullSchema,
114
+ passRateDelta: numberOrNullSchema,
115
+ hardErrorDelta: z.number().int(),
116
+ });
117
+ const SCHEMA_VERSION = "release-showcase-v2";
118
+ export const ReleaseEvidenceReportSchema = z.object({
119
+ schemaVersion: z.literal(SCHEMA_VERSION),
120
+ generatedAt: nonEmptyString,
121
+ release: z.object({
122
+ version: nonEmptyString,
123
+ codename: nonEmptyString,
124
+ comparisonPolicy: nonEmptyString,
125
+ }),
126
+ baseline: z
127
+ .object({
128
+ version: nonEmptyString,
129
+ evidencePath: nonEmptyString.optional(),
130
+ available: z.boolean(),
131
+ })
132
+ .optional(),
133
+ providers: z.array(providerEvidenceSchema),
134
+ aggregate: z.object({
135
+ quality: aggregateQualitySchema,
136
+ features: aggregateFeaturesSchema,
137
+ }),
138
+ comparisons: z.object({
139
+ providerPairs: z.array(providerComparisonSchema).default([]),
140
+ baselineDelta: baselineDeltaSchema.optional(),
141
+ }),
142
+ scenarios: z.array(scenarioEvidenceSchema),
143
+ });
144
+ /** Read v1 or v2 reports, normalizing v1 to v2. */
145
+ export function normalizeReportSchema(raw) {
146
+ const obj = raw;
147
+ if (obj.schemaVersion === "release-showcase-v1") {
148
+ const comparisons = obj.comparisons;
149
+ const providerPairs = [];
150
+ if (comparisons?.openaiVsNano) {
151
+ const pair = comparisons.openaiVsNano;
152
+ providerPairs.push({ id: "openai-vs-nano", ...pair });
153
+ }
154
+ return {
155
+ ...obj,
156
+ schemaVersion: SCHEMA_VERSION,
157
+ comparisons: {
158
+ providerPairs,
159
+ baselineDelta: comparisons?.baselineDelta,
160
+ },
161
+ };
162
+ }
163
+ return raw;
164
+ }
165
+ //# sourceMappingURL=releaseEvidenceSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"releaseEvidenceSchema.js","sourceRoot":"","sources":["../../src/showcase/releaseEvidenceSchema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type ResolvedReleaseShowcaseConfig } from "./releaseConfig.js";
2
+ import { type ReleaseEvidenceReport } from "./releaseEvidenceSchema.js";
3
+ export interface PanelManifest {
4
+ generatedAt: string;
5
+ releaseVersion: string;
6
+ panelCount: number;
7
+ panels: {
8
+ id: string;
9
+ title: string;
10
+ file: string;
11
+ }[];
12
+ }
13
+ export declare function renderScenarioPanels(params: {
14
+ config: ResolvedReleaseShowcaseConfig;
15
+ report: ReleaseEvidenceReport;
16
+ baselineReport?: ReleaseEvidenceReport;
17
+ destDir: string;
18
+ repoRoot: string;
19
+ }): Promise<PanelManifest>;
@@ -0,0 +1,488 @@
1
+ import { readFile, mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import sharp from "sharp";
4
+ import { cardImage, renderSideBySide, renderGallery, renderFeatureShowcase, resolveImageOrPlaceholder, makePlaceholderImage, tilePreview, } from "./panelRenderer.js";
5
+ // ---------------------------------------------------------------------------
6
+ // Scenario registry
7
+ // ---------------------------------------------------------------------------
8
+ const SCENARIO_RENDERERS = {
9
+ "quality-delta": renderQualityDelta,
10
+ "provider-openai-vs-nano": renderProviderComparison,
11
+ "provider-comparison": renderProviderComparison,
12
+ "spritesheet-drift-qa": renderSpritesheetDrift,
13
+ "style-bible-policy": renderStylePolicy,
14
+ "coarse-to-fine": renderCoarseToFine,
15
+ "agentic-retry": renderAgenticRetry,
16
+ "pack-invariants": renderPackInvariants,
17
+ "consistency-outliers": renderConsistencyOutliers,
18
+ };
19
+ // ---------------------------------------------------------------------------
20
+ // Main entry point
21
+ // ---------------------------------------------------------------------------
22
+ export async function renderScenarioPanels(params) {
23
+ const { config, report, baselineReport, destDir, repoRoot } = params;
24
+ await mkdir(destDir, { recursive: true });
25
+ const ctx = { config, report, baselineReport, repoRoot };
26
+ const scenarioConfigById = new Map(config.scenarios.map((s) => [s.id, s]));
27
+ const manifest = {
28
+ generatedAt: new Date().toISOString(),
29
+ releaseVersion: config.releaseVersion,
30
+ panelCount: 0,
31
+ panels: [],
32
+ };
33
+ for (let index = 0; index < config.readmePanels.length; index += 1) {
34
+ const panelId = config.readmePanels[index];
35
+ const scenarioConfig = scenarioConfigById.get(panelId);
36
+ if (!scenarioConfig)
37
+ continue;
38
+ const outPath = path.join(destDir, scenarioConfig.panelFile);
39
+ const subtitle = buildSubtitle(scenarioConfig.description, index + 1, config.readmePanels.length);
40
+ const renderer = SCENARIO_RENDERERS[panelId] ?? renderDefaultGallery;
41
+ process.stdout.write(`[render] ${panelId} -> ${scenarioConfig.panelFile}\n`);
42
+ await renderer(ctx, outPath, scenarioConfig.title, subtitle);
43
+ manifest.panels.push({
44
+ id: panelId,
45
+ title: scenarioConfig.title,
46
+ file: scenarioConfig.panelFile,
47
+ });
48
+ }
49
+ manifest.panelCount = manifest.panels.length;
50
+ await writeFile(path.join(destDir, "panel-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
51
+ return manifest;
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Helpers
55
+ // ---------------------------------------------------------------------------
56
+ function buildSubtitle(description, panelIndex, panelCount) {
57
+ const desc = description ?? "Release showcase evidence";
58
+ return `Panel ${panelIndex}/${panelCount} - ${desc}`;
59
+ }
60
+ function providerImagesDir(outDir) {
61
+ return path.join(outDir, "assets", "imagegen", "processed", "images");
62
+ }
63
+ function primaryProvider(report) {
64
+ return report.providers.reduce((best, p) => (!best || p.quality.targetCount > best.quality.targetCount ? p : best), undefined);
65
+ }
66
+ function canonicalProvider(config, report) {
67
+ return (report.providers.find((provider) => provider.id === config.canonicalRunId) ??
68
+ primaryProvider(report));
69
+ }
70
+ function providerWithHighestFeatureScore(providers, scoreFor) {
71
+ return providers.reduce((best, provider) => (!best || scoreFor(provider) > scoreFor(best) ? provider : best), undefined);
72
+ }
73
+ function providerWithCoarseToFineTrace(config, report) {
74
+ const traced = providerWithHighestFeatureScore(report.providers, (provider) => {
75
+ const feature = provider.features.coarseToFine;
76
+ return (feature.enabledJobs * 100 + feature.promotedCandidates * 10 + feature.discardedCandidates);
77
+ });
78
+ if (!traced || traced.features.coarseToFine.enabledJobs === 0) {
79
+ return canonicalProvider(config, report);
80
+ }
81
+ return traced;
82
+ }
83
+ function providerWithAgenticRetryTrace(config, report) {
84
+ const traced = providerWithHighestFeatureScore(report.providers, (provider) => {
85
+ const feature = provider.features.agenticRetry;
86
+ return feature.attemptedJobs * 100 + feature.attemptedTotal * 10 + feature.enabledJobs;
87
+ });
88
+ if (!traced || traced.features.agenticRetry.attemptedJobs === 0) {
89
+ return canonicalProvider(config, report);
90
+ }
91
+ return traced;
92
+ }
93
+ function findProviderById(report, id) {
94
+ return report.providers.find((p) => p.id === id);
95
+ }
96
+ const SHOWCASE_TARGETS = ["hero-idle", "dungeon-tile", "ui-relic"];
97
+ /** Maps 0.3.0 target names to prior-version equivalents for baseline resolution. */
98
+ const BASELINE_TARGET_ALIASES = {
99
+ "ui-relic": ["ui-relic-icon"],
100
+ };
101
+ // ---------------------------------------------------------------------------
102
+ // Scenario: quality-delta (side-by-side)
103
+ // ---------------------------------------------------------------------------
104
+ async function renderQualityDelta(ctx, outPath, title, subtitle) {
105
+ const canonical = canonicalProvider(ctx.config, ctx.report);
106
+ if (!canonical) {
107
+ await renderPlaceholderPanel(outPath, title, subtitle);
108
+ return;
109
+ }
110
+ const currentImages = providerImagesDir(canonical.outDir);
111
+ const baselineProvider = ctx.baselineReport ? primaryProvider(ctx.baselineReport) : undefined;
112
+ const baselineImages = baselineProvider ? providerImagesDir(baselineProvider.outDir) : undefined;
113
+ const baselineFallbackDir = ctx.report.baseline?.version
114
+ ? path.join(ctx.repoRoot, "docs", "showcase", ctx.report.baseline.version)
115
+ : undefined;
116
+ const pairs = [];
117
+ for (const targetId of SHOWCASE_TARGETS) {
118
+ const baselineCandidates = [];
119
+ const aliases = [targetId, ...(BASELINE_TARGET_ALIASES[targetId] ?? [])];
120
+ for (const alias of aliases) {
121
+ if (baselineImages)
122
+ baselineCandidates.push(path.join(baselineImages, `${alias}.png`));
123
+ if (baselineFallbackDir)
124
+ baselineCandidates.push(path.join(baselineFallbackDir, `${alias}.png`));
125
+ }
126
+ pairs.push({
127
+ label: targetId,
128
+ leftImage: await resolveImageOrPlaceholder(baselineCandidates, `${ctx.report.baseline?.version ?? "Baseline"} unavailable`),
129
+ rightImage: await resolveImageOrPlaceholder([path.join(currentImages, `${targetId}.png`)], `${ctx.config.releaseVersion} unavailable`),
130
+ });
131
+ }
132
+ await renderSideBySide({
133
+ title,
134
+ subtitle,
135
+ pairs,
136
+ leftLabel: ctx.report.baseline?.version ?? "Baseline",
137
+ rightLabel: ctx.config.releaseVersion,
138
+ outPath,
139
+ });
140
+ }
141
+ // ---------------------------------------------------------------------------
142
+ // Scenario: provider comparison (side-by-side)
143
+ // ---------------------------------------------------------------------------
144
+ async function renderProviderComparison(ctx, outPath, title, subtitle) {
145
+ if (ctx.report.comparisons.providerPairs.length === 0) {
146
+ await renderDefaultGallery(ctx, outPath, title, subtitle);
147
+ return;
148
+ }
149
+ const pair = ctx.report.comparisons.providerPairs[0];
150
+ const leftProvider = findProviderById(ctx.report, pair.leftId);
151
+ const rightProvider = findProviderById(ctx.report, pair.rightId);
152
+ if (!leftProvider || !rightProvider) {
153
+ await renderDefaultGallery(ctx, outPath, title, subtitle);
154
+ return;
155
+ }
156
+ const leftImages = providerImagesDir(leftProvider.outDir);
157
+ const rightImages = providerImagesDir(rightProvider.outDir);
158
+ const pairs = [];
159
+ for (const targetId of SHOWCASE_TARGETS) {
160
+ pairs.push({
161
+ label: targetId,
162
+ leftImage: await resolveImageOrPlaceholder([path.join(leftImages, `${targetId}.png`)], `${leftProvider.label} unavailable`),
163
+ rightImage: await resolveImageOrPlaceholder([path.join(rightImages, `${targetId}.png`)], `${rightProvider.label} unavailable`),
164
+ });
165
+ }
166
+ await renderSideBySide({
167
+ title,
168
+ subtitle,
169
+ pairs,
170
+ leftLabel: leftProvider.label,
171
+ rightLabel: rightProvider.label,
172
+ outPath,
173
+ });
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // Scenario: spritesheet drift (gallery)
177
+ // ---------------------------------------------------------------------------
178
+ async function renderSpritesheetDrift(ctx, outPath, title, subtitle) {
179
+ const canonical = canonicalProvider(ctx.config, ctx.report);
180
+ if (!canonical) {
181
+ await renderPlaceholderPanel(outPath, title, subtitle);
182
+ return;
183
+ }
184
+ const imagesDir = providerImagesDir(canonical.outDir);
185
+ const framesDir = path.join(imagesDir, "__frames", "hero-sheet");
186
+ await renderGallery({
187
+ title,
188
+ subtitle,
189
+ cardHeightScale: 1.3,
190
+ items: [
191
+ {
192
+ label: "Seed Reference",
193
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, "hero-idle.png")], "Seed image unavailable"),
194
+ },
195
+ {
196
+ label: "Walk Frame 1",
197
+ image: await resolveImageOrPlaceholder([path.join(framesDir, "hero-sheet__walk_00.png")], "Walk frame unavailable"),
198
+ },
199
+ {
200
+ label: "Walk Frame 2",
201
+ image: await resolveImageOrPlaceholder([path.join(framesDir, "hero-sheet__walk_01.png")], "Walk frame unavailable"),
202
+ },
203
+ {
204
+ label: "Walk Frame 3",
205
+ image: await resolveImageOrPlaceholder([path.join(framesDir, "hero-sheet__walk_02.png")], "Walk frame unavailable"),
206
+ },
207
+ ],
208
+ outPath,
209
+ });
210
+ }
211
+ // ---------------------------------------------------------------------------
212
+ // Scenario: style policy (gallery)
213
+ // ---------------------------------------------------------------------------
214
+ async function renderStylePolicy(ctx, outPath, title, subtitle) {
215
+ const canonical = canonicalProvider(ctx.config, ctx.report);
216
+ if (!canonical) {
217
+ await renderPlaceholderPanel(outPath, title, subtitle);
218
+ return;
219
+ }
220
+ const imagesDir = providerImagesDir(canonical.outDir);
221
+ await renderGallery({
222
+ title,
223
+ subtitle,
224
+ items: [
225
+ {
226
+ label: "Hero Sprite",
227
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, "hero-idle.png")], "Hero unavailable"),
228
+ },
229
+ {
230
+ label: "Dungeon Tile",
231
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, "dungeon-tile.png")], "Tile unavailable"),
232
+ },
233
+ {
234
+ label: "UI Icon",
235
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, "ui-relic.png")], "UI relic unavailable"),
236
+ },
237
+ {
238
+ label: "Spritesheet",
239
+ image: await renderWalkStripPreview(imagesDir),
240
+ },
241
+ ],
242
+ outPath,
243
+ });
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // Scenario: coarse-to-fine (feature showcase)
247
+ // ---------------------------------------------------------------------------
248
+ async function renderCoarseToFine(ctx, outPath, title, subtitle) {
249
+ const traced = providerWithCoarseToFineTrace(ctx.config, ctx.report);
250
+ if (!traced) {
251
+ await renderPlaceholderPanel(outPath, title, subtitle);
252
+ return;
253
+ }
254
+ const resolved = await resolveCoarseToFineImages(traced.outDir);
255
+ await renderFeatureShowcase({
256
+ title,
257
+ subtitle,
258
+ ...resolved,
259
+ outPath,
260
+ });
261
+ }
262
+ // ---------------------------------------------------------------------------
263
+ // Scenario: agentic retry (feature showcase)
264
+ // ---------------------------------------------------------------------------
265
+ async function renderAgenticRetry(ctx, outPath, title, subtitle) {
266
+ const traced = providerWithAgenticRetryTrace(ctx.config, ctx.report);
267
+ if (!traced) {
268
+ await renderPlaceholderPanel(outPath, title, subtitle);
269
+ return;
270
+ }
271
+ const resolved = await resolveAgenticRetryImages(traced.outDir);
272
+ await renderFeatureShowcase({
273
+ title,
274
+ subtitle,
275
+ ...resolved,
276
+ outPath,
277
+ });
278
+ }
279
+ // ---------------------------------------------------------------------------
280
+ // Scenario: pack invariants (gallery)
281
+ // ---------------------------------------------------------------------------
282
+ async function renderPackInvariants(ctx, outPath, title, subtitle) {
283
+ const canonical = canonicalProvider(ctx.config, ctx.report);
284
+ if (!canonical) {
285
+ await renderPlaceholderPanel(outPath, title, subtitle);
286
+ return;
287
+ }
288
+ const imagesDir = providerImagesDir(canonical.outDir);
289
+ const tile = await resolveImageOrPlaceholder([path.join(imagesDir, "dungeon-tile.png")], "Tile unavailable");
290
+ const tileGrid = await tilePreview(tile, 6, 6, 80);
291
+ await renderGallery({
292
+ title,
293
+ subtitle,
294
+ items: [
295
+ { label: "Tile Repeat Grid", image: tileGrid },
296
+ {
297
+ label: "Sheet Continuity",
298
+ image: await renderWalkStripPreview(imagesDir),
299
+ },
300
+ {
301
+ label: "Sprite Uniqueness",
302
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, "hero-idle.png")], "Hero unavailable"),
303
+ },
304
+ {
305
+ label: "Icon Uniqueness",
306
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, "ui-relic.png")], "Relic unavailable"),
307
+ },
308
+ ],
309
+ outPath,
310
+ });
311
+ }
312
+ // ---------------------------------------------------------------------------
313
+ // Scenario: consistency outliers (gallery)
314
+ // ---------------------------------------------------------------------------
315
+ async function renderConsistencyOutliers(ctx, outPath, title, subtitle) {
316
+ const canonical = canonicalProvider(ctx.config, ctx.report);
317
+ if (!canonical) {
318
+ await renderPlaceholderPanel(outPath, title, subtitle);
319
+ return;
320
+ }
321
+ const imagesDir = providerImagesDir(canonical.outDir);
322
+ const items = [];
323
+ for (const targetId of SHOWCASE_TARGETS) {
324
+ items.push({
325
+ label: targetId,
326
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, `${targetId}.png`)], `${targetId} unavailable`),
327
+ });
328
+ }
329
+ await renderGallery({ title, subtitle, items, outPath });
330
+ }
331
+ // ---------------------------------------------------------------------------
332
+ // Default fallback (gallery of showcase targets)
333
+ // ---------------------------------------------------------------------------
334
+ async function renderDefaultGallery(ctx, outPath, title, subtitle) {
335
+ const canonical = canonicalProvider(ctx.config, ctx.report);
336
+ if (!canonical) {
337
+ await renderPlaceholderPanel(outPath, title, subtitle);
338
+ return;
339
+ }
340
+ const imagesDir = providerImagesDir(canonical.outDir);
341
+ const items = [];
342
+ for (const targetId of SHOWCASE_TARGETS) {
343
+ items.push({
344
+ label: targetId,
345
+ image: await resolveImageOrPlaceholder([path.join(imagesDir, `${targetId}.png`)], `${targetId} unavailable`),
346
+ });
347
+ }
348
+ await renderGallery({ title, subtitle, items, outPath });
349
+ }
350
+ // ---------------------------------------------------------------------------
351
+ // Placeholder panel for zero-provider edge case
352
+ // ---------------------------------------------------------------------------
353
+ async function renderPlaceholderPanel(outPath, title, subtitle) {
354
+ const placeholder = await makePlaceholderImage("No provider data available");
355
+ await renderGallery({
356
+ title,
357
+ subtitle,
358
+ items: [{ label: "No data", image: placeholder }],
359
+ outPath,
360
+ });
361
+ }
362
+ async function readProvenanceJobs(outDir) {
363
+ try {
364
+ const raw = await readFile(path.join(outDir, "provenance", "run.json"), "utf8");
365
+ const parsed = JSON.parse(raw);
366
+ return parsed.jobs ?? [];
367
+ }
368
+ catch {
369
+ return [];
370
+ }
371
+ }
372
+ async function resolveCoarseToFineImages(outDir) {
373
+ const jobs = await readProvenanceJobs(outDir);
374
+ for (const job of jobs) {
375
+ const coarse = job.coarseToFine;
376
+ if (!coarse)
377
+ continue;
378
+ const promoted = coarse.promoted?.[0];
379
+ const discarded = coarse.discarded?.[0];
380
+ // Show the two raw draft candidates side-by-side so the viewer sees two
381
+ // distinct generations and understands which one the scorer promoted.
382
+ // We intentionally show the raw drafts (not the refined output) because
383
+ // they have visible composition differences that demonstrate selection.
384
+ if (promoted?.outputPath && discarded?.outputPath) {
385
+ const fmtScore = (s) => s != null ? ` (${Math.round(s).toLocaleString()})` : "";
386
+ return {
387
+ beforeImage: await resolveImageOrPlaceholder([discarded.outputPath], "No discarded draft"),
388
+ afterImage: await resolveImageOrPlaceholder([promoted.outputPath], "No promoted draft"),
389
+ beforeLabel: `Rejected Draft${fmtScore(discarded.score)}`,
390
+ afterLabel: `Promoted Draft${fmtScore(promoted.score)}`,
391
+ };
392
+ }
393
+ // Only promoted available -- fall back to promoted vs placeholder
394
+ const fallbackPath = promoted?.refinedOutputPath ?? promoted?.outputPath;
395
+ if (fallbackPath) {
396
+ return {
397
+ beforeImage: await resolveImageOrPlaceholder(discarded?.outputPath ? [discarded.outputPath] : [], "No discarded draft"),
398
+ afterImage: await resolveImageOrPlaceholder([fallbackPath], "No promoted candidate"),
399
+ beforeLabel: "Draft (Discarded)",
400
+ afterLabel: "Promoted (Selected)",
401
+ };
402
+ }
403
+ // All candidates discarded -- show the two best discarded side-by-side
404
+ const allDiscarded = coarse.discarded ?? [];
405
+ if (allDiscarded.length >= 2) {
406
+ const sorted = [...allDiscarded].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
407
+ const fmtScore = (s) => s != null ? ` (${Math.round(s).toLocaleString()})` : "";
408
+ return {
409
+ beforeImage: await resolveImageOrPlaceholder(sorted[1].outputPath ? [sorted[1].outputPath] : [], "No draft available"),
410
+ afterImage: await resolveImageOrPlaceholder(sorted[0].outputPath ? [sorted[0].outputPath] : [], "No draft available"),
411
+ beforeLabel: `Rejected #2${fmtScore(sorted[1].score)}`,
412
+ afterLabel: `Rejected #1${fmtScore(sorted[0].score)}`,
413
+ };
414
+ }
415
+ if (allDiscarded.length === 1 && allDiscarded[0].outputPath) {
416
+ const fmtScore = (s) => s != null ? ` (${Math.round(s).toLocaleString()})` : "";
417
+ return {
418
+ beforeImage: await resolveImageOrPlaceholder([allDiscarded[0].outputPath], "No draft available"),
419
+ afterImage: await makePlaceholderImage("None promoted"),
420
+ beforeLabel: `Rejected${fmtScore(allDiscarded[0].score)}`,
421
+ afterLabel: "None Promoted",
422
+ };
423
+ }
424
+ }
425
+ const placeholder = await resolveImageOrPlaceholder([], "No coarse-to-fine trace");
426
+ return {
427
+ beforeImage: placeholder,
428
+ afterImage: placeholder,
429
+ beforeLabel: "Draft (Discarded)",
430
+ afterLabel: "Promoted (Selected)",
431
+ };
432
+ }
433
+ async function renderWalkStripPreview(imagesDir) {
434
+ const framesDir = path.join(imagesDir, "__frames", "hero-sheet");
435
+ const frameImages = await Promise.all([0, 1, 2].map((index) => resolveImageOrPlaceholder([path.join(framesDir, `hero-sheet__walk_${String(index).padStart(2, "0")}.png`)], "Walk frame unavailable")));
436
+ const tileWidth = 118;
437
+ const tileHeight = 220;
438
+ const padding = 12;
439
+ const gap = 8;
440
+ const width = padding * 2 + tileWidth * frameImages.length + gap * (frameImages.length - 1);
441
+ const height = padding * 2 + tileHeight;
442
+ const composites = await Promise.all(frameImages.map(async (image, index) => ({
443
+ input: await cardImage(image, tileWidth, tileHeight),
444
+ left: padding + index * (tileWidth + gap),
445
+ top: padding,
446
+ })));
447
+ return sharp({
448
+ create: {
449
+ width,
450
+ height,
451
+ channels: 4,
452
+ background: { r: 9, g: 7, b: 14, alpha: 1 },
453
+ },
454
+ })
455
+ .composite(composites)
456
+ .png({ compressionLevel: 9 })
457
+ .toBuffer();
458
+ }
459
+ async function resolveAgenticRetryImages(outDir) {
460
+ const jobs = await readProvenanceJobs(outDir);
461
+ const fmtScore = (s) => s != null ? ` (${Math.round(s).toLocaleString()})` : "";
462
+ for (const job of jobs) {
463
+ const retry = job.agenticRetry;
464
+ if (!retry?.enabled || !retry.attempts?.length)
465
+ continue;
466
+ const attempt = retry.attempts[0];
467
+ // Use retryOutputPath (the actual retry candidate) rather than outputPath
468
+ // (which is the re-selected best candidate and may be the same as source
469
+ // when the retry didn't improve the score).
470
+ const retryImage = attempt.retryOutputPath ?? attempt.outputPath;
471
+ if (attempt.sourceOutputPath && retryImage) {
472
+ return {
473
+ beforeImage: await resolveImageOrPlaceholder([attempt.sourceOutputPath], "Original unavailable"),
474
+ afterImage: await resolveImageOrPlaceholder([retryImage], "Corrected unavailable"),
475
+ beforeLabel: `Original${fmtScore(attempt.scoreBefore)}`,
476
+ afterLabel: `Auto-Corrected${fmtScore(attempt.scoreAfter)}`,
477
+ };
478
+ }
479
+ }
480
+ const placeholder = await resolveImageOrPlaceholder([], "No agentic retry trace");
481
+ return {
482
+ beforeImage: placeholder,
483
+ afterImage: placeholder,
484
+ beforeLabel: "Original",
485
+ afterLabel: "Auto-Corrected",
486
+ };
487
+ }
488
+ //# sourceMappingURL=scenarioRenderer.js.map