{
  "name": "Rune Portal Gate",
  "dimension": "3d",
  "slot": "action_bar",
  "width": 256,
  "height": 128,
  "textBaseline": 128,
  "placement": {
    "x": 0,
    "y": 0
  },
  "shapes": [],
  "texts": [],
  "heads": [],
  "images": [],
  "bars": [],
  "triggers": [],
  "world": {
    "glsl": "// A standing stone ring with runes, facing along its own Z. CLOSED it is cold\n// stone with the runes barely there; OPENING lights the runes one after\n// another round the ring, then a swirling membrane tears open from the middle\n// with sparks at its edge; OPEN it churns; CLOSING pinches it shut to a point\n// with a flash and the runes go dark. Turn the object to aim it.\nconst int STATE_CLOSED = 0;\nconst int STATE_OPENING = 1;\nconst int STATE_OPEN = 2;\nconst int STATE_CLOSING = 3;\n\nconst float RING_R = 0.74;\nconst float RING_W = 0.13;\nconst float HOLE = 0.62;\nconst float SEGMENTS = 14.0;\n\nfloat pgRunes;\nfloat pgOpen;\nfloat pgEdge;\nfloat pgFlash;\n\n// How far each rune is lit (0..1) and how far open the membrane is, per state.\nvoid pgState() {\n    float st = rpw_state_time;\n    pgRunes = 0.06;\n    pgOpen = 0.0;\n    pgEdge = 0.0;\n    pgFlash = 0.0;\n    if (rpw_state == STATE_OPENING) {\n        pgRunes = st * 1.4;\n        float o = smoothstep(1.3, 3.2, st);\n        pgOpen = o;\n        pgEdge = o * (1.0 - smoothstep(3.0, 4.5, st)) + 0.25;\n    } else if (rpw_state == STATE_OPEN) {\n        pgRunes = 2.0;\n        pgOpen = 1.0;\n        pgEdge = 0.25;\n    } else if (rpw_state == STATE_CLOSING) {\n        pgRunes = 2.0 * (1.0 - smoothstep(0.9, 2.2, st));\n        pgOpen = 1.0 - smoothstep(0.0, 1.0, st);\n        pgEdge = 0.6 * pgOpen + 0.2;\n        pgFlash = smoothstep(0.85, 1.0, st) * (1.0 - smoothstep(1.0, 1.6, st));\n    }\n}\n\n// The ring, in its own frame: a torus round Z, squared off into stone blocks.\nfloat ring(vec3 p) {\n    float a = atan(p.y, p.x);\n    float seg = floor((a / 6.2831853 + 0.5) * SEGMENTS);\n    float local = fract((a / 6.2831853 + 0.5) * SEGMENTS);\n    vec2 q = vec2(length(p.xy) - RING_R, p.z);\n    vec2 b = abs(q) - vec2(RING_W, RING_W * 0.85);\n    float d = length(max(b, 0.0)) + min(max(b.x, b.y), 0.0) - 0.012;\n    // Grooves between the blocks.\n    d += 0.012 * (1.0 - smoothstep(0.0, 0.06, min(local, 1.0 - local)));\n    return d + 0.006 * rpw_noise(p * 40.0 + seg);\n}\n\nvec3 ringNormal(vec3 p) {\n    vec3 e = vec3(0.002, 0.0, 0.0);\n    return normalize(vec3(ring(p + e.xyy) - ring(p - e.xyy), ring(p + e.yxy) - ring(p - e.yxy), ring(p + e.yyx) - ring(p - e.yyx)));\n}\n\n// How lit the rune on the block at this angle is.\nfloat runeLit(float a) {\n    float seg = floor((a / 6.2831853 + 0.5) * SEGMENTS);\n    float order = mod(seg + SEGMENTS * 0.5, SEGMENTS) / SEGMENTS;\n    return clamp((pgRunes - order) * 3.0, 0.0, 1.0);\n}\n\nvec4 membrane(vec2 p, float time) {\n    float r = length(p);\n    float rm = HOLE * pgOpen;\n    if (r > rm + 0.03) return vec4(0.0);\n    float a = atan(p.y, p.x);\n    float sw = a * 3.0 + log(max(r, 0.01)) * 5.0 - time * 2.6;\n    float n = rpw_fbm(vec3(cos(sw) * 2.0, sin(sw) * 2.0, r * 6.0 - time * 0.7));\n    float arms = 0.5 + 0.5 * sin(sw + n * 3.0);\n    vec3 deep = vec3(0.12, 0.02, 0.32);\n    vec3 mid = vec3(0.62, 0.12, 1.0);\n    vec3 hot = vec3(1.0, 0.75, 1.0);\n    float k = 1.0 - smoothstep(0.0, rm + 0.001, r);\n    vec3 c = mix(deep, mid, arms * (0.4 + 0.6 * n));\n    c = mix(c, hot, pow(k, 6.0));\n    float edge = exp(-abs(r - rm) * 60.0) * (0.6 + 0.4 * rpw_noise(vec3(a * 8.0, time * 12.0, 0.0)));\n    c += vec3(0.9, 0.6, 1.0) * edge * (1.0 + 3.0 * pgEdge);\n    float alpha = smoothstep(rm + 0.02, rm - 0.02, r) * 0.92 + edge * 0.5;\n    return vec4(c * 1.4, clamp(alpha, 0.0, 1.0));\n}\n\nvec4 object(vec3 ro, vec3 rd, float t0, float t1, float time, vec3 light, inout float depth) {\n    pgState();\n    // The membrane: where the ray crosses the ring's plane.\n    float tPlane = abs(rd.z) > 1e-4 ? -ro.z / rd.z : -1.0;\n    vec4 film = vec4(0.0);\n    if (tPlane > t0 && tPlane < t1 && pgOpen > 0.0) film = membrane((ro + rd * tPlane).xy, time);\n\n    // The stone.\n    float t = t0;\n    float tRing = -1.0;\n    for (int i = 0; i < 80; i++) {\n        vec3 p = ro + rd * t;\n        float d = ring(p);\n        if (d < 0.0015) {\n            tRing = t;\n            break;\n        }\n        t += d * 0.9;\n        if (t > t1) break;\n    }\n    vec4 stone = vec4(0.0);\n    if (tRing > 0.0) {\n        vec3 p = ro + rd * tRing;\n        vec3 n = ringNormal(p);\n        vec3 sun = normalize(vec3(0.4, 0.8, 0.3));\n        float a = atan(p.y, p.x);\n        vec3 base = mix(vec3(0.3, 0.29, 0.33), vec3(0.42, 0.4, 0.45), rpw_noise(p * 18.0));\n        vec3 col = base * (0.35 + 0.65 * max(dot(n, sun), 0.0)) * light;\n        // Runes on the two faces, a mark per block.\n        float local = fract((a / 6.2831853 + 0.5) * SEGMENTS);\n        float radial = (length(p.xy) - RING_R) / RING_W;\n        float mark = step(0.55, rpw_hash(vec3(floor(local * 4.0), floor(radial * 3.0 + 3.0), floor((a / 6.2831853 + 0.5) * SEGMENTS))));\n        float onFace = smoothstep(0.6, 0.9, abs(n.z)) * step(0.15, local) * step(local, 0.85) * step(abs(radial), 0.6);\n        float lit = runeLit(a);\n        vec3 glow = vec3(0.55, 0.18, 1.0) * (0.12 + 1.5 * lit) * mark * onFace;\n        // The inner edge catches the membrane's light.\n        float inner = smoothstep(0.1, -0.6, radial) * pgOpen;\n        col += glow + vec3(0.5, 0.15, 0.9) * inner * 0.6;\n        stone = vec4(col, 1.0);\n    }\n\n    vec4 outCol;\n    if (tRing > 0.0 && (film.a <= 0.0 || tRing < tPlane)) {\n        outCol = stone;\n        depth = tRing;\n    } else if (film.a > 0.0) {\n        vec3 behind = tRing > 0.0 ? stone.rgb : vec3(0.0);\n        float behindA = tRing > 0.0 ? 1.0 : 0.0;\n        outCol = vec4(mix(behind, film.rgb, film.a), max(film.a, behindA));\n        depth = tPlane;\n    } else {\n        outCol = vec4(0.0);\n    }\n    if (pgFlash > 0.0) {\n        float tc = clamp(-dot(ro, rd), t0, t1);\n        float b = length(ro + rd * tc);\n        float f = exp(-b * b / (0.01 + 0.12 * pgFlash)) * pgFlash;\n        outCol.rgb = mix(outCol.rgb, vec3(1.0, 0.9, 1.0) * 2.0, clamp(f, 0.0, 1.0));\n        outCol.a = max(outCol.a, clamp(f * 1.3, 0.0, 1.0));\n        if (outCol.a > 0.3 && tRing < 0.0 && film.a <= 0.0) depth = tc;\n    }\n    return outCol;\n}\n",
    "prompt": "A standing stone ring with runes that light up one by one, then a swirling membrane tears open in the middle, churns, and pinches shut.",
    "size": 6
  }
}