diff --git a/CHANGELOG.md b/CHANGELOG.md index bb35e91..a8e629c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable user-facing changes to Gig Line. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use Semantic Versioning. +## [Unreleased] + +### Fixed + +- Tourists no longer appear to carry two backpacks: the photo-pose camera bag is + hidden except while they are actually taking a picture. +- Backpack shoulder straps are now flattened bands that sit outside the chest + instead of thin rods partly buried in the torso. + ## [0.9.3] — 2026-11-02 ### Added diff --git a/MEMORY.md b/MEMORY.md index 000f384..b0daa58 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -66,8 +66,10 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o crowd). It uses a fixed orbit camera, mid-grey studio backdrop, key/fill lights, no HUD/crowd/animation; raw frames stay in `tmp/`, and `tools/npc_baseline.py sheet` builds the committed `sheet_npc_baseline.jpg`. Criteria and measurements are in `docs/npc-visual-review.md`. The first - ranked gaps are: tourist photo prop duplicates the backpack silhouette; shallow/faceted face; - backpack straps read as rigid bars. No model/GLB changes were made in the baseline step. + ranked gaps were: tourist photo prop duplicates the backpack silhouette; shallow/faceted face; + backpack straps read as rigid bars. The tourist prop now hides outside photo/film poses and the + backpack straps were refit as flat forward bands (GLB + AO rebuilt); the face remains P1. Before/ + after sheet: `sheet_npc_baseline_compare.jpg`. - **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and exercises inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6 @@ -741,7 +743,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o - Venue `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI. - Access loadout is implemented; do not re-fix `override_access` as if it were still unread. - `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`. -- Headless suite is **261/261**; Python tooling is **99/99**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`. +- Headless suite is **262/262**; Python tooling is **99/99**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`. - Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args). - GDScript inference trap that broke the main scene: a `var` typed as a base class (`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a diff --git a/TODO.md b/TODO.md index 800703e..a3e9990 100644 --- a/TODO.md +++ b/TODO.md @@ -7,7 +7,7 @@ see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these Baseline: **v0.9.3** (2026-11-02). Three playable venues, tutorial/campaign/checkpoint resume, difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete -escalation/access/radio loop, shift review, the Blender asset library, 261 headless + 99 Python +escalation/access/radio loop, shift review, the Blender asset library, 262 headless + 99 Python tests, a 21/21 clean screen-design judge and an optional Forward+ Desktop HQ build. This is a strong release-candidate foundation, not yet a 0.10.0 release candidate. @@ -76,8 +76,9 @@ fallback behavior. the side-by-side after sheet without overwriting it.) Completed 2026-11-02: 48-frame neutral studio baseline, eight criteria, current fit and -triangle/vertex measurements, and ranked P1/P2 gaps are in `docs/npc-visual-review.md`. No -character model was changed in this pass. +triangle/vertex measurements, and ranked P1/P2 gaps are in `docs/npc-visual-review.md`. The +baseline itself changed no character models; the first P1 gear fixes are recorded under Gear +and equipment fit. ### Body proportions and silhouette @@ -126,7 +127,11 @@ character model was changed in this pass. head-band, skirt/hip clearance and inner-face clearances. - [ ] Re-fit caps, beanie, hood, sunglasses and firefighter helmet against the refined face/hair; ensure eyebrow/ear clearance and a readable face with the hood up. -- [ ] Re-fit backpack, shoulder bag and SCBA tank against the revised torso/hips; fix strap and +- [x] Hide the tourist's photo-pose camera bag outside `photo`/`film` poses; it duplicated the + backpack silhouette. (`photo_prop_visible_for_pose()` plus regression test.) +- [x] Refit the backpack straps as flattened bands that follow the torso instead of rods partly + buried in the chest; rebuild the GLB, bake AO and confirm in the before/after sheet. +- [ ] Re-fit shoulder bag and SCBA tank against the revised torso/hips; fix remaining strap and tank intersections and keep attachments outside the body silhouette. - [ ] Verify lanyards, radios, phones, cups, clipboards, umbrellas, cameras, protest signs, flashlights and wands sit correctly in the hand/grip and do not float. diff --git a/assets/models/blender/manifest.json b/assets/models/blender/manifest.json index 9c4106a..ebafaf9 100644 --- a/assets/models/blender/manifest.json +++ b/assets/models/blender/manifest.json @@ -29,8 +29,8 @@ "bounds_godot": { "max": [ 0.15, - 0.3, - 0.141 + 0.297, + 0.214 ], "min": [ -0.15, @@ -45,8 +45,8 @@ "plastic_black" ], "path": "assets/models/blender/npc_gear/backpack.glb", - "triangles": 316, - "vertices": 360 + "triangles": 380, + "vertices": 406 }, "beanie": { "attach": "head", diff --git a/assets/models/blender/npc_gear/backpack.glb b/assets/models/blender/npc_gear/backpack.glb index 0471f02..53c0f3c 100644 Binary files a/assets/models/blender/npc_gear/backpack.glb and b/assets/models/blender/npc_gear/backpack.glb differ diff --git a/assets/models/blender/npc_gear/backpack_ao.png b/assets/models/blender/npc_gear/backpack_ao.png index 89271c9..f333c63 100644 Binary files a/assets/models/blender/npc_gear/backpack_ao.png and b/assets/models/blender/npc_gear/backpack_ao.png differ diff --git a/docs/blender-assets.md b/docs/blender-assets.md index 6458087..f75ef56 100644 --- a/docs/blender-assets.md +++ b/docs/blender-assets.md @@ -200,7 +200,8 @@ carry baked AO and the shared neutral material maps described above. the legs through the hem. - Preview colours (`PREVIEW_TINTS`) only stand in for `outfit_for()`. The GLBs stay neutral. -- The 0.9.4 baseline found three P1 portrait issues: the tourist's photo prop - duplicates the backpack silhouette, the face is shallow/faceted at portrait - range, and the backpack straps read as rigid bars. The full ranked list and - accepted/deferred items are in [`npc-visual-review.md`](npc-visual-review.md). +- The 0.9.4 baseline found three P1 portrait issues. The tourist's photo prop + now only appears in photo/film poses and the backpack straps were refit as + flattened chest bands; the face is still shallow/faceted at portrait range. + The full ranked list, measurements and before/after sheet are in + [`npc-visual-review.md`](npc-visual-review.md). diff --git a/docs/blender_previews/README.md b/docs/blender_previews/README.md index bcc91a2..0c553ab 100644 --- a/docs/blender_previews/README.md +++ b/docs/blender_previews/README.md @@ -44,6 +44,7 @@ Committed contact sheets and local Blender renders for the character, gear and p | sedan.png | [`sedan.png`](sedan.png) | | sheet_npc.jpg | [`sheet_npc.jpg`](sheet_npc.jpg) | | sheet_npc_baseline.jpg | [`sheet_npc_baseline.jpg`](sheet_npc_baseline.jpg) | +| sheet_npc_baseline_compare-p1.jpg | [`sheet_npc_baseline_compare-p1.jpg`](sheet_npc_baseline_compare-p1.jpg) | | sheet_npc_gear.jpg | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) | | sheet_props.jpg | [`sheet_props.jpg`](sheet_props.jpg) | | shoulder_bag.png | [`shoulder_bag.png`](shoulder_bag.png) | diff --git a/docs/blender_previews/sheet_npc_baseline_compare-p1.jpg b/docs/blender_previews/sheet_npc_baseline_compare-p1.jpg new file mode 100644 index 0000000..9c712c8 Binary files /dev/null and b/docs/blender_previews/sheet_npc_baseline_compare-p1.jpg differ diff --git a/docs/development-status.md b/docs/development-status.md index 09f2a1f..278e56d 100644 --- a/docs/development-status.md +++ b/docs/development-status.md @@ -5,7 +5,7 @@ Implemented so far: - Godot 4.7.2 project scaffold, input map, procedural world builder (CI/local/release unified) - Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem` - Three authored scenarios (El Capitan Entertainment Centre / Hollywood Boulevard, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools -- Headless unit tests: **261 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings) +- Headless unit tests: **262 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings) - Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event - First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review - Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`) @@ -39,6 +39,7 @@ Implemented so far: - Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work - Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks. +- NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the face and remaining clothing/hand work stay open. - NPC visual baseline (0.9.4 work): `src/test/capture_npc_baseline.gd` captures all eight library characters from front, three-quarter, profile, back, full-body and crowd views in a neutral studio (48 frames). The criteria, measurements and ranked gaps are in [`npc-visual-review.md`](npc-visual-review.md); the committed sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg). No model changes were made in this baseline step. - UI-state capture pass (0.9.3 work): `src/test/capture_ui_states.gd` adds committed captures 14–21 for the start menu, pause, settings, shift review, emergency radio, action wheel, crowd panic and responder arrival. Every one of the 21 catalogue entries now has a current-build capture; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge reports 21/21 match, 0 diff. Crowd panic and responder arrival are staged presentation frames, not gameplay changes. - Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts. diff --git a/docs/npc-visual-review.md b/docs/npc-visual-review.md index 7572e3b..7dbd5eb 100644 --- a/docs/npc-visual-review.md +++ b/docs/npc-visual-review.md @@ -1,8 +1,10 @@ # NPC visual baseline and acceptance criteria -Status: baseline captured 2026-11-02. No character GLB, texture, gear or runtime -mesh was changed in this step. The baseline is the reference for the 0.9.4 NPC -visual optimization. +Status: baseline captured 2026-11-02; the first P1 fixes were applied the same +day (tourist photo-prop visibility and backpack strap geometry). The face pass +and the remaining body/clothing work are still open. The baseline remains the +reference for the 0.9.4 NPC visual optimization; the post-fix comparison is +[`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg). ## What was captured @@ -99,27 +101,32 @@ From `python3 tools/blender_assets.py fit` and the manifest: The widest character is `npc_coat` at 5,026 triangles and 4,807 vertices, so the current headroom is 974 triangles and 193 vertices per character. Any face -or hand refinement must come out of that budget or replace existing detail. +or hand refinement must come out of that budget or replace existing detail. The +refit backpack measures 380 triangles / 406 vertices; the character and gear +table above remains valid because the character meshes did not change. ## Baseline findings, ranked ### P1 — fix in the first model pass -1. **The tourist carries two bag silhouettes.** `_add_props()` creates a - 0.30 × 0.36 × 0.14 box at `(-0.34, 1.15, -0.18)` for the tourist photo pose - while `_add_accessory("backpack")` also attaches the library backpack. From - the three-quarter/back view the two read as separate bags. The photo prop - should be hidden outside the photo pose, merged into the backpack, or moved to - a clearly held camera. +1. **The tourist carried two bag silhouettes. Fixed 2026-11-02.** `_add_props()` + created a 0.30 × 0.36 × 0.14 box at `(-0.34, 1.15, -0.18)` for the tourist + photo pose while `_add_accessory("backpack")` also attached the library + backpack. From the three-quarter/back view the two read as separate bags. The + photo prop is now hidden outside `photo`/`film` poses + (`photo_prop_visible_for_pose()`), so it only appears when the character is + actually taking a picture; the after frame confirms one bag at rest. 2. **The face is the largest portrait gap.** The eyes, nose and lips are readable but sit on a shallow, faceted head: the eye whites protrude, the eyelid/cheek transition is missing, and the mouth is a dark slot. This is intentionally stylized, but the head volume and eye socket are the first things a player sees at conversation range. -3. **Backpack straps read as rigid black bars.** The back gear's straps cross - the chest as thin, straight geometry with visible gaps from the shoulders, - not as fabric sitting on the torso. A player sees this on the most common - tourist silhouette in every crowd. +3. **Backpack straps read as rigid black bars. Refit 2026-11-02.** The straps + were thin round tubes whose chest/waist points sat inside the torso, so only + fragments showed from the front. They are now flattened bands whose front path + sits outside the chest; the after sheet shows a continuous strap instead of + two detached rods. The backpack asset is now 380 triangles / 406 vertices + (still far below the 1,500-triangle gear budget). ### P2 — fix after the first P1 pass @@ -167,17 +174,17 @@ python3 tools/npc_baseline.py compare \ ``` The comparison sheet places the before/after pair for each view side by side. -If the change is accepted, copy the after sheet to a new committed name -(`sheet_npc_baseline_after.jpg`) and link both from the preview index. The -baseline is not replaced. +The first run is committed as +[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); +keep the baseline sheet as the before artifact and add a new numbered/after +sheet rather than replacing it. ## Next implementation order -1. Remove the duplicate tourist bag silhouette and refit the backpack straps. -2. Refine the head/eye socket/jaw volume without changing the face direction or +1. Refine the head/eye socket/jaw volume without changing the face direction or the five tint slots. -3. Add a conversation-distance hand/grip capture and fix hand geometry before +2. Add a conversation-distance hand/grip capture and fix hand geometry before judging hand items. -4. Audit hems and footwear in walk/sit/block poses. -5. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC - budget and `blender_assets.py audit/fit`; publish the before/after sheet. +3. Audit hems and footwear in walk/sit/block poses. +4. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC + budget and `blender_assets.py audit/fit`; publish the next comparison sheet. diff --git a/src/game/npc_avatar.gd b/src/game/npc_avatar.gd index c04db84..7825e78 100644 --- a/src/game/npc_avatar.gd +++ b/src/game/npc_avatar.gd @@ -114,6 +114,7 @@ var _focus_circle: MeshInstance3D = null var _name_tag: Label3D = null ## Theater props and limbs for the idle poses (ADR-0010 rig). var _photo_prop: MeshInstance3D = null +var _photo_flash: MeshInstance3D = null var _sign_prop: MeshInstance3D = null var _legs: Array[Node3D] = [] var _arms: Array[Node3D] = [] @@ -891,8 +892,11 @@ func _add_props(npc_type: int) -> void: match npc_type: Rules.NpcType.INFLUENCER: _photo_prop = _add_box_prop(_rig, Vector3(0.16, 0.1, 0.09), Vector3(0.3, chest_y, 0.22), Color(0.1, 0.1, 0.1)) + _photo_prop.name = "PhotoProp" + _photo_prop.visible = false _photo_prop_base_y = _photo_prop.position.y - _add_box_prop(_rig, Vector3(0.05, 0.05, 0.06), Vector3(0.34, chest_y + 0.06, 0.30), Color(0.4, 0.7, 1.0)) + _photo_flash = _add_box_prop(_rig, Vector3(0.05, 0.05, 0.06), Vector3(0.34, chest_y + 0.06, 0.30), Color(0.4, 0.7, 1.0)) + _photo_flash.visible = false Rules.NpcType.DELIVERY: _add_box_prop(_rig, Vector3(0.34, 0.26, 0.28), Vector3(0.0, 1.02, 0.3), Color(0.55, 0.4, 0.25)) Rules.NpcType.PERFORMER: @@ -917,6 +921,8 @@ func _add_props(npc_type: int) -> void: _sign_prop_base_y = _sign_prop.position.y Rules.NpcType.TOURIST: _photo_prop = _add_box_prop(_rig, Vector3(0.3, 0.36, 0.14), Vector3(-0.34, 1.15, -0.18), Color(0.25, 0.3, 0.45)) + _photo_prop.name = "PhotoProp" + _photo_prop.visible = false _photo_prop_base_y = _photo_prop.position.y Rules.NpcType.CONFUSED_VISITOR: _add_box_prop(_rig, Vector3(0.3, 0.05, 0.34), Vector3(0, 0.92, 0.06), Color(0.9, 0.88, 0.8)) @@ -1042,6 +1048,12 @@ static func prop_lift_for_pose(pose: String, phase: float) -> float: return 0.0 return maxf(0.0, sin(phase * 1.7)) * 0.16 +## The tourist's camera bag and the influencer's chest camera are only part of +## the silhouette while the character is actually taking photos. Keeping them +## hidden at other times prevents the tourist from reading as two backpacks. +static func photo_prop_visible_for_pose(pose: String) -> bool: + return pose == "photo" or pose == "film" + ## Opposite arm/leg swing for the walk cycle. `side` is 0 or 1; pure so the gait ## can be checked without a scene tree. static func limb_swing(phase: float, side: int, amplitude: float) -> float: @@ -1060,6 +1072,10 @@ func _animate(delta: float) -> void: var pose := theater_pose() var escalation: int = npc.highest_escalation_received if npc != null else 0 var crouch := crouch_for_pose(pose) + if _photo_prop: + _photo_prop.visible = photo_prop_visible_for_pose(pose) + if _photo_flash: + _photo_flash.visible = photo_prop_visible_for_pose(pose) if speed > 0.3: _idle_t = 0.0 _bob_phase += delta * (6.0 + speed * 1.7) diff --git a/src/test/tests/test_npc_character.gd b/src/test/tests/test_npc_character.gd index c792e0c..68bdf37 100644 --- a/src/test/tests/test_npc_character.gd +++ b/src/test/tests/test_npc_character.gd @@ -47,6 +47,14 @@ func test_hand_items_are_known_and_deterministic() -> bool: Rules.NpcType.TOURIST, seed_value * 31) return TestUtils.check(ok, "hand items stay in the catalogue and are deterministic") +func test_photo_prop_only_shows_for_camera_poses() -> bool: + return TestUtils.check( + not NpcAvatar.photo_prop_visible_for_pose("idle") + and not NpcAvatar.photo_prop_visible_for_pose("perform") + and NpcAvatar.photo_prop_visible_for_pose("photo") + and NpcAvatar.photo_prop_visible_for_pose("film"), + "camera bag prop is hidden outside photo/film poses") + func test_idle_styles_are_known() -> bool: var allowed := ["phone", "crossed", "look", "clipboard", "breathe"] var ok := true diff --git a/tools/blender_assets.py b/tools/blender_assets.py index 241da5f..1dd54cd 100644 --- a/tools/blender_assets.py +++ b/tools/blender_assets.py @@ -412,8 +412,13 @@ def lathe(name, profile, mat, seg=16, loc=(0, 0, 0), smooth=True, sharp=30.0, return _finish(_link(name, bm), mat, smooth=smooth, sharp_angle=sharp) -def tube(name, points, radius, mat, seg=8, caps=True): - """Bent tube through 3D points (handrails, umbrella handles, frames).""" +def tube(name, points, radius, mat, seg=8, caps=True, width_scale=1.0, + thickness_scale=1.0): + """Bent tube through 3D points (handrails, umbrella handles, frames). + + `width_scale`/`thickness_scale` turn the round cross-section into a flat + strap for shoulder harnesses; the default keeps the old round tube. + """ bm = bmesh.new() rings = [] pts = [Vector(p) for p in points] @@ -430,7 +435,9 @@ def tube(name, points, radius, mat, seg=8, caps=True): ring = [] for k in range(seg): a = math.tau * k / seg - ring.append(bm.verts.new(p + radius * (math.cos(a) * side + math.sin(a) * up2))) + ring.append(bm.verts.new(p + radius * ( + math.cos(a) * side * width_scale + + math.sin(a) * up2 * thickness_scale))) rings.append(ring) for a, b in zip(rings, rings[1:]): for k in range(seg): @@ -1008,9 +1015,15 @@ def build_gear(name): bevel=0.02, segs=2)) p.append(box("Zip", (0.24, 0.005, 0.01), (0, 0.317, 0.05), M("steel"))) for s in (-1.0, 1.0): + # Strap path: over the shoulder, then down the chest outside the + # torso front (Godot z≈0.21). The old chest/waist points sat at + # z≈0.13 inside the body, so only thin tube fragments read as two + # rigid black bars from the front. p.append(tube("Strap", [(0.09 * s, 0.13, 0.27), (0.10 * s, -0.02, 0.29), - (0.11 * s, -0.13, 0.18), (0.12 * s, -0.12, -0.08)], - 0.012, M("plastic_black"), seg=6)) + (0.105 * s, -0.06, 0.25), + (0.11 * s, -0.19, 0.18), (0.12 * s, -0.19, -0.08)], + 0.018, M("plastic_black"), seg=8, width_scale=1.35, + thickness_scale=0.42)) elif name == "shoulder_bag": # Keep the bag's inner face outside the widest skirt flare (dress = 0.26): # 0.292 - 0.06/2 = 0.262. The strap starts/ends on that same outboard line.