Correct Lucent dome spin-gravity frame
This commit is contained in:
parent
153f07adce
commit
1720d737a4
10 changed files with 169 additions and 71 deletions
|
|
@ -27,14 +27,13 @@ therefore treats surfaces as active controls, not passive decoration.
|
|||
## Coordinate Contract
|
||||
|
||||
- `X`: tether axis, from the city-bubble side to the media-eye lounge.
|
||||
- `Z`: local visual altitude in the scene; the city bubble sits below the
|
||||
tether/lounge assembly to preserve the story's "far below" read.
|
||||
- `Y`: lounge width and panel spread.
|
||||
- `YZ`: spinning city ground plane, orthogonal to the tether.
|
||||
- `+X`: outward from the city ground toward the dome/headquarters anchor side;
|
||||
building height runs along this axis.
|
||||
- `Y`: lounge width and city ground-plane axis.
|
||||
- The media-eye lounge is at positive `X`.
|
||||
- The sealed city bubble is at negative `X`, lower `Z`, and attached to the
|
||||
tether by a load spine, elevator umbilical, collar, stays, and transfer node.
|
||||
- The tether continues down through the dome to the city ground surface, landing
|
||||
in a prestige anchor plaza.
|
||||
- The dome city is at negative `X`, boolean-cut flat at the ground plane, and
|
||||
attached to the tether through a prestige anchor plaza.
|
||||
- Crisis geometry lives inside the lounge: elevator gate and glass rail at the
|
||||
threshold, safe-line arc behind it, security beyond the doors.
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ lanyard.
|
|||
```python
|
||||
add_cylinder_between("lucent_tether_main_cable", TETHER_START, TETHER_END, 0.075, mats["tether"])
|
||||
add_uv_sphere("city_bubble_sealed_aquarium", CITY_BUBBLE_CENTER, CITY_BUBBLE_SCALE, mats["glass"])
|
||||
add_cylinder_between("city_bubble_tether_load_spine", CITY_TETHER_JUNCTION, collar, 0.045, mats["tether"])
|
||||
add_yz_disk("city_bubble_flat_ground_surface_disk", CITY_GROUND_X + 0.004, 1.54, mats["park"], segments=128)
|
||||
```
|
||||
|
||||
**Crisis Geometry As Spatial Contract**
|
||||
|
|
@ -127,14 +126,14 @@ for i in range(16):
|
|||
u = step / 8
|
||||
r = 0.12 + 1.42 * smoothstep(u)
|
||||
angle = base_angle + 0.08 * pymath.sin(u * TAU + i)
|
||||
line.append((center[0] + r * pymath.cos(angle), center[1] + r * pymath.sin(angle), ground_z + 0.025))
|
||||
line.append((ground_x + 0.025, r * pymath.cos(angle), r * pymath.sin(angle)))
|
||||
roads.append(line)
|
||||
|
||||
for ring_i, radius in enumerate([0.22, 0.42, 0.68, 0.95, 1.2, 1.42]):
|
||||
for arc_i in range(3):
|
||||
start = TAU * arc_i / 3 + ring_i * 0.21
|
||||
end = start + TAU / 3 * (0.72 + 0.12 * hash01(ring_i, arc_i, 23))
|
||||
roads.append(arc_points((center[0], center[1], ground_z + 0.028), radius, pymath.degrees(start), pymath.degrees(end), 24, plane="xy"))
|
||||
roads.append(arc_points((ground_x + 0.028, 0.0, 0.0), radius, pymath.degrees(start), pymath.degrees(end), 24, plane="yz"))
|
||||
```
|
||||
|
||||
**Land Use Falls Off From The Anchor**
|
||||
|
|
@ -153,6 +152,19 @@ else:
|
|||
verts, faces = cottage_verts, cottage_faces
|
||||
```
|
||||
|
||||
**Sunflower Crest**
|
||||
|
||||
The city disk is the disk-floret region. The crest is the ray-floret region:
|
||||
Perlin-warped translucent petals anchored around the flat dome base and climbing
|
||||
partway up the dome, not a loose decorative halo.
|
||||
|
||||
```python
|
||||
for petal in range(34):
|
||||
center_a = TAU * petal / petals + 0.025 * pymath.sin(petal * 1.7)
|
||||
climb = 0.58 + 0.36 * fbm_2d(pymath.cos(center_a) * 2.1, pymath.sin(center_a) * 2.1, seed=177)
|
||||
x = CITY_GROUND_X + climb * smoothstep(t)
|
||||
```
|
||||
|
||||
**Shader Graphs Are Geometry Evidence Too**
|
||||
|
||||
Flat material defaults can make a procedural scene read like a diagram even
|
||||
|
|
@ -177,21 +189,23 @@ Command:
|
|||
Result:
|
||||
|
||||
```text
|
||||
LUCENT_TETHER_VERIFY ok objects=78
|
||||
LUCENT_TETHER_VERIFY ok objects=81
|
||||
```
|
||||
|
||||
Render review rejected the first pass because the city bubble read as detached
|
||||
from the tether and the EEVEE/default-material pass was too shallow. The
|
||||
accepted revision uses Cycles, explicit bubble-to-tether attachment geometry,
|
||||
shader-node work, and a non-grid city map. The verifier now checks the
|
||||
attachment objects, Cycles engine, material shader-node types, named crisis
|
||||
geometry, render artifacts, and the `VG Lucent Feed Ribbon` Geometry Script
|
||||
group.
|
||||
shader-node work, a non-grid city map, the orthogonal spinning city frame,
|
||||
boolean-cut flat dome base, and a sunflower ray-floret crest. The verifier now
|
||||
checks the attachment objects, Cycles engine, material shader-node types, named
|
||||
crisis geometry, city coordinate contract, render artifacts, and the `VG Lucent
|
||||
Feed Ribbon` Geometry Script group.
|
||||
|
||||
## Durable Lesson
|
||||
|
||||
For Lucent spaces, avoid thinking "set dressing." The set is the power system.
|
||||
Build public surfaces as rails, rankings, prompt stacks, edits, delayed feeds,
|
||||
and shader behavior. Then put physical bodies and thresholds where those
|
||||
abstractions become dangerous. Also: "far below" does not mean "unattached."
|
||||
Large habitat parts need visible load paths, or the render becomes a polite lie.
|
||||
abstractions become dangerous. Also: "far below" is not the coordinate system.
|
||||
For a tether habitat, spin gravity decides the ground plane. Large habitat parts
|
||||
need visible load paths, or the render becomes a polite lie.
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ from vibegeometry import (
|
|||
|
||||
TAU = pymath.tau
|
||||
OUT_DIR = Path("experiments/generated/lucent_tether_habitat")
|
||||
TETHER_START = (-6.5, 0.0, 0.0)
|
||||
CITY_GROUND_X = -5.45
|
||||
CITY_BUBBLE_CENTER = (-4.95, 0.0, 0.0)
|
||||
CITY_BUBBLE_SCALE = (1.0, 1.95, 1.95)
|
||||
CITY_TETHER_JUNCTION = (CITY_GROUND_X, 0.0, 0.0)
|
||||
TETHER_START = CITY_TETHER_JUNCTION
|
||||
TETHER_END = (6.8, 0.0, 0.0)
|
||||
CITY_BUBBLE_CENTER = (-4.85, 0.0, -2.35)
|
||||
CITY_BUBBLE_SCALE = (1.95, 1.95, 1.05)
|
||||
CITY_TETHER_JUNCTION = (-4.85, 0.0, 0.0)
|
||||
MEDIA_EYE_CENTER = (4.6, 0.0, 0.0)
|
||||
|
||||
|
||||
|
|
@ -157,6 +158,7 @@ def enrich_shader_graphs(mats):
|
|||
add_shader_bump(mats["park"], scale=18.0, detail=6.0, strength=0.045, distance=0.028)
|
||||
add_shader_bump(mats["cottage"], scale=31.0, detail=5.0, strength=0.055, distance=0.03)
|
||||
add_shader_bump(mats["plaza"], scale=36.0, detail=6.0, strength=0.028, distance=0.02)
|
||||
add_shader_bump(mats["crest"], scale=22.0, detail=8.0, strength=0.035, distance=0.025)
|
||||
for key, pulse in [
|
||||
("cyan", (0.65, 1.0, 1.0, 1.0)),
|
||||
("amber", (1.0, 0.88, 0.32, 1.0)),
|
||||
|
|
@ -228,12 +230,20 @@ def arc_points(center, radius, start_deg, end_deg, steps, plane="xy", wobble=0.0
|
|||
return points
|
||||
|
||||
|
||||
def annular_sector_points(center, inner_radius, outer_radius, start_angle, end_angle, z):
|
||||
def annular_sector_points(center, inner_radius, outer_radius, start_angle, end_angle, plane="xy"):
|
||||
if plane == "yz":
|
||||
x, y, z = center
|
||||
return [
|
||||
(x, y + inner_radius * pymath.cos(start_angle), z + inner_radius * pymath.sin(start_angle)),
|
||||
(x, y + outer_radius * pymath.cos(start_angle), z + outer_radius * pymath.sin(start_angle)),
|
||||
(x, y + outer_radius * pymath.cos(end_angle), z + outer_radius * pymath.sin(end_angle)),
|
||||
(x, y + inner_radius * pymath.cos(end_angle), z + inner_radius * pymath.sin(end_angle)),
|
||||
]
|
||||
return [
|
||||
(center[0] + inner_radius * pymath.cos(start_angle), center[1] + inner_radius * pymath.sin(start_angle), z),
|
||||
(center[0] + outer_radius * pymath.cos(start_angle), center[1] + outer_radius * pymath.sin(start_angle), z),
|
||||
(center[0] + outer_radius * pymath.cos(end_angle), center[1] + outer_radius * pymath.sin(end_angle), z),
|
||||
(center[0] + inner_radius * pymath.cos(end_angle), center[1] + inner_radius * pymath.sin(end_angle), z),
|
||||
(center[0] + inner_radius * pymath.cos(start_angle), center[1] + inner_radius * pymath.sin(start_angle), center[2]),
|
||||
(center[0] + outer_radius * pymath.cos(start_angle), center[1] + outer_radius * pymath.sin(start_angle), center[2]),
|
||||
(center[0] + outer_radius * pymath.cos(end_angle), center[1] + outer_radius * pymath.sin(end_angle), center[2]),
|
||||
(center[0] + inner_radius * pymath.cos(end_angle), center[1] + inner_radius * pymath.sin(end_angle), center[2]),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -241,6 +251,47 @@ def add_flat_polygon(name, verts, material):
|
|||
return add_mesh_parts(name, verts, [(0, 1, 2, 3)], material)
|
||||
|
||||
|
||||
def add_yz_disk(name, x, radius, material, segments=96):
|
||||
verts = [(x, 0.0, 0.0)]
|
||||
faces = []
|
||||
for i in range(segments):
|
||||
a = TAU * i / segments
|
||||
verts.append((x, radius * pymath.cos(a), radius * pymath.sin(a)))
|
||||
for i in range(segments):
|
||||
faces.append((0, i + 1, 1 + ((i + 1) % segments)))
|
||||
return add_mesh_parts(name, verts, faces, material)
|
||||
|
||||
|
||||
def add_dome_base_crest(mats):
|
||||
verts = []
|
||||
faces = []
|
||||
petals = 34
|
||||
radial_steps = 5
|
||||
for petal in range(petals):
|
||||
center_a = TAU * petal / petals + 0.025 * pymath.sin(petal * 1.7)
|
||||
width = TAU / petals * (0.82 + 0.36 * hash01(petal, 0, 251))
|
||||
climb = 0.58 + 0.36 * fbm_2d(pymath.cos(center_a) * 2.1, pymath.sin(center_a) * 2.1, seed=177)
|
||||
twist = 0.08 * (hash01(petal, 4, 271) - 0.5)
|
||||
petal_base = len(verts)
|
||||
for side in (-1.0, 1.0):
|
||||
for step in range(radial_steps + 1):
|
||||
t = step / radial_steps
|
||||
edge = (0.55 + 0.45 * smoothstep(1.0 - abs(t - 0.55) * 2.0))
|
||||
noise = fbm_2d(petal * 0.41, step * 0.73 + side, seed=303) - 0.5
|
||||
local_width = width * (1.0 - 0.42 * smoothstep(t)) * edge + noise * 0.018
|
||||
a = center_a + side * local_width * 0.5 + twist * t
|
||||
radius = 1.48 + 0.22 * smoothstep(t) + 0.03 * noise
|
||||
x = CITY_GROUND_X + climb * smoothstep(t)
|
||||
y = radius * pymath.cos(a)
|
||||
z = radius * pymath.sin(a)
|
||||
verts.append((x, y, z))
|
||||
for step in range(radial_steps):
|
||||
faces.append((petal_base + step, petal_base + step + 1, petal_base + radial_steps + 2 + step + 1, petal_base + radial_steps + 2 + step))
|
||||
obj = add_mesh_parts("city_bubble_perlin_leaf_crest", verts, faces, mats["crest"])
|
||||
obj.show_transparent = True
|
||||
return obj
|
||||
|
||||
|
||||
def add_floor_patch(name, center, width, depth, material, z=0.0):
|
||||
return add_box(name, (center[0], center[1], z), ((1, 0, 0), (0, 1, 0), (0, 0, 1)), (width, depth, 0.035), material)
|
||||
|
||||
|
|
@ -269,34 +320,47 @@ def add_habitat_cable_bundle(mats):
|
|||
|
||||
|
||||
def add_city_bubble(mats):
|
||||
import bpy
|
||||
|
||||
bubble = add_uv_sphere("city_bubble_sealed_aquarium", CITY_BUBBLE_CENTER, CITY_BUBBLE_SCALE, mats["glass"], 64, 32)
|
||||
bubble.rotation_euler[0] = pymath.radians(8)
|
||||
bpy.ops.mesh.primitive_cube_add(size=1.0, location=(CITY_GROUND_X - 2.0, 0.0, 0.0))
|
||||
cutter = bpy.context.object
|
||||
cutter.name = "city_bubble_flattening_boolean_cutter"
|
||||
cutter.dimensions = (4.0, 5.0, 5.0)
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
boolean = bubble.modifiers.new("boolean cut flat ground base", "BOOLEAN")
|
||||
boolean.operation = "DIFFERENCE"
|
||||
boolean.object = cutter
|
||||
bpy.context.view_layer.objects.active = bubble
|
||||
bubble.select_set(True)
|
||||
bpy.ops.object.modifier_apply(modifier=boolean.name)
|
||||
cutter.hide_viewport = True
|
||||
cutter.hide_render = True
|
||||
add_torus(
|
||||
"city_bubble_pressure_equator",
|
||||
CITY_BUBBLE_CENTER,
|
||||
(CITY_GROUND_X + 0.02, 0.0, 0.0),
|
||||
1.95,
|
||||
0.022,
|
||||
mats["cyan"],
|
||||
rotation=(0.0, 0.0, 0.0),
|
||||
rotation=(0.0, pymath.radians(90), 0.0),
|
||||
segments=128,
|
||||
)
|
||||
add_torus(
|
||||
"city_bubble_lower_aquarium_rim",
|
||||
(CITY_BUBBLE_CENTER[0], CITY_BUBBLE_CENTER[1], CITY_BUBBLE_CENTER[2] - 0.27),
|
||||
"city_bubble_flat_ground_rim",
|
||||
(CITY_GROUND_X + 0.01, 0.0, 0.0),
|
||||
1.42,
|
||||
0.018,
|
||||
mats["aquarium"],
|
||||
rotation=(0.0, 0.0, 0.0),
|
||||
rotation=(0.0, pymath.radians(90), 0.0),
|
||||
segments=96,
|
||||
)
|
||||
add_yz_disk("city_bubble_flat_ground_surface_disk", CITY_GROUND_X + 0.004, 1.54, mats["park"], segments=128)
|
||||
|
||||
ground_z = CITY_BUBBLE_CENTER[2] - 0.30
|
||||
center = (CITY_BUBBLE_CENTER[0], CITY_BUBBLE_CENTER[1])
|
||||
ground_x = CITY_GROUND_X + 0.018
|
||||
tower_verts, tower_faces = [], []
|
||||
prestige_verts, prestige_faces = [], []
|
||||
cottage_verts, cottage_faces = [], []
|
||||
park_faces = []
|
||||
plaza_faces = []
|
||||
roads = []
|
||||
|
||||
# Street field: major avenues radiate from the tether anchor, then bend
|
||||
|
|
@ -310,22 +374,19 @@ def add_city_bubble(mats):
|
|||
r = 0.12 + 1.42 * smoothstep(u)
|
||||
drift = 0.22 * pymath.sin(u * pymath.pi) * (hash01(i, step, 17) - 0.5)
|
||||
angle = base_angle + drift + 0.08 * pymath.sin(u * TAU + i)
|
||||
line.append((center[0] + r * pymath.cos(angle), center[1] + r * pymath.sin(angle), ground_z + 0.025))
|
||||
line.append((ground_x + 0.025, r * pymath.cos(angle), r * pymath.sin(angle)))
|
||||
roads.append(line)
|
||||
for ring_i, radius in enumerate([0.22, 0.42, 0.68, 0.95, 1.2, 1.42]):
|
||||
for arc_i in range(3):
|
||||
start = TAU * arc_i / 3 + ring_i * 0.21
|
||||
end = start + TAU / 3 * (0.72 + 0.12 * hash01(ring_i, arc_i, 23))
|
||||
roads.append(arc_points((center[0], center[1], ground_z + 0.028), radius, pymath.degrees(start), pymath.degrees(end), 24, plane="xy", wobble=0.018))
|
||||
roads.append(arc_points((ground_x + 0.028, 0.0, 0.0), radius, pymath.degrees(start), pymath.degrees(end), 24, plane="yz", wobble=0.018))
|
||||
|
||||
# Anchor plaza: the tether physically lands on the ground surface. The
|
||||
# skyline knows where power plugs in.
|
||||
for i in range(8):
|
||||
a0 = TAU * i / 8
|
||||
a1 = TAU * (i + 1) / 8
|
||||
plaza_faces.extend(annular_sector_points(center, 0.0, 0.23, a0, a1, ground_z + 0.006))
|
||||
add_flat_polygon("city_bubble_tether_anchor_plaza_core", annular_sector_points(center, 0.0, 0.24, 0.0, TAU / 4, ground_z + 0.012), mats["plaza"])
|
||||
add_torus("city_bubble_anchor_plaza_ring", (center[0], center[1], ground_z + 0.025), 0.32, 0.011, mats["soft_white"], segments=96)
|
||||
add_yz_disk("city_bubble_tether_anchor_plaza_core", ground_x + 0.012, 0.24, mats["plaza"], segments=48)
|
||||
add_torus("city_bubble_anchor_plaza_ring", (ground_x + 0.025, 0.0, 0.0), 0.32, 0.011, mats["soft_white"], rotation=(0.0, pymath.radians(90), 0.0), segments=96)
|
||||
add_dome_base_crest(mats)
|
||||
|
||||
rings = [0.24, 0.42, 0.62, 0.84, 1.08, 1.31, 1.52]
|
||||
for ring in range(len(rings) - 1):
|
||||
|
|
@ -341,33 +402,33 @@ def add_city_bubble(mats):
|
|||
n = fbm_2d(ring * 0.61, cell * 0.29, seed=91)
|
||||
park_bias = fbm_2d(pymath.cos((a0 + a1) * 0.5) * 2.2 + ring, pymath.sin((a0 + a1) * 0.5) * 2.2, seed=121)
|
||||
if zone > 0.32 and park_bias > 0.68:
|
||||
park_faces.extend(annular_sector_points(center, inner + 0.025, outer - 0.02, a0 + 0.025, a1 - 0.018, ground_z + 0.01))
|
||||
park_faces.extend(annular_sector_points((ground_x + 0.012, 0.0, 0.0), inner + 0.025, outer - 0.02, a0 + 0.025, a1 - 0.018, plane="yz"))
|
||||
continue
|
||||
r = inner + (outer - inner) * (0.45 + 0.22 * (n - 0.5))
|
||||
angle = (a0 + a1) * 0.5
|
||||
radial_axis = (pymath.cos(angle), pymath.sin(angle), 0.0)
|
||||
tangent_axis = (-pymath.sin(angle), pymath.cos(angle), 0.0)
|
||||
radial_axis = (0.0, pymath.cos(angle), pymath.sin(angle))
|
||||
tangent_axis = (0.0, -pymath.sin(angle), pymath.cos(angle))
|
||||
radial_width = max(0.04, (outer - inner) * (0.36 + 0.28 * n))
|
||||
tangential_width = max(0.045, r * (a1 - a0) * (0.55 + 0.3 * hash01(cell, ring, 37)))
|
||||
if zone < 0.28:
|
||||
height = 0.5 + 0.62 * n
|
||||
verts, faces = prestige_verts, prestige_faces
|
||||
size = (tangential_width * 0.72, radial_width * 0.8, height)
|
||||
loc = (center[0] + r * radial_axis[0], center[1] + r * radial_axis[1], ground_z + height * 0.5)
|
||||
loc = (ground_x + height * 0.5, r * pymath.cos(angle), r * pymath.sin(angle))
|
||||
elif zone < 0.72:
|
||||
height = 0.16 + 0.36 * n * (1.0 - zone * 0.35)
|
||||
verts, faces = tower_verts, tower_faces
|
||||
size = (tangential_width * 0.82, radial_width * 0.9, height)
|
||||
loc = (center[0] + r * radial_axis[0], center[1] + r * radial_axis[1], ground_z + height * 0.5)
|
||||
loc = (ground_x + height * 0.5, r * pymath.cos(angle), r * pymath.sin(angle))
|
||||
else:
|
||||
if n < 0.38:
|
||||
park_faces.extend(annular_sector_points(center, inner + 0.02, outer - 0.02, a0 + 0.02, a1 - 0.02, ground_z + 0.012))
|
||||
park_faces.extend(annular_sector_points((ground_x + 0.012, 0.0, 0.0), inner + 0.02, outer - 0.02, a0 + 0.02, a1 - 0.02, plane="yz"))
|
||||
continue
|
||||
height = 0.045 + 0.07 * n
|
||||
verts, faces = cottage_verts, cottage_faces
|
||||
size = (tangential_width * 0.55, radial_width * 0.6, height)
|
||||
loc = (center[0] + r * radial_axis[0], center[1] + r * radial_axis[1], ground_z + height * 0.5)
|
||||
append_box_parts(verts, faces, loc, (tangent_axis, radial_axis, (0, 0, 1)), size)
|
||||
loc = (ground_x + height * 0.5, r * pymath.cos(angle), r * pymath.sin(angle))
|
||||
append_box_parts(verts, faces, loc, (tangent_axis, radial_axis, (1, 0, 0)), size)
|
||||
|
||||
if park_faces:
|
||||
verts = park_faces
|
||||
|
|
@ -379,7 +440,7 @@ def add_city_bubble(mats):
|
|||
add_multi_polyline_curve("city_bubble_tensor_field_street_network", roads, 0.006, mats["soft_white"], resolution=3)
|
||||
add_multi_polyline_curve(
|
||||
"city_bubble_prestige_plaza_spokes",
|
||||
[[(center[0], center[1], ground_z + 0.04), road[-1]] for road in roads[:16:2]],
|
||||
[[(ground_x + 0.04, 0.0, 0.0), road[-1]] for road in roads[:16:2]],
|
||||
0.012,
|
||||
mats["cyan"],
|
||||
resolution=3,
|
||||
|
|
@ -389,35 +450,35 @@ def add_city_bubble(mats):
|
|||
def add_city_bubble_tether_attachment(mats):
|
||||
x, y, z = CITY_TETHER_JUNCTION
|
||||
top = (x, y, z)
|
||||
collar = (CITY_BUBBLE_CENTER[0], CITY_BUBBLE_CENTER[1], CITY_BUBBLE_CENTER[2] + 1.02)
|
||||
plaza = (CITY_BUBBLE_CENTER[0], CITY_BUBBLE_CENTER[1], CITY_BUBBLE_CENTER[2] - 0.30)
|
||||
keel = (CITY_BUBBLE_CENTER[0], CITY_BUBBLE_CENTER[1], CITY_BUBBLE_CENTER[2] - 0.22)
|
||||
collar = (CITY_GROUND_X + 0.08, 0.0, 0.0)
|
||||
plaza = (CITY_GROUND_X + 0.08, 0.0, 0.0)
|
||||
keel = (CITY_GROUND_X - 0.12, 0.0, 0.0)
|
||||
add_cylinder_between("city_bubble_tether_load_spine", top, plaza, 0.045, mats["tether"], vertices=16)
|
||||
add_cylinder_between("city_bubble_elevator_umbilical", (x + 0.14, 0.0, -0.05), (collar[0] + 0.14, 0.0, collar[2] - 0.03), 0.022, mats["cyan"], vertices=10)
|
||||
add_cylinder_between("city_bubble_elevator_drop_to_anchor_plaza", (x + 0.14, 0.0, collar[2] - 0.03), (plaza[0] + 0.14, 0.0, plaza[2] + 0.06), 0.018, mats["cyan"], vertices=10)
|
||||
add_cylinder_between("city_bubble_elevator_umbilical", (x + 0.14, 0.0, -0.05), (plaza[0] + 0.14, 0.0, -0.05), 0.022, mats["cyan"], vertices=10)
|
||||
add_cylinder_between("city_bubble_elevator_drop_to_anchor_plaza", (plaza[0] + 0.14, 0.0, -0.05), (plaza[0] + 0.14, 0.0, 0.18), 0.018, mats["cyan"], vertices=10)
|
||||
add_torus(
|
||||
"city_bubble_upper_tether_collar",
|
||||
collar,
|
||||
0.52,
|
||||
0.025,
|
||||
mats["soft_white"],
|
||||
rotation=(0.0, 0.0, 0.0),
|
||||
rotation=(0.0, pymath.radians(90), 0.0),
|
||||
segments=96,
|
||||
)
|
||||
add_box("city_bubble_tether_transfer_node", (x, 0.0, -0.1), ((1, 0, 0), (0, 1, 0), (0, 0, 1)), (0.42, 0.34, 0.16), mats["lucent_gray"])
|
||||
add_box("city_bubble_tether_transfer_node", (x + 0.08, 0.0, 0.0), ((1, 0, 0), (0, 1, 0), (0, 0, 1)), (0.42, 0.34, 0.34), mats["lucent_gray"])
|
||||
for i, angle in enumerate([0.0, pymath.pi / 2, pymath.pi, pymath.pi * 1.5]):
|
||||
y2 = pymath.cos(angle) * 1.35
|
||||
x2 = CITY_BUBBLE_CENTER[0] + pymath.sin(angle) * 0.42
|
||||
z2 = CITY_BUBBLE_CENTER[2] + 0.78
|
||||
z2 = pymath.sin(angle) * 1.35
|
||||
x2 = CITY_GROUND_X + 0.18 + 0.22 * pymath.sin(angle * 2.0)
|
||||
add_cylinder_between(f"city_bubble_suspension_stay_{i:02d}", top, (x2, y2, z2), 0.012, mats["soft_white"], vertices=6)
|
||||
add_curve_polyline(
|
||||
f"city_bubble_tension_trace_{i:02d}",
|
||||
[top, ((top[0] + x2) * 0.5, y2 * 0.35, -0.72), (x2, y2, z2)],
|
||||
[top, ((top[0] + x2) * 0.5, y2 * 0.35, z2 * 0.35), (x2, y2, z2)],
|
||||
0.004,
|
||||
mats["cyan"],
|
||||
resolution=3,
|
||||
)
|
||||
add_cylinder_between("city_bubble_under_keel_counterweight", keel, (keel[0], keel[1], keel[2] - 0.75), 0.028, mats["dark"], vertices=10)
|
||||
add_cylinder_between("city_bubble_under_keel_counterweight", keel, (keel[0] - 0.75, keel[1], keel[2]), 0.028, mats["dark"], vertices=10)
|
||||
|
||||
|
||||
def add_media_eye_lounge(mats):
|
||||
|
|
@ -506,7 +567,7 @@ def add_overlay_ribbons_and_signage(mats):
|
|||
labels = [
|
||||
("label_media_eye", "media-eye transfer lounge", (4.65, -1.52, 0.7), 0.082),
|
||||
("label_safe_line", "safe-line / glass rail", (5.18, 1.2, -0.34), 0.066),
|
||||
("label_city_bubble", "city bubble below: sealed aquarium", (-5.9, -1.9, -2.42), 0.12),
|
||||
("label_city_bubble", "orthogonal dome city / sunflower crest", (-5.38, -1.82, -1.72), 0.095),
|
||||
("label_lucent_surfaces", "surfaces as controls, rankings, edits", (1.6, -0.82, 0.5), 0.082),
|
||||
]
|
||||
for name, text, loc, size in labels:
|
||||
|
|
@ -540,6 +601,7 @@ def build_scene():
|
|||
"park": mat("interspersed park green", (0.12, 0.48, 0.22), alpha=0.92),
|
||||
"cottage": mat("verdant faux rural cottagecore", (0.46, 0.64, 0.39), alpha=0.96),
|
||||
"plaza": mat("tether anchor plaza stone", (0.68, 0.7, 0.66), alpha=0.96),
|
||||
"crest": mat("Perlin-edged dome flower crest", (0.25, 0.9, 0.74), emission=True, strength=0.12, alpha=0.34),
|
||||
"label": mat("label white", (0.92, 0.95, 1.0), emission=True, strength=0.45),
|
||||
}
|
||||
enrich_shader_graphs(mats)
|
||||
|
|
@ -569,10 +631,10 @@ def build_scene():
|
|||
bpy.context.scene.view_settings.view_transform = "Filmic"
|
||||
bpy.context.scene.view_settings.look = "Medium High Contrast"
|
||||
|
||||
bpy.ops.object.camera_add(location=(6.9, -8.2, 4.6))
|
||||
bpy.ops.object.camera_add(location=(6.6, -7.6, 4.9))
|
||||
overview = bpy.context.object
|
||||
overview.name = "Camera_Lucent_Tether_Overview"
|
||||
aim_camera(overview, (0.2, 0.0, -1.0))
|
||||
aim_camera(overview, (0.15, 0.0, 0.0))
|
||||
overview.data.type = "ORTHO"
|
||||
overview.data.ortho_scale = 7.6
|
||||
bpy.context.scene.camera = overview
|
||||
|
|
@ -584,12 +646,12 @@ def build_scene():
|
|||
lounge.data.type = "ORTHO"
|
||||
lounge.data.ortho_scale = 2.5
|
||||
|
||||
bpy.ops.object.camera_add(location=(-3.35, -3.15, -0.95))
|
||||
bpy.ops.object.camera_add(location=(-2.75, -3.25, 1.75))
|
||||
city = bpy.context.object
|
||||
city.name = "Camera_City_Bubble_Aquarium"
|
||||
aim_camera(city, CITY_BUBBLE_CENTER)
|
||||
city.data.type = "ORTHO"
|
||||
city.data.ortho_scale = 4.2
|
||||
city.data.ortho_scale = 4.05
|
||||
|
||||
|
||||
def render_camera(camera_name, path):
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1,010 KiB After Width: | Height: | Size: 996 KiB |
|
|
@ -47,8 +47,9 @@ From-scratch story-bound builds:
|
|||
tether habitat from Ghostlight `Lucent Hostage Feed`: tether axis, city bubble
|
||||
below, media-eye lounge, crisis threshold, overlays, control/side panels, and
|
||||
security doors. Accepted revision uses Cycles, shader nodes, explicit bubble
|
||||
attachment, radial/tangential streets, irregular parcels, prestige anchor
|
||||
towers, parks, and faux-rural edge falloff.
|
||||
attachment, an orthogonal spin-gravity city plane, a flat-cut dome base,
|
||||
radial/tangential streets, irregular parcels, prestige anchor towers, parks,
|
||||
faux-rural edge falloff, and a sunflower-like ray-floret crest.
|
||||
Build notes: `docs/research/builds/lucent-tether-habitat.md`; verifier:
|
||||
`tools/verify_lucent_tether_habitat.py`.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,3 +45,4 @@
|
|||
{"ts": "2026-05-05T01:40:49+00:00", "type": "implementation", "status": "accepted", "note": "Built and verified the Lucent tether habitat from Ghostlight source: tether axis, city bubble below, media-eye transfer lounge, glass rail/safe-line threshold, Lucent overlay/control surfaces, and an inspectable VG Lucent Feed Ribbon Geometry Script group. Render review accepted overview, lounge, and city-bubble inspection artifacts."}
|
||||
{"ts": "2026-05-05T01:59:41+00:00", "type": "implementation", "status": "accepted", "note": "Revised Lucent tether habitat after visual review: city bubble now has explicit tether load spine, elevator umbilical, collar, stays, and transfer node; renders now use Cycles; shader graphs add procedural noise/bump/ramp nodes to glass, tether, and Lucent overlay surfaces; verifier checks these contracts."}
|
||||
{"ts": "2026-05-05T02:20:23+00:00", "type": "implementation", "status": "accepted", "note": "Replaced Lucent city-bubble grid with street-generation-inspired radial/tangential field roads, irregular annular parcel cells, anchor-plaza prestige skyscrapers, park voids, midcity blocks, and faux-rural cottagecore edge falloff; tether now visibly descends to the city ground-surface plaza."}
|
||||
{"ts": "2026-05-05T03:01:59+00:00", "type": "implementation", "status": "accepted", "note": "Corrected Lucent dome city coordinate frame: ground is now a YZ disk orthogonal to the tether/spin axis, buildings extrude along +X, dome is boolean-cut flat at the base, city fills to the rim, and a Perlin-edged sunflower ray-floret crest rises from the base partway up the dome."}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ data_flow:
|
|||
- If state grows faster than understanding, cut it back.
|
||||
|
||||
current_status:
|
||||
summary: VibeGeometry has verified Geometry Script translations for local samples, official Blender demos, and IRCSS French Houses footholds, plus from-scratch Aetheria Bloom and Lucent tether habitat builds. Durable lessons now driving the pipeline: Python orchestrates intent and batching; Geometry Script emits inspectable node groups; source-bound scenes need explicit coordinate contracts before ornament; large habitat parts need visible load paths across coordinate frames; organic city maps need street fields, irregular parcels, parks, and land-use gradients rather than grids; dense detail must be batched; institutional spaces should model power as surfaces, rails, overlays, thresholds, shader behavior, and camera evidence; and visual acceptance needs domain-facing inspection renders plus verifier checks, not only overview renders. The first reusable `vibegeometry/` helper library now carries coordinate frames, fields, batching, Blender scene primitives, and verifier helpers used by the Bloom and Lucent builds. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
summary: VibeGeometry has verified Geometry Script translations for local samples, official Blender demos, and IRCSS French Houses footholds, plus from-scratch Aetheria Bloom and Lucent tether habitat builds. Durable lessons now driving the pipeline: Python orchestrates intent and batching; Geometry Script emits inspectable node groups; source-bound scenes need explicit coordinate contracts before ornament; spin-gravity habitats need ground planes orthogonal to their tether/rotation axis; large habitat parts need visible load paths across coordinate frames; organic city maps need street fields, irregular parcels, parks, and land-use gradients rather than grids; dense detail must be batched; institutional spaces should model power as surfaces, rails, overlays, thresholds, shader behavior, and camera evidence; and visual acceptance needs domain-facing inspection renders plus verifier checks, not only overview renders. The first reusable `vibegeometry/` helper library now carries coordinate frames, fields, batching, Blender scene primitives, and verifier helpers used by the Bloom and Lucent builds. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
next_action: Extract the first higher-level reusable builder seam from the completed story-bound builds: either threshold/control-rail interface kits from the Lucent habitat or endcap terrace/spiral artery kits from the Bloom, then bind that seam to scene-brief, manifest, inspection-render, and verifier contract drafts.
|
||||
remotes:
|
||||
vibegeometry: https://github.com/GameCult/VibeGeometry
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ REQUIRED_OBJECTS = [
|
|||
"lucent_tether_main_cable",
|
||||
"lucent_tether_spin_reference_core",
|
||||
"city_bubble_sealed_aquarium",
|
||||
"city_bubble_pressure_equator",
|
||||
"city_bubble_flat_ground_rim",
|
||||
"city_bubble_tether_load_spine",
|
||||
"city_bubble_elevator_umbilical",
|
||||
"city_bubble_upper_tether_collar",
|
||||
|
|
@ -28,6 +30,9 @@ REQUIRED_OBJECTS = [
|
|||
"city_bubble_under_keel_counterweight",
|
||||
"city_bubble_tether_anchor_plaza_core",
|
||||
"city_bubble_anchor_plaza_ring",
|
||||
"city_bubble_flat_ground_surface_disk",
|
||||
"city_bubble_flattening_boolean_cutter",
|
||||
"city_bubble_perlin_leaf_crest",
|
||||
"city_bubble_interspersed_park_cells",
|
||||
"city_bubble_prestige_anchor_skyscrapers",
|
||||
"city_bubble_midcity_irregular_blocks",
|
||||
|
|
@ -86,6 +91,20 @@ def verify_render_engine_and_shaders():
|
|||
raise AssertionError(f"Material {material_name} is missing shader node types {missing}")
|
||||
|
||||
|
||||
def verify_city_coordinate_contract():
|
||||
bubble = bpy.data.objects["city_bubble_sealed_aquarium"]
|
||||
plaza = bpy.data.objects["city_bubble_tether_anchor_plaza_core"]
|
||||
crest = bpy.data.objects["city_bubble_perlin_leaf_crest"]
|
||||
if bubble.dimensions.x >= bubble.dimensions.y * 0.7:
|
||||
raise AssertionError("City dome is not flattened along the tether axis")
|
||||
if plaza.dimensions.x >= 0.05:
|
||||
raise AssertionError("City ground surface should be orthogonal to tether with minimal X depth")
|
||||
if crest.dimensions.x <= 0.45:
|
||||
raise AssertionError("Sunflower crest should climb partway up the dome along the tether axis")
|
||||
if crest.dimensions.y < 2.8 or crest.dimensions.z < 2.8:
|
||||
raise AssertionError("Sunflower crest should wrap around most of the dome rim")
|
||||
|
||||
|
||||
def verify_artifacts():
|
||||
assert_render_artifacts(
|
||||
[
|
||||
|
|
@ -103,6 +122,7 @@ def main():
|
|||
geometry_objects = assert_scene_density(bpy, min_geometry_objects=55)
|
||||
verify_geometry_script_group()
|
||||
verify_render_engine_and_shaders()
|
||||
verify_city_coordinate_contract()
|
||||
verify_artifacts()
|
||||
print(f"LUCENT_TETHER_VERIFY ok objects={len(geometry_objects)}")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue