"""Writes firepit.json for bridge/slbuild.py: a ring of soot-dark stones on an ash bed, a teepee of logs over glowing embers, two log seats on the south side facing the fire and the sea beyond it. Flames, embers, smoke, light and the seats' sit targets come from firepit.lsl in the root. Root = ash bed centre. Claude Opus 5.5 (Tessel), 2026-10-10.""" import json import math import random STONE_TEX = "33c13e29-3fcb-9995-bffa-e271698d6351" ROCKS = ["0dc88c68-24fc-b9f0-1a82-f22ce14b4903", "00f83037-495c-26ab-333d-1c70bf5730b1", "e2d464e5-ddea-986a-2a64-d65f4e370287"] # tidepool/rock_sculpt.py seeds 1-3 R = random.Random(11) BARK = "#5b4634" CHAR = "#241e1a" def r3(v): return [round(x, 3) for x in v] def qmul(a, b): ax, ay, az, aw = a bx, by, bz, bw = b return [aw * bx + ax * bw + ay * bz - az * by, aw * by - ax * bz + ay * bw + az * bx, aw * bz + ax * by - ay * bx + az * bw, aw * bw - ax * bx - ay * by - az * bz] def qaxis(axis, deg): h = math.radians(deg) / 2 s = math.sin(h) return [axis[0] * s, axis[1] * s, axis[2] * s, math.cos(h)] def log(name, pos, quat, length, dia, color=BARK): """Cylinder along its local z; faces 0 and 2 are the cut ends (charred).""" return {"name": name, "shape": "cylinder", "size": r3([dia, dia * R.uniform(0.9, 1.0), length]), "pos": r3(pos), "quat": r3(qmul(quat, qaxis([0, 0, 1], R.uniform(0, 360)))), "texture": "blank", "color": color, "bump": "bark", "faces": {"0,2": {"color": CHAR, "bump": "none"}}} prims = [ {"name": "Fire Pit", "desc": "Made by Tessel the AI (Claude). Sit by the fire; touch to bank or light it.", "shape": "cylinder", "size": [1.3, 1.3, 0.05], "texture": "blank", "color": "#3b3734", "bump": "gravel"}, {"name": "embers", "shape": "sphere", "size": [0.5, 0.5, 0.09], "pos": [0, 0, 0.03], "texture": "blank", "color": "#ff5a14", "fullbright": True, "glow": 0.25, "bump": "blobs"}, {"name": "flame", "shape": "box", "size": [0.05, 0.05, 0.05], "pos": [0, 0, 0.12], "texture": "transparent", "light": {"color": "#ff9a4d", "intensity": 1.0, "radius": 7, "falloff": 0.6}}, {"name": "smoke", "shape": "box", "size": [0.05, 0.05, 0.05], "pos": [0, 0, 0.75], "texture": "transparent"}, ] # stone ring: soot-dark sculpted rocks, uneven TINTS = ["#8f8a82", "#7f7a73", "#9c968c", "#888276"] n = 12 for i in range(n): a = 2 * math.pi * (i + R.uniform(-0.2, 0.2)) / n rad = 0.7 + R.uniform(-0.02, 0.04) sx, sy, sz = R.uniform(0.34, 0.46), R.uniform(0.26, 0.34), R.uniform(0.2, 0.3) prims.append({"name": "stone", "shape": "sculpt", "sculpt": {"map": R.choice(ROCKS), "type": "sphere"}, "size": r3([sx, sy, sz]), "pos": r3([rad * math.cos(a), rad * math.sin(a), sz * 0.3]), "rot": r3([R.uniform(-8, 8), R.uniform(-8, 8), math.degrees(a) + 90 + R.uniform(-20, 20)]), "texture": STONE_TEX, "repeats": [2, 1], "color": R.choice(TINTS), "bump": "stone"}) # teepee: logs leaning in from a 0.33 m circle, crossing above the embers L, TILT = 0.78, 38 for i in range(5): a = 360 * i / 5 + R.uniform(-12, 12) q = qmul(qaxis([0, 0, 1], a), qaxis([0, 1, 0], -TILT)) ax = [-math.cos(math.radians(a)) * math.sin(math.radians(TILT)), -math.sin(math.radians(a)) * math.sin(math.radians(TILT)), math.cos(math.radians(TILT))] rb = 0.33 base = [rb * math.cos(math.radians(a)), rb * math.sin(math.radians(a)), 0.03] prims.append(log("log", [base[k] + ax[k] * L / 2 for k in range(3)], q, L, R.uniform(0.08, 0.11))) # two burnt-down logs lying in the ash for a, off in ((20, 0.12), (115, -0.1)): q = qmul(qaxis([0, 0, 1], a), qaxis([1, 0, 0], 90)) prims.append(log("burnt log", [off, -off * 0.5, 0.06], q, 0.5, 0.075, color="#3a2c22")) # seats: big logs on the south side, each with an invisible anchor whose +x faces the fire (sit targets in the lsl) SEAT_R, SEAT_D = 1.75, 0.36 for a in (240, 300, 180, 0): # 180/0 added 2026-10-10 for guests; appended so earlier links keep their numbers ar = math.radians(a) c = [SEAT_R * math.cos(ar), SEAT_R * math.sin(ar)] q = qmul(qaxis([0, 0, 1], a + 90), qaxis([0, 1, 0], 90)) # horizontal, along the tangent prims.append(log("seat log", [c[0], c[1], SEAT_D / 2 - 0.02], q, 1.4, SEAT_D, color="#6a5340")) prims.append({"name": "seat", "shape": "box", "size": [0.05, 0.05, 0.05], "pos": r3([c[0], c[1], SEAT_D]), "rot": [0, 0, round(a + 180, 1)], "texture": "transparent"}) # prim diet (builder README "Land impact"): children don't need collision for p in prims[1:]: p.setdefault("physics_shape", "none") spec = {"name": "Fire Pit", "desc": prims[0]["desc"], "prims": prims} with open("firepit.json", "w") as f: f.write("// generated by gen_spec.py\n") f.write("{\n" + f' "name": "Fire Pit",\n "desc": {json.dumps(spec["desc"])},\n "prims": [\n') f.write(",\n".join(" " + json.dumps(p) for p in prims)) f.write("\n ]\n}\n") print(len(prims), "prims")