diff --git a/packages/core/src/__tests__/if-export-html.test.ts b/packages/core/src/__tests__/if-export-html.test.ts index 85824cc6..7c76dcab 100644 --- a/packages/core/src/__tests__/if-export-html.test.ts +++ b/packages/core/src/__tests__/if-export-html.test.ts @@ -44,6 +44,24 @@ describe("buildPlayableHtml", () => { expect(html).toContain('<script>BAD</script>'); }); + // Determinism parity: relational ops must Number()-coerce both operands (mirrors evaluator.ts) + it("player evalCond uses Number() coercion for relational ops (parity with evaluator)", () => { + const html = buildPlayableHtml(graph); + // All four relational ops must use Number(v) and Number(c.value) + expect(html).toMatch(/Number\(v\)\s*>=\s*Number\(c\.value\)/); + expect(html).toMatch(/Number\(v\)\s*<=\s*Number\(c\.value\)/); + expect(html).toMatch(/Number\(v\)\s*>\s*Number\(c\.value\)/); + expect(html).toMatch(/Number\(v\)\s*<\s*Number\(c\.value\)/); + // Raw lexical comparison must not appear for relational ops + expect(html).not.toMatch(/return v>=c\.value/); + expect(html).not.toMatch(/return v<=c\.value/); + expect(html).not.toMatch(/return v>c\.value/); + expect(html).not.toMatch(/return v { const xssGraph = StoryGraphSchema.parse({ diff --git a/packages/core/src/interactive-film/export-html.ts b/packages/core/src/interactive-film/export-html.ts index 518cc155..064b133d 100644 --- a/packages/core/src/interactive-film/export-html.ts +++ b/packages/core/src/interactive-film/export-html.ts @@ -8,9 +8,9 @@ const PLAYER_JS = String.raw` var nodeById = {}; (GRAPH.nodes||[]).forEach(function(n){ nodeById[n.id] = n; }); var endingByNode = {}; (GRAPH.endings||[]).forEach(function(e){ endingByNode[e.nodeId] = e; }); function evalCond(c){ if(!c) return true; var v = vars[c.var]; - switch(c.op){ case ">=": return v>=c.value; case "<=": return v<=c.value; case ">": return v>c.value; case "<": return v=": return Number(v)>=Number(c.value); case "<=": return Number(v)<=Number(c.value); case ">": return Number(v)>Number(c.value); case "<": return Number(v)'+s+'' : ''; } diff --git a/packages/studio/src/api/server.ts b/packages/studio/src/api/server.ts index 9d79401f..3e39cb01 100644 --- a/packages/studio/src/api/server.ts +++ b/packages/studio/src/api/server.ts @@ -5270,8 +5270,8 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string, o const file = resolveProjectImageFile(root, ref); const buf = await readFile(file.resolved); assetDataUris[ref] = `data:${file.contentType};base64,${buf.toString("base64")}`; - } catch { - /* missing or unreadable image โ€” skip this assetRef, bundle is still generated */ + } catch (err) { + console.warn(`[studio] export/html: skipping assetRef "${ref}" โ€”`, err); } } return new Response(buildPlayableHtml(graph, { assetDataUris }), {