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) |
|
| [`sheet_npc.jpg`](sheet_npc.jpg) |
|
| [`sheet_npc_baseline.jpg`](sheet_npc_baseline.jpg) |
+|
| [`sheet_npc_baseline_compare-p1.jpg`](sheet_npc_baseline_compare-p1.jpg) |
|
| [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
|
| [`sheet_props.jpg`](sheet_props.jpg) |
|
| [`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.