When a replacement part with a caseLockGroup is grabbed, sibling parts
sharing the same group become non-interactable and ghosted (35% opacity)
until the held part is released. This implements the pylon cable choice
where the player picks either cable1 or cable2 (both valid) without
being able to grab both simultaneously.
- GrabbableObject: add disabled prop (skips interaction frame logic and
unmounts InteractableObject so it does not register with the manager)
and onGrabChange callback fired on press, release, hand grab, and hand
release. Force-releases when disabled becomes true mid-grab.
- SimpleModel: add opacity prop, traversed onto cloned mesh materials
(safe because cloneResources clones materials per instance).
- RepairObjectModel: forward ghosted prop as opacity 0.35.
- RepairRepairingStep: track heldPartByLockGroup and pass disabled +
ghosted to siblings of the currently held part.
- Ebike replacement parts: cooling core (correct, anchored at refroidisseur)
+ four distractors anchored at cabledroit/cablegauche/pucehaut/pucebas.
Removes the ad-hoc gant_l/talkie distractors in favor of consistent
case-anchored visuals.
- Pylon replacement parts: cable1 + cable2 (alternative correct, both with
caseLockGroup 'pylon-cable' for upcoming soft-lock) + refroidisseur and
two puce distractors anchored to packderelance.
- Farm replacement parts kept as-is (caseAnchor undefined falls back to
placeholder slot positions for backward compatibility).
- RepairGame threads anchors from RepairCaseModel through RepairMissionCase
to RepairRepairingStep; replacement-part initial position now resolves
to the anchor world position when caseAnchor is set, falling back to the
legacy slot index otherwise.
- RepairMissionConfig.requiredReplacementPartId (string) is replaced by
requiredReplacementPartIds (readonly string[]) so a mission can accept
several alternative correct parts (e.g. pylon will accept either cable).
- RepairMissionPartConfig gains optional caseAnchor (where the standalone
spawns inside packderelance), caseLockGroup (mutually exclusive parts),
and targetNodeName (snap onto a node of the broken model rather than a
placeholder slot in the case).
- RepairScannedBrokenPart gains targetNodeName so scan results can carry
this hint through to the repairing step.
- RepairRepairingStep validation logic (placed/wrong/feedback) now matches
any id in requiredReplacementPartIds. Existing data is migrated mechanically
(single-element arrays); part-level new fields are wired in subsequent
commits.
- Fix REPAIR_CASE_LID_NODE_NAME from "partiesup" to "partsup" (the actual
node name in packderelance.gltf), restoring the lid open/close animation
that was silently no-oping since introduction.
- Add REPAIR_CASE_PART_ANCHOR_NAMES (cabledroit, cablegauche, pucehaut,
pucebas, refroidisseur) and REPAIR_CASE_PART_ANCHOR_FALLBACKS for
case-local positions used when the GLTF lacks a node (refroidisseur).
- RepairCaseModel now resolves these anchor nodes on mount, hides existing
meshes underneath them, and creates lightweight Object3D placeholders for
missing names so the anchoring pipeline is uniform.
- Each frame, anchor world positions are converted to step-local space and
emitted via the new onAnchorsChange callback (debounced via signature like
placeholders). Consumers added in subsequent commits.