{
  "format": "relay-web-workflow",
  "version": 1,
  "title": "One-Line Pavilion · L-System 3D · Ground to Ground",
  "request": "Rewrite a chord in its own 3D frame. The rotations carry the same geometric family from the large folds to the small ones. Sweep a single 100 mm tube through the four shared corner contacts on a 6 × 6 m grid.",
  "workflow": {
    "schema_version": 1,
    "kind": "workflow",
    "inputs": {},
    "tasks": [
      {
        "schema_version": 3,
        "id": "pavilion",
        "operation": "code.javascript",
        "objective": "Rewrite a chord in its own 3D frame. The rotations carry the same geometric family from the large folds to the small ones.",
        "inputs": {
          "code": {
            "value": "function run(inputs, controls) {\n  const trace = inputs?.capture ? [] : null;\n  const path = [];\n  const feet = [[-3,-3,0],[3,-3,0],[3,3,0],[-3,3,0]];\n  const clamp = (n, low, high) => Math.max(low, Math.min(high, n));\n  const mix = (a, b, t) => a + (b - a) * t;\n  const add = (a, b) => a.map((v, i) => v + b[i]);\n  const scale = (a, n) => a.map(v => v * n);\n  const sub = (a, b) => add(a, scale(b, -1));\n  const dot = (a, b) => a.reduce((sum, v, i) => sum + v * b[i], 0);\n  const length = v => Math.hypot(...v);\n  const unit = a => scale(a, 1 / Math.max(1e-8,length(a)));\n  const cross = ([a,b,c], [x,y,z]) => [b*z-c*y, c*x-a*z, a*y-b*x];\n  const lerp = (a,b,t) => add(a,scale(sub(b,a),t));\n  const smooth = t => { const p=clamp(t,0,1); return p*p*(3-2*p); };\n  const clip = ([x,y,z])=>[clamp(x,-3,3),clamp(y,-3,3),clamp(z,.14,4.8)];\n\n  // Keep the site and the four contacts fixed. Each rule supplies its own route.\n  function append(route) {\n    path.push(...route.slice(path.length ? 1 : 0));\n  }\n\n  function round(route, amount, steps) {\n    const points=[route[0]];\n    for(let i=1;i<route.length-1;i++){\n      const p=route[i], a=lerp(p,route[i-1],amount), b=lerp(p,route[i+1],amount);\n      for(let j=0;j<steps;j++){\n        const t=j/(steps-1);\n        points.push(lerp(lerp(a,p,t),lerp(p,b,t),t));\n      }\n    }\n    points.push(route.at(-1));\n    return points;\n  }\n\n  const { generations } = controls;\n  if (generations!==2 && generations!==3) throw new Error('Choose two or three rewrites.');\n  for(let span=0;span<3;span++){\n    const frames=[];\n    let word=[{a:feet[span],b:feet[span+1],roll:.61+span*.63}];\n    const points=()=>[word[0].a,...word.map(symbol=>symbol.b)];\n    if(trace) frames.push(points());\n    for(let iteration=0;iteration<generations;iteration++){\n      word=word.flatMap(({a,b,roll})=>{\n        const tangent=unit(sub(b,a)), size=length(sub(b,a));\n        const axis=Math.abs(tangent[2])>.92 ? [0,1,0] : [0,0,1];\n        const lateral=unit(cross(axis,tangent));\n        const up=cross(tangent,lateral);\n        const knots=[a];\n        for(const [t,reach,turn] of [[.21,.34,.24],[.46,.56,1.33],[.73,.43,2.76]]){\n          const angle=roll+turn;\n          const offset=add(scale(lateral,Math.sin(angle)),scale(up,Math.cos(angle)));\n          const raw=add(lerp(a,b,t),scale(offset,size*reach));\n          const reflect=(v,limit)=>Math.abs(v)<=limit ? v : Math.sign(v)*(2*limit-Math.abs(v));\n          const [x,y,z]=raw;\n          knots.push(clip([reflect(x,3),reflect(y,3),z<.14?.28-z:z]));\n        }\n        knots.push(b);\n        return knots.slice(0,-1).map((a,k)=>({a,b:knots[k+1],roll:roll+[.36,-1.22,1.65,-.58][k]}))\n          .filter(({a,b})=>length(sub(b,a))>.021);\n      });\n      if(trace) frames.push(points());\n    }\n    const route=points();\n    if(trace) trace.push({route,frames});\n    append(round(route,.31,3));\n  }\n\n  // Give overlapping passes room for the tube, leaving the four contacts fixed.\n  // The simulation decides the route. This step only moves near neighbours.\n  const gap = 0.185;\n  const fixed = path.map(p => p[2] === 0);\n  const stations = [0];\n  for (let i=1; i<path.length; i++) {\n    stations.push(stations.at(-1)+Math.hypot(...sub(path[i],path[i-1])));\n  }\n\n  function separate(i,j) {\n    const a=path[i], b=path[i+1], c=path[j], d=path[j+1];\n    for(let k=0;k<3;k++){\n      if(Math.min(a[k],b[k])-Math.max(c[k],d[k])>gap || Math.min(c[k],d[k])-Math.max(a[k],b[k])>gap) return false;\n    }\n    const u=sub(b,a), v=sub(d,c), r=sub(a,c);\n    const uu=dot(u,u), vv=dot(v,v), uv=dot(u,v);\n    const ru=dot(r,u), rv=dot(r,v), den=uu*vv-uv*uv;\n    let s=den>1e-10 ? clamp((uv*rv-vv*ru)/den,0,1) : 0;\n    let t=(uv*s+rv)/vv;\n    if(t<0 || t>1){\n      t=clamp(t,0,1);\n      s=clamp((uv*t-ru)/uu,0,1);\n    }\n    const delta=sub(add(r,scale(u,s)),scale(v,t));\n    const distance=Math.hypot(...delta);\n    if(distance>=gap) return false;\n    const direction=distance>1e-7 ? scale(delta,1/distance) : [1,0,0];\n    const weights=[1-s,s,t-1,-t].map((weight,k)=>fixed[[i,i+1,j,j+1][k]] ? 0 : weight);\n    const total=dot(weights,weights);\n    if(total<1e-8) return false;\n    const shift=scale(direction,0.72*(gap-distance)/total);\n    [i,i+1,j,j+1].forEach((k,n)=>{\n      if(fixed[k]) return;\n      const [x,y,z]=add(path[k],scale(shift,weights[n]));\n      path[k]=clip([x,y,z]);\n    });\n    return true;\n  }\n\n  for(let iteration=0;iteration<32;iteration++){\n    let moved=false;\n    for(let i=0;i<path.length-1;i++){\n      for(let j=i+2;j<path.length-1;j++){\n        if(stations[j]-stations[i+1]>.30 && separate(i,j)) moved=true;\n      }\n    }\n    if(!moved) break;\n  }\n\n  const curve = path;\n  // Sweep one circular section in a parallel-transport frame.\n  const radius = 0.050;\n  const sides = 10;\n  const vertices = [];\n  const faces = [Array.from({length:sides},(_,j)=>sides-1-j)];\n  let normal=[0,1,0];\n  for(let i=0;i<curve.length;i++){\n    const p=curve[i];\n    const tangent=unit(sub(curve[Math.min(i+1,curve.length-1)],curve[Math.max(i-1,0)]));\n    normal=unit(sub(normal,scale(tangent,dot(normal,tangent))));\n    const binormal=cross(tangent,normal);\n    for(let j=0;j<sides;j++){\n      const angle=2*Math.PI*j/sides;\n      const q=add(p,add(scale(normal,radius*Math.cos(angle)),scale(binormal,radius*Math.sin(angle))));\n      vertices.push(q.map(v=>Number(v.toFixed(5))));\n      if(i){\n        const a=(i-1)*sides+j, b=(i-1)*sides+(j+1)%sides;\n        faces.push([a,b,b+sides,a+sides]);\n      }\n    }\n  }\n  const last=vertices.length-sides;\n  faces.push(Array.from({length:sides},(_,j)=>last+j));\n  return {\n    version:2, units:'metres',\n    objects:[{type:'MESH',name:'One continuous tube',color:'#3954b5',vertices,faces}]\n  };\n}\n"
          },
          "controls": {
            "value": {
              "generations": 3
            }
          }
        }
      },
      {
        "schema_version": 3,
        "id": "viewer",
        "operation": "scene.view",
        "objective": "Inspect the pavilion in 3D and render a model reference.",
        "inputs": {
          "scene": {
            "task": "pavilion",
            "output": "result"
          },
          "azimuth": {
            "value": -55
          },
          "elevation": {
            "value": 25
          },
          "resolution": {
            "value": 640
          }
        }
      },
      {
        "schema_version": 3,
        "id": "visualization",
        "operation": "gemini.image",
        "objective": "Visualize the exact pavilion in its setting with Gemini.",
        "inputs": {
          "model": {
            "value": "gemini-3.1-flash-image"
          },
          "files": {
            "value": [
              {
                "task": "viewer",
                "output": "preview"
              }
            ]
          },
          "aspect_ratio": {
            "value": "16:9"
          },
          "prompt": {
            "value": "Create one architectural concept image based on the supplied 3D viewer reference. Match its camera and preserve the exact continuous blue tube, including every curve where it forms the enclosure, folds overhead, and reaches the ground. Keep the spaces between the folds visible. Show matte powder-coated steel on paving in a public garden in daylight. Keep the geometry fixed and change materials, light and surroundings. No text or extra architecture."
          }
        }
      }
    ]
  }
}
