diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index f44c4526c..a6d5750f8 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -781,3 +781,18 @@ Scope guard (user concern, 2026-07-06): Synty intake must NOT displace the creat ROUTE DECISION (user, 2026-07-06, final): Synty intake REJECTED despite the spike''s technical PASS — (1) USD199.99/pack is too expensive; (2) Sidekick modularity is baked body-segment swap (clothes fused to body chunks), incompatible with our layered clothing-over-body system (11 bodies, skin tones, slot swapping); (3) user dislikes the Synty style. The spike still paid: transplant/batch scripts are source-agnostic and become the G1-family production pipeline; T-1090 (5 bodies broken bare) discovered; the Synty question is closed with evidence, not parked. OPERATIVE ROUTE: (b) derive from owned Fantasy parts + (c) hand-author — with a new preferred authoring technique: OFFSET-SHELL FROM OUR OWN BODY MESHES (extract body segment surface, offset outward into garment shell, cut neckline/hem/sleeves; weights inherited by construction, style fully ours, USD0). Suit + uniform remain the full hand-author proof. Purchases still open (separate concerns): UAL1/UAL2 Source ~USD30 for animation clips; Fantasy Outfits Source USD20 low-priority convenience. Chromakey garment-QA harness DELIVERED (2026-07-06, commits eaca6c8c4/74fa16260/b09a50efb): two-pass depth-proximity clip detection — pass A keys covered body segments magenta, pass B re-renders garment-only with a depth-only 3cm epsilon bias toward camera; analyzer splits key pixels into exposed_skin (informational — sleeveless arms, collars) vs clip_through (gating — skin within 3cm in front of cloth). Run: tooling/garment-qa/run-garment-qa . Verified on the peasant set (avg_m/f x Walk/Sprint/Crouch_Fwd x 3 frames x 4 yaws): 0/18 front-view false positives; genuine findings = sleeveless armhole seam (avg_f, sides), deep-crouch waist gap (worst 150px, back), collar nape. First real fit observations already on record for the peasant set. Sensitivity: clip_epsilon_m + --min-pixels, to calibrate against the first modern garments. This is the acceptance gate for every garment the wardrobe pipeline produces; pair with the T-1088 sandbox for in-world walk QA.', NULL, '2026-07-06 12:59:45', '2026-07-06 12:59:45.213', '2026-07-06 12:59:45.213', NULL, 'd416d57aec826414cd29d739238749cf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FKCER95EK266H8RWPW8XT1F0', 'description', 'Fable-5-window sidequest (sanctioned cascade exception, 2026-07-05): the presentation layer that turns discrete server tile steps into a believable walking 3D character. Scope: 3D sandbox dev scene (SR_LIVE=1, greybox tiles derived from server snapshots, no hand-authored map), CharacterVisual instantiated in-world, locomotion rig (tile-step + facing-octant interpolation, stance-aware speeds), UAL animation state machine (idle/walk/sprint/careful/crouch), D-148 camera rig (30deg low-angle follow, 45deg rotation). Server-authoritative from day one — no client-local movement model. Builds on D-148/D-149/D-244/D-066 and the T-417 stance system. If the Fable 5 window closes mid-flight, work parks here. + +Visual reference (user, 2026-07-06): Xenonauts 2 nails the target tiles/occlusion/character perspective — readable tile grid, low-angle tactical camera with angle options (matches D-148 30deg low-angle + 45deg rotation steps and the spikes/3dpipeline angle presets), and crucially its wall/roof occlusion treatment: camera-facing walls cut down/ghosted so interiors stay readable, floors peel per z-level. One deliberate difference: our camera locks to the player character (D-015/T-116) — no free map panning. Implication for the locomotion sandbox: greybox walls should prototype camera-facing cutaway occlusion early, since wall readability interacts with the locked low-angle camera. + +Scoping call (user + lead agreement, 2026-07-06): wall cutaway is CLIENT-SIDE presentation, decoupled from fog-of-perception. Rationale: two occlusion systems with different subjects — perception/fog governs what the CHARACTER knows (server-enforced info boundary; client only receives observer-filtered geometry), cutaway governs what the PLAYER''S CAMERA sees (pure render mode on wall data the client already holds). They compose: cutaway cuts render height; fog overlay still gates what is visible behind. Walls remain first-class client data objects (future destruction/explosion interactions), cutaway is a render mode on them like tint/LOD. Nuance parked for Phase 5 proper: destroyed-while-unobserved walls should render in remembered state (fog-memory applied to geometry). If this holds through implementation, mint a D-record (perception domain) when the sidequest lands. + +Animation supply resolved (2026-07-06): user purchased UAL1[Source+Pro] (120 clips, identical lists) + UAL2[Source] (134 clips), unzipped in docs/assets/downloads/. Gains: Turn90_L/R + Turn180_L/R (turn-in-place), Sprint_Enter/Exit + Crouch_Enter/Exit (stance transitions), full 8-direction Walk/Jog/Crouch sets incl. diagonals, Counter_Idle + talking/phone/sitting idles (life-sim verbs), plus _RM root-motion variants (unused — in-place is correct for server-authoritative interpolation). Still absent everywhere: walk start/stop + stairs — the only remaining custom clips. Integration is NOT a drop-in swap: upstream renamed clips (Walk_Loop -> Walk_Fwd_Loop etc.), so wire via the design''s recorded plan — explicit library names + lib/Clip addressing + gait-table update. NOTE for facing design: the 8-dir walk sets unlock Q-084''s parked walk-vs-aim split (direction-matched clips relative to facing) — revisit during the S9 tuning session.', 'Fable-5-window sidequest (sanctioned cascade exception, 2026-07-05): the presentation layer that turns discrete server tile steps into a believable walking 3D character. Scope: 3D sandbox dev scene (SR_LIVE=1, greybox tiles derived from server snapshots, no hand-authored map), CharacterVisual instantiated in-world, locomotion rig (tile-step + facing-octant interpolation, stance-aware speeds), UAL animation state machine (idle/walk/sprint/careful/crouch), D-148 camera rig (30deg low-angle follow, 45deg rotation). Server-authoritative from day one — no client-local movement model. Builds on D-148/D-149/D-244/D-066 and the T-417 stance system. If the Fable 5 window closes mid-flight, work parks here. + +Visual reference (user, 2026-07-06): Xenonauts 2 nails the target tiles/occlusion/character perspective — readable tile grid, low-angle tactical camera with angle options (matches D-148 30deg low-angle + 45deg rotation steps and the spikes/3dpipeline angle presets), and crucially its wall/roof occlusion treatment: camera-facing walls cut down/ghosted so interiors stay readable, floors peel per z-level. One deliberate difference: our camera locks to the player character (D-015/T-116) — no free map panning. Implication for the locomotion sandbox: greybox walls should prototype camera-facing cutaway occlusion early, since wall readability interacts with the locked low-angle camera. + +Scoping call (user + lead agreement, 2026-07-06): wall cutaway is CLIENT-SIDE presentation, decoupled from fog-of-perception. Rationale: two occlusion systems with different subjects — perception/fog governs what the CHARACTER knows (server-enforced info boundary; client only receives observer-filtered geometry), cutaway governs what the PLAYER''S CAMERA sees (pure render mode on wall data the client already holds). They compose: cutaway cuts render height; fog overlay still gates what is visible behind. Walls remain first-class client data objects (future destruction/explosion interactions), cutaway is a render mode on them like tint/LOD. Nuance parked for Phase 5 proper: destroyed-while-unobserved walls should render in remembered state (fog-memory applied to geometry). If this holds through implementation, mint a D-record (perception domain) when the sidequest lands. + +Animation supply resolved (2026-07-06): user purchased UAL1[Source+Pro] (120 clips, identical lists) + UAL2[Source] (134 clips), unzipped in docs/assets/downloads/. Gains: Turn90_L/R + Turn180_L/R (turn-in-place), Sprint_Enter/Exit + Crouch_Enter/Exit (stance transitions), full 8-direction Walk/Jog/Crouch sets incl. diagonals, Counter_Idle + talking/phone/sitting idles (life-sim verbs), plus _RM root-motion variants (unused — in-place is correct for server-authoritative interpolation). Still absent everywhere: walk start/stop + stairs — the only remaining custom clips. Integration is NOT a drop-in swap: upstream renamed clips (Walk_Loop -> Walk_Fwd_Loop etc.), so wire via the design''s recorded plan — explicit library names + lib/Clip addressing + gait-table update. NOTE for facing design: the 8-dir walk sets unlock Q-084''s parked walk-vs-aim split (direction-matched clips relative to facing) — revisit during the S9 tuning session. + +Live-session feedback (user, 2026-07-06): (1) Corridor cutaway verdict — two minds: ''extremely trippy, could be the game''s own look'' vs ''player cannot see what is in the room unless close''. Resolution: cutaway MODES, toggled live in the sandbox (corridor / bottom-walls-low / both) — bottom-wall rule is data-driven (wall drops to stub when a KNOWN floor tile sits on its camera-far side; camera-facing directions derived from WorldRoot basis at runtime, not hand-derived). Default picked by feel during the session; the trippy corridor stays available as a signature-look option. (2) NEW: mouse-over move-here marker on the hovered tile + optimal path line — client-side A* over the never-evict KNOWN tile store (info-boundary-honest: the character plans only through known tiles; fog is unpathable), 8-dir with diagonal step cost 1 (time-optimal per the no-sqrt2 wire), terrain-cost provider seam (uniform today, Phase-4 terrain later), click executes by streaming ordinary Move* step commands through the existing throttle (zero protocol change, server validates every step), cancelled by WASD override / path invalidation / arrival. Settles more of Q-084 (click-to-move half).', NULL, '2026-07-06 17:12:09', '2026-07-06 17:12:09.008', '2026-07-06 17:12:09.008', NULL, 'c973d0c7b4460d39520d95a4693a87d0', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index a3697955f..cab71b37e 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -674,3 +674,12 @@ Scope guard (user concern, 2026-07-06): Synty intake must NOT displace the creat ROUTE DECISION (user, 2026-07-06, final): Synty intake REJECTED despite the spike''s technical PASS — (1) USD199.99/pack is too expensive; (2) Sidekick modularity is baked body-segment swap (clothes fused to body chunks), incompatible with our layered clothing-over-body system (11 bodies, skin tones, slot swapping); (3) user dislikes the Synty style. The spike still paid: transplant/batch scripts are source-agnostic and become the G1-family production pipeline; T-1090 (5 bodies broken bare) discovered; the Synty question is closed with evidence, not parked. OPERATIVE ROUTE: (b) derive from owned Fantasy parts + (c) hand-author — with a new preferred authoring technique: OFFSET-SHELL FROM OUR OWN BODY MESHES (extract body segment surface, offset outward into garment shell, cut neckline/hem/sleeves; weights inherited by construction, style fully ours, USD0). Suit + uniform remain the full hand-author proof. Purchases still open (separate concerns): UAL1/UAL2 Source ~USD30 for animation clips; Fantasy Outfits Source USD20 low-priority convenience. Chromakey garment-QA harness DELIVERED (2026-07-06, commits eaca6c8c4/74fa16260/b09a50efb): two-pass depth-proximity clip detection — pass A keys covered body segments magenta, pass B re-renders garment-only with a depth-only 3cm epsilon bias toward camera; analyzer splits key pixels into exposed_skin (informational — sleeveless arms, collars) vs clip_through (gating — skin within 3cm in front of cloth). Run: tooling/garment-qa/run-garment-qa . Verified on the peasant set (avg_m/f x Walk/Sprint/Crouch_Fwd x 3 frames x 4 yaws): 0/18 front-view false positives; genuine findings = sleeveless armhole seam (avg_f, sides), deep-crouch waist gap (worst 150px, back), collar nape. First real fit observations already on record for the peasant set. Sensitivity: clip_epsilon_m + --min-pixels, to calibrate against the first modern garments. This is the acceptance gate for every garment the wardrobe pipeline produces; pair with the T-1088 sandbox for in-world walk QA.', 'backlog', 'high', NULL, 'client', NULL, '2026-07-06 07:34:18.656', '2026-07-06 12:59:45.213', NULL, 'ffdb04fb1abf542f041161529a4a2f89', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FKCER95EK266H8RWPW8XT1F0', 'story', '06FB0TNSRXX29H5X0EB0F0GBW4', '3D character locomotion sidequest — composited character walks the Gauntlet on the live server', 'Fable-5-window sidequest (sanctioned cascade exception, 2026-07-05): the presentation layer that turns discrete server tile steps into a believable walking 3D character. Scope: 3D sandbox dev scene (SR_LIVE=1, greybox tiles derived from server snapshots, no hand-authored map), CharacterVisual instantiated in-world, locomotion rig (tile-step + facing-octant interpolation, stance-aware speeds), UAL animation state machine (idle/walk/sprint/careful/crouch), D-148 camera rig (30deg low-angle follow, 45deg rotation). Server-authoritative from day one — no client-local movement model. Builds on D-148/D-149/D-244/D-066 and the T-417 stance system. If the Fable 5 window closes mid-flight, work parks here. + +Visual reference (user, 2026-07-06): Xenonauts 2 nails the target tiles/occlusion/character perspective — readable tile grid, low-angle tactical camera with angle options (matches D-148 30deg low-angle + 45deg rotation steps and the spikes/3dpipeline angle presets), and crucially its wall/roof occlusion treatment: camera-facing walls cut down/ghosted so interiors stay readable, floors peel per z-level. One deliberate difference: our camera locks to the player character (D-015/T-116) — no free map panning. Implication for the locomotion sandbox: greybox walls should prototype camera-facing cutaway occlusion early, since wall readability interacts with the locked low-angle camera. + +Scoping call (user + lead agreement, 2026-07-06): wall cutaway is CLIENT-SIDE presentation, decoupled from fog-of-perception. Rationale: two occlusion systems with different subjects — perception/fog governs what the CHARACTER knows (server-enforced info boundary; client only receives observer-filtered geometry), cutaway governs what the PLAYER''S CAMERA sees (pure render mode on wall data the client already holds). They compose: cutaway cuts render height; fog overlay still gates what is visible behind. Walls remain first-class client data objects (future destruction/explosion interactions), cutaway is a render mode on them like tint/LOD. Nuance parked for Phase 5 proper: destroyed-while-unobserved walls should render in remembered state (fog-memory applied to geometry). If this holds through implementation, mint a D-record (perception domain) when the sidequest lands. + +Animation supply resolved (2026-07-06): user purchased UAL1[Source+Pro] (120 clips, identical lists) + UAL2[Source] (134 clips), unzipped in docs/assets/downloads/. Gains: Turn90_L/R + Turn180_L/R (turn-in-place), Sprint_Enter/Exit + Crouch_Enter/Exit (stance transitions), full 8-direction Walk/Jog/Crouch sets incl. diagonals, Counter_Idle + talking/phone/sitting idles (life-sim verbs), plus _RM root-motion variants (unused — in-place is correct for server-authoritative interpolation). Still absent everywhere: walk start/stop + stairs — the only remaining custom clips. Integration is NOT a drop-in swap: upstream renamed clips (Walk_Loop -> Walk_Fwd_Loop etc.), so wire via the design''s recorded plan — explicit library names + lib/Clip addressing + gait-table update. NOTE for facing design: the 8-dir walk sets unlock Q-084''s parked walk-vs-aim split (direction-matched clips relative to facing) — revisit during the S9 tuning session. + +Live-session feedback (user, 2026-07-06): (1) Corridor cutaway verdict — two minds: ''extremely trippy, could be the game''s own look'' vs ''player cannot see what is in the room unless close''. Resolution: cutaway MODES, toggled live in the sandbox (corridor / bottom-walls-low / both) — bottom-wall rule is data-driven (wall drops to stub when a KNOWN floor tile sits on its camera-far side; camera-facing directions derived from WorldRoot basis at runtime, not hand-derived). Default picked by feel during the session; the trippy corridor stays available as a signature-look option. (2) NEW: mouse-over move-here marker on the hovered tile + optimal path line — client-side A* over the never-evict KNOWN tile store (info-boundary-honest: the character plans only through known tiles; fog is unpathable), 8-dir with diagonal step cost 1 (time-optimal per the no-sqrt2 wire), terrain-cost provider seam (uniform today, Phase-4 terrain later), click executes by streaming ordinary Move* step commands through the existing throttle (zero protocol change, server validates every step), cancelled by WASD override / path invalidation / arrival. Settles more of Q-084 (click-to-move half).', 'backlog', 'high', NULL, 'client', NULL, '2026-07-06 07:01:06.475', '2026-07-06 17:12:09.007', NULL, '4286ff0868e7ab0aaafbaf2d183a6975', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/client/scripts/sandbox/greybox_world.gd b/client/scripts/sandbox/greybox_world.gd index 5d9d3c0f0..36b5c0c0f 100644 --- a/client/scripts/sandbox/greybox_world.gd +++ b/client/scripts/sandbox/greybox_world.gd @@ -155,6 +155,11 @@ var _free_slots: Array = [[], []] var _state_colors: Array[Color] = [] var _last_tick: int = -1 var _capacity_warned := false +## Cutaway mode: 0 = sightline corridor, 1 = bottom walls low, 2 = both. +var cutaway_mode: int = 0 +## Camera-far sim-neighbor deltas for the bottom-wall rule (see +## _compute_behind_dirs — derived from the live WorldRoot basis). +var _behind_dirs: Array[Vector3i] = [] func _ready() -> void: @@ -190,7 +195,12 @@ func _ready() -> void: _wall_material.set_shader_parameter("u_cut_height", SandboxConstants.CUT_HEIGHT) _wall_material.set_shader_parameter("u_corridor_half_w", SandboxConstants.CUT_CORRIDOR_HALF_W) _wall_material.set_shader_parameter("u_cut_band", SandboxConstants.CUT_BAND) - _wall_mm = _make_layer("WallMM", wall_mesh, _wall_material) + _wall_mm = _make_layer("WallMM", wall_mesh, _wall_material, true) + # Camera-far neighbor directions for the bottom-wall rule — derived from the + # actual WorldRoot basis at runtime (never hand-derived; the E/W-mirror + # lesson). A wall is a "bottom wall" when a KNOWN non-wall tile sits on its + # camera-far side. Deferred: global_transform is not final until tree-ready. + _compute_behind_dirs.call_deferred() ## Push the world->sim transform into the floor grid shader (deferred from @@ -227,13 +237,17 @@ func set_cutaway_char_pos(world_pos: Vector3, reach_m: float) -> void: _wall_material.set_shader_parameter("u_cut_reach", reach_m) -func _make_layer(layer_name: String, mesh: Mesh, material: ShaderMaterial) -> MultiMeshInstance3D: +func _make_layer( + layer_name: String, mesh: Mesh, material: ShaderMaterial, with_custom := false +) -> MultiMeshInstance3D: var mm := MultiMesh.new() # Format flags must be set before instance_count (buffer layout is fixed at # allocation). use_colors routes set_instance_color() to the shaders' COLOR - # built-in, which multiplies albedo (§3.2). + # built-in, which multiplies albedo (§3.2). use_custom_data (walls only) + # carries the bottom-wall flag to INSTANCE_CUSTOM.r for the cutaway modes. mm.transform_format = MultiMesh.TRANSFORM_3D mm.use_colors = true + mm.use_custom_data = with_custom mm.mesh = mesh mm.instance_count = INSTANCE_CAPACITY for i in INSTANCE_CAPACITY: @@ -267,6 +281,63 @@ func _paint_new(key: Vector3i) -> void: mm.set_instance_transform(slot, _tile_xform(key, kind)) mm.set_instance_color(slot, _state_colors[store.state_of(key)]) _slots[key] = Vector2i(kind, slot) + if kind == Store.Kind.WALL: + _refresh_bottom_flag(key) + else: + # A newly-learned floor can promote camera-ward walls to bottom walls. + for d in _behind_dirs: + _refresh_bottom_flag(key - d) + + +## Cutaway mode (design amendment, live session 2026-07-06): 0 = sightline +## corridor, 1 = bottom walls low, 2 = both. Pushed to the wall shader. +func set_cutaway_mode(mode: int) -> void: + cutaway_mode = mode % 3 + _wall_material.set_shader_parameter("u_cutaway_mode", cutaway_mode) + print("GreyboxWorld: cutaway mode %d (%s)" % [ + cutaway_mode, ["sightline corridor", "bottom walls low", "both"][cutaway_mode] + ]) + + +# Dev affordance (same pattern as the camera's T tilt cycle): C cycles the +# cutaway mode live so the corridor-vs-bottom-walls call is made by feel. +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventKey: + var key := event as InputEventKey + if key.pressed and not key.echo and key.physical_keycode == KEY_C: + set_cutaway_mode(cutaway_mode + 1) + get_viewport().set_input_as_handled() + + +## World-space camera-far sim directions (as tile-key deltas): the wall face +## pointing toward the camera hides the tile at key + behind_dir. Computed from +## this node's global basis so the WorldRoot rotation can never desync it. +func _compute_behind_dirs() -> void: + _behind_dirs.clear() + for d: Vector3i in [Vector3i(1, 0, 0), Vector3i(-1, 0, 0), Vector3i(0, 1, 0), Vector3i(0, -1, 0)]: + # Sim x -> local X, sim y -> local Z (SandboxSpace convention). + var world_dir := global_transform.basis * Vector3(float(d.x), 0.0, float(d.y)) + # Camera looks world -Z, so a camera-ward face points +Z; the tile it + # hides lies OPPOSITE that face -> behind_dir = -d for camera-ward d. + if world_dir.z > 0.01: + _behind_dirs.append(-d) + # Repaint flags for walls painted before the deferred computation ran. + for key: Vector3i in _slots: + if (_slots[key] as Vector2i).x == Store.Kind.WALL: + _refresh_bottom_flag(key) + + +func _refresh_bottom_flag(key: Vector3i) -> void: + var slot: Vector2i = _slots.get(key, Vector2i(-1, -1)) + if slot.y < 0 or slot.x != Store.Kind.WALL: + return + var flag := 0.0 + for d in _behind_dirs: + var n: Vector3i = key + d + if store.has_tile(n) and store.kind_of(n) != Store.Kind.WALL: + flag = 1.0 + break + _wall_mm.multimesh.set_instance_custom_data(slot.y, Color(flag, 0.0, 0.0, 0.0)) func _repaint_color(key: Vector3i) -> void: diff --git a/client/shaders/sandbox/greybox_wall_cutaway.gdshader b/client/shaders/sandbox/greybox_wall_cutaway.gdshader index 9474d86e4..882dc98b5 100644 --- a/client/shaders/sandbox/greybox_wall_cutaway.gdshader +++ b/client/shaders/sandbox/greybox_wall_cutaway.gdshader @@ -33,13 +33,20 @@ uniform float u_cut_height = 0.75; // SandboxConstants.CUT_HEIGHT — the uniform float u_corridor_half_w = 1.4; // SandboxConstants.CUT_CORRIDOR_HALF_W uniform float u_cut_band = 0.75; // SandboxConstants.CUT_BAND — falloff band uniform float u_cut_reach = 4.4; // per-frame: WALL_H / tan(camera pitch) +// 0 = sightline corridor, 1 = bottom walls low, 2 = both (live-toggled, C key). +uniform int u_cutaway_mode = 0; uniform vec3 u_base_albedo : source_color = vec3(0.66, 0.64, 0.60); varying vec3 v_world_pos; +// Bottom-wall flag (INSTANCE_CUSTOM.r, set by GreyboxWorld): 1.0 when a KNOWN +// non-wall tile sits on this wall's camera-far side — the wall would occlude +// interior the player should see, X2-style. +varying flat float v_bottom; void vertex() { // MODEL_MATRIX folds in the per-instance MultiMesh transform. v_world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + v_bottom = INSTANCE_CUSTOM.r; } void fragment() { @@ -50,7 +57,14 @@ void fragment() { float near_f = smoothstep(-0.25, 0.75, to_wall.y); // Far gate: corridor ends where a WALL_H wall can no longer occlude. float far_f = 1.0 - smoothstep(u_cut_reach, u_cut_reach + u_cut_band, to_wall.y); - float allowed = mix(u_wall_h, u_cut_height, lateral * near_f * far_f); + float corridor_f = lateral * near_f * far_f; + float cut_f = corridor_f; + if (u_cutaway_mode == 1) { + cut_f = v_bottom; + } else if (u_cutaway_mode == 2) { + cut_f = max(corridor_f, v_bottom); + } + float allowed = mix(u_wall_h, u_cut_height, cut_f); if (v_world_pos.y > allowed) { discard; }