Skip to content

Options-driven workflow: configurable paths, physics, richer FrameData JSONs, 0-based frames + GP/DK fixes - #2

Open
polynaut wants to merge 35 commits into
soltude:mainfrom
polynaut:feature/small-enhancements
Open

Options-driven workflow: configurable paths, physics, richer FrameData JSONs, 0-based frames + GP/DK fixes#2
polynaut wants to merge 35 commits into
soltude:mainfrom
polynaut:feature/small-enhancements

Conversation

@polynaut

@polynaut polynaut commented Jun 13, 2026

Copy link
Copy Markdown

Grows feature/small-enhancements beyond the original autoBase change (all done with claude.ai). Every new options.* falls back to the existing path/constant when unset — current workflows behave identically.

Options-driven — exact ROM paths (jcmRomPath/gpRomPath/…), per-character art-direction JSONs, and bIncludePhysics to append the G9 Physics ROM after GP/DK.

Richer FrameData JSONsbase/autoBase per morph; generation-method groups (additive/cumulative) that sustain instead of sawtooth; and a 0-based frame convention (gpFrameCount = 104, matching DK's 54) so post-GP sections land cleanly. ⚠️ external JSON generators must now emit 0-based — absolute PoseAsset layout unchanged (verified against a real ElectraG9 export).

PackagingApplyDTHCharacter(config): one call with config + all morph data inline, no extra files read from disk. ScanKeyFrames merged into DthWorkflow (one fewer runtime file).

Fixes — FlexionStrength wrote to the wrong property; DK trailing empty frame (dk9FrameCount 55→54); DS6 play-range/clamping prompts; restore initial selection after a run; reset Dicktator morphs alongside GP; debug CSVs now gated behind bWriteDebugCSV (default off).

Status — GP9/DQS is the validated path. DK is functional but not byte-hardened (runs end-to-end into Unreal; uncircumcised-foreskin path still a gap).

Polynaut and others added 13 commits June 12, 2026 14:05
Morphs in FrameData JSONs can carry base (explicit frame-0 value) and
autoBase (resolve the frame-0 value from the current scene state before
reset). Electra gains the InvictaCouture pressure morphs and the combined
SLGlutes frames; frame 6 switches to Booty Preset 04.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.gpArtDirectionPath / options.dkArtDirectionPath override the
hardcoded GP9_ArtDirection.json / DK9_ArtDirection.json next to the
scripts, falling back to those when unset. Generated per-character
wrappers (DTH Character Studio) use this to ship their own art direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The optional groups array (from DTH Character Studio) marks frame ranges
as additive or cumulative. Their keyframes sustain until the group end
instead of sawtoothing back: for additive only the base (first) pose
sustains while the rest sawtooth within; for cumulative every pose holds
until the group end, and props repeating inside a group ramp through their
values (AnusOpen 22/35/80 pattern) without re-anchoring to base.
advancedAdditive shapes the timeline like additive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.jcmRomPath/mouthRomPath short-circuit getRomPaths, and
options.gpRomPath/dkRomPath override the ROM_GP9/ROM_DK9 constants - all
falling back to the DTH_POSES_PATH resolution when unset, so existing
workflows are unaffected. Also turns the trailing comma after the
DTH_POSES_PATH declaration into a semicolon (invalid JS that QtScript
happened to tolerate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApplyPhysics loads the 43-frame G9 Physics Example ROM (offsets 0-42,
determined from its DSON keyframes) onto the base figure after the GP/DK
blocks, with options.physRomPath overriding the default location.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, refresh docs

Bug fix:
- ApplyInitialValues wrote options.FACsDetailStrength into
  body_ctrl_FlexionAutoStrength instead of options.FlexionStrength.

Hygiene:
- Gate the OUT*.csv debug dumps (frameDatas / success / failed / scanned) behind
  a new options.bWriteDebugCSV (default false) so normal runs no longer litter the
  scripts folder. The morph application itself is unchanged.
- Add .gitignore (.claude/, OUT*.csv) and untrack per-user .claude/settings.local.json.

Honesty:
- Mark the DicktatorG9 (ApplyDK9) path EXPERIMENTAL/INCOMPLETE with its known gaps
  (frame count, unused fence offsets, stale linear base) rather than shipping it as
  if finished. Logic left as-is.

Docs:
- Rewrite README: GP9 now works and DQS is the validated path (were marked
  not-working / untested); document the FrameData JSON format and the full options
  surface (romPaths, art-direction paths, physics, preserve*, jcmMorphMods,
  generation-method groups). Refresh the CLAUDE.md options list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DK status

The DicktatorG9 ROM is 54 frames (DK9.csv / DK9_ArtDirection.json are 0-53), but
dk9FrameCount was 55. setTotalFrames(startFrame + 55) reserved one frame past the
last pose, and since the next section derives its start from the timeline end, that
reserved frame surfaced as an empty gap once a section (e.g. Physics) was appended
after DK. Correcting it to 54 makes the next section start immediately after DK's
last pose.

Also reframe the ApplyDK9 banner + README from "experimental/incomplete" to
"functional, not yet hardened": DK has been run end-to-end with the morphs coming
through in Unreal, just without byte-level validation. Internal fences stay off
(tested fine without them); the uncircumcised foreskin path is the remaining gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Daz timelines are 0-based: frame 0 is the first frame, and each section's frame 0
is its own first frame. Make the framework consistent with that instead of relying
on two off-by-ones cancelling.

- ApplyGP9: gpFrameCount = 104 (the GP ROM's full frame count, rel 0-103), so the
  next section starts at rel 104 - the frame after ClitorisErect - exactly like DK
  (dk9FrameCount = 54). A loadPreset ROM block (Physics) and a 0-based extraJSONs
  section then both land cleanly at rel 104, with no overlap.
- Migrate the example FrameData JSONs (ElectraG9, MattG9) from 1-based to 0-based
  (first FBM frame 1 -> 0). The 1-based numbering only cancelled the older 103
  handoff; under 104 it would push the FBM block one frame too far.

Verified against a real ElectraG9 PoseAsset export: GP ClitorisErect at abs 431,
FBM BodyTone at abs 432. With gpFrameCount 104 and the 0-based JSON, BodyTone lands
at 432 + 0 = 432 - identical layout, now convention-consistent, and Physics-after-GP
no longer collides.

NOTE: any external generator of FrameData JSONs (the studio's toDazFbmJson) must
also emit 0-based. The absolute PoseAsset CSV layout is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ApplyDTHCharacter(config) to DthWorkflow.dsa: one call that takes a single
object carrying the full character config AND all ROM morph definitions inline,
maps it onto the shared options, and runs the normal workflow — no extra
.json/.csv files read from disk. Powers DTH Character Studio's one-file
<Name>_<Genesis>.dsa output.

To support inline data without changing the file-based behaviour:
- DthUtils: split readFrameDataJSON into a file reader + processFrameDataJSON
  (takes parsed data + optional GP art-direction data for the resetGP reset);
  split applyGPArtDirection into a file reader + applyArtDirectionData.
- DthWorkflow: CreateFrameDatas honours options.extraFrames (inline); ApplyGP9
  and ApplyDK9 prefer options.gpArtDirection / dkArtDirection over file paths.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline the keyframe-scanning functions (scanNodesForFrameDatas + helpers)
from ScanKeyFrames.dsa into DthWorkflow.dsa and drop the include, so the
runtime ships as one fewer file. Only reached via the bWriteDebugCSV path.
ScanKeyFrames.dsa stays as a standalone debug script.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- setTotalFrames now also calls Scene.setPlayRange so the timeline scrubber
  reaches the later frames (the FBM section after GP) instead of staying
  bounded to the previous play range — fixes "can only scrub to ~431".
- applyKeyData disables clamping on each morph property (setIsClamped(false),
  guarded) before setting values, so DAZ Studio 6 doesn't prompt "keep
  limits?" for FBM/custom morphs that exceed their authored range.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ApplyDTHWorkflow already captures the initial selection (aNodes / oNode) at
the start; on success (after setFrame(0), before acceptUndo) it now deselects
all nodes and reselects the original selection + primary, since loading the
ROM presets re-selects nodes along the way. Error paths return early and skip
this, leaving the scene as-is for debugging.

Lives in DthWorkflow.dsa (the shared runtime), so DthWorkflowElectraG9.dsa and
the studio-generated character scripts both pick it up via their include.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
processFrameDataJSON now resets whichever genital ROM's morphs are present
(Golden Palace and/or Dicktator) at the FBM block's first frame, reading the
matching sibling GP9/DK9 art-direction JSON or the inline gpArtDirection /
dkArtDirection; CreateFrameDatas passes options.dkArtDirection through. README
documents both meta flags (resetGPBeforeApplying / resetDKBeforeApplying).

Also clarify the include-path comment: getScriptFileName() resolves to the
invoking script's folder, not this file's.

DK path is functional but not byte-validated (see Status).
@polynaut polynaut changed the title Add autoBase morph support and update the Electra FBM frames Options-driven workflow: configurable paths, physics, richer FrameData JSONs, 0-based frames + GP/DK fixes Jun 20, 2026
polynaut and others added 16 commits June 26, 2026 08:58
Five fixes root-caused from a generated ROM script's log (kept in sync
with the DTH Character Studio bundled runtime copy):

- setFencePoses: replace `for…in` over the fence-frame array (which in
  Daz's engine also yields enumerable Array.prototype members, restoring
  the figure at `function f(){…}`/empty frames) with an indexed loop.
- getValueChannel(0) -> getValueChannel(): the method takes no args; the
  extra 0 logged "Too many arguments, ignoring 1" on every morph lookup.
- applyArtDirectionData: add a findProperty/findPropertyByLabel fallback
  (mirroring applyKeyData) so geo-graft alias morphs like Golden Palace
  GP_PR_* resolve instead of "Property not found".
- setPropertyByName: verify by reading the value back instead of trusting
  setValue's return, so a no-op (value already set) stops logging a false
  "Failed" (e.g. FACS Detail Strength / Flexion Automatic Strength).
- var-scope implicit globals (oProp/oMod/oMorph/oContentMgr), silencing
  the "used before declaration" warning.

Co-authored-by: Polynaut <polynaut.git@proton.me>
Morphs in FrameData JSONs can carry base (explicit frame-0 value) and
autoBase (resolve the frame-0 value from the current scene state before
reset). Electra gains the InvictaCouture pressure morphs and the combined
SLGlutes frames; frame 6 switches to Booty Preset 04.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.gpArtDirectionPath / options.dkArtDirectionPath override the
hardcoded GP9_ArtDirection.json / DK9_ArtDirection.json next to the
scripts, falling back to those when unset. Generated per-character
wrappers (DTH Character Studio) use this to ship their own art direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The optional groups array (from DTH Character Studio) marks frame ranges
as additive or cumulative. Their keyframes sustain until the group end
instead of sawtoothing back: for additive only the base (first) pose
sustains while the rest sawtooth within; for cumulative every pose holds
until the group end, and props repeating inside a group ramp through their
values (AnusOpen 22/35/80 pattern) without re-anchoring to base.
advancedAdditive shapes the timeline like additive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.jcmRomPath/mouthRomPath short-circuit getRomPaths, and
options.gpRomPath/dkRomPath override the ROM_GP9/ROM_DK9 constants - all
falling back to the DTH_POSES_PATH resolution when unset, so existing
workflows are unaffected. Also turns the trailing comma after the
DTH_POSES_PATH declaration into a semicolon (invalid JS that QtScript
happened to tolerate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApplyPhysics loads the 43-frame G9 Physics Example ROM (offsets 0-42,
determined from its DSON keyframes) onto the base figure after the GP/DK
blocks, with options.physRomPath overriding the default location.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, refresh docs

Bug fix:
- ApplyInitialValues wrote options.FACsDetailStrength into
  body_ctrl_FlexionAutoStrength instead of options.FlexionStrength.

Hygiene:
- Gate the OUT*.csv debug dumps (frameDatas / success / failed / scanned) behind
  a new options.bWriteDebugCSV (default false) so normal runs no longer litter the
  scripts folder. The morph application itself is unchanged.
- Add .gitignore (.claude/, OUT*.csv) and untrack per-user .claude/settings.local.json.

Honesty:
- Mark the DicktatorG9 (ApplyDK9) path EXPERIMENTAL/INCOMPLETE with its known gaps
  (frame count, unused fence offsets, stale linear base) rather than shipping it as
  if finished. Logic left as-is.

Docs:
- Rewrite README: GP9 now works and DQS is the validated path (were marked
  not-working / untested); document the FrameData JSON format and the full options
  surface (romPaths, art-direction paths, physics, preserve*, jcmMorphMods,
  generation-method groups). Refresh the CLAUDE.md options list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DK status

The DicktatorG9 ROM is 54 frames (DK9.csv / DK9_ArtDirection.json are 0-53), but
dk9FrameCount was 55. setTotalFrames(startFrame + 55) reserved one frame past the
last pose, and since the next section derives its start from the timeline end, that
reserved frame surfaced as an empty gap once a section (e.g. Physics) was appended
after DK. Correcting it to 54 makes the next section start immediately after DK's
last pose.

Also reframe the ApplyDK9 banner + README from "experimental/incomplete" to
"functional, not yet hardened": DK has been run end-to-end with the morphs coming
through in Unreal, just without byte-level validation. Internal fences stay off
(tested fine without them); the uncircumcised foreskin path is the remaining gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Daz timelines are 0-based: frame 0 is the first frame, and each section's frame 0
is its own first frame. Make the framework consistent with that instead of relying
on two off-by-ones cancelling.

- ApplyGP9: gpFrameCount = 104 (the GP ROM's full frame count, rel 0-103), so the
  next section starts at rel 104 - the frame after ClitorisErect - exactly like DK
  (dk9FrameCount = 54). A loadPreset ROM block (Physics) and a 0-based extraJSONs
  section then both land cleanly at rel 104, with no overlap.
- Migrate the example FrameData JSONs (ElectraG9, MattG9) from 1-based to 0-based
  (first FBM frame 1 -> 0). The 1-based numbering only cancelled the older 103
  handoff; under 104 it would push the FBM block one frame too far.

Verified against a real ElectraG9 PoseAsset export: GP ClitorisErect at abs 431,
FBM BodyTone at abs 432. With gpFrameCount 104 and the 0-based JSON, BodyTone lands
at 432 + 0 = 432 - identical layout, now convention-consistent, and Physics-after-GP
no longer collides.

NOTE: any external generator of FrameData JSONs (the studio's toDazFbmJson) must
also emit 0-based. The absolute PoseAsset CSV layout is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ApplyDTHCharacter(config) to DthWorkflow.dsa: one call that takes a single
object carrying the full character config AND all ROM morph definitions inline,
maps it onto the shared options, and runs the normal workflow — no extra
.json/.csv files read from disk. Powers DTH Character Studio's one-file
<Name>_<Genesis>.dsa output.

To support inline data without changing the file-based behaviour:
- DthUtils: split readFrameDataJSON into a file reader + processFrameDataJSON
  (takes parsed data + optional GP art-direction data for the resetGP reset);
  split applyGPArtDirection into a file reader + applyArtDirectionData.
- DthWorkflow: CreateFrameDatas honours options.extraFrames (inline); ApplyGP9
  and ApplyDK9 prefer options.gpArtDirection / dkArtDirection over file paths.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline the keyframe-scanning functions (scanNodesForFrameDatas + helpers)
from ScanKeyFrames.dsa into DthWorkflow.dsa and drop the include, so the
runtime ships as one fewer file. Only reached via the bWriteDebugCSV path.
ScanKeyFrames.dsa stays as a standalone debug script.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- setTotalFrames now also calls Scene.setPlayRange so the timeline scrubber
  reaches the later frames (the FBM section after GP) instead of staying
  bounded to the previous play range — fixes "can only scrub to ~431".
- applyKeyData disables clamping on each morph property (setIsClamped(false),
  guarded) before setting values, so DAZ Studio 6 doesn't prompt "keep
  limits?" for FBM/custom morphs that exceed their authored range.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ApplyDTHWorkflow already captures the initial selection (aNodes / oNode) at
the start; on success (after setFrame(0), before acceptUndo) it now deselects
all nodes and reselects the original selection + primary, since loading the
ROM presets re-selects nodes along the way. Error paths return early and skip
this, leaving the scene as-is for debugging.

Lives in DthWorkflow.dsa (the shared runtime), so DthWorkflowElectraG9.dsa and
the studio-generated character scripts both pick it up via their include.

Local only — for review before pushing/PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
processFrameDataJSON now resets whichever genital ROM's morphs are present
(Golden Palace and/or Dicktator) at the FBM block's first frame, reading the
matching sibling GP9/DK9 art-direction JSON or the inline gpArtDirection /
dkArtDirection; CreateFrameDatas passes options.dkArtDirection through. README
documents both meta flags (resetGPBeforeApplying / resetDKBeforeApplying).

Also clarify the include-path comment: getScriptFileName() resolves to the
invoking script's folder, not this file's.

DK path is functional but not byte-validated (see Status).
Five fixes root-caused from a generated ROM script's log (kept in sync
with the DTH Character Studio bundled runtime copy):

- setFencePoses: replace `for…in` over the fence-frame array (which in
  Daz's engine also yields enumerable Array.prototype members, restoring
  the figure at `function f(){…}`/empty frames) with an indexed loop.
- getValueChannel(0) -> getValueChannel(): the method takes no args; the
  extra 0 logged "Too many arguments, ignoring 1" on every morph lookup.
- applyArtDirectionData: add a findProperty/findPropertyByLabel fallback
  (mirroring applyKeyData) so geo-graft alias morphs like Golden Palace
  GP_PR_* resolve instead of "Property not found".
- setPropertyByName: verify by reading the value back instead of trusting
  setValue's return, so a no-op (value already set) stops logging a false
  "Failed" (e.g. FACS Detail Strength / Flexion Automatic Strength).
- var-scope implicit globals (oProp/oMod/oMorph/oContentMgr), silencing
  the "used before declaration" warning.

Co-authored-by: Polynaut <polynaut.git@proton.me>
Character generation now lives in polynaut/dth-character-studio, so the
per-character payloads and one-off debug tools no longer belong here.

Delete:
- DK9_Workflow.dsa (superseded; ApplyDK9 lives in DthWorkflow.dsa)
- DthApplyFrameDatas.dsa (orphan; read a non-existent CSV)
- DthScanDK9Props.dsa, DthVerifyDK9ArtDirection.dsa (one-off DK9 debug tools)
- ScannedFrames.CSV, ElectraG9_FBMs.csv (stale export artifacts)
- DK9.json, MattG9_FBMs.json, DthWorkflowMattG9.dsa, GP9.md, GP9_prep.dsa
  (character/product data now owned by dth-character-studio)

Keep GP9/DK9_ArtDirection.json as runtime fallback defaults, and DK9.csv.

Trim ElectraG9_FBMs.json from 49 frames to a 4-frame minimal example, and
repoint DthWorkflowExample/FromCSV at it (both referenced deleted or
never-existent files).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFpiyAjBvNqVjT7R7L2NDi
polynaut and others added 2 commits June 27, 2026 18:50
The previous cleanup overreached and deleted four files authored by
soltude (the upstream maintainer) rather than only the Polynaut/Claude
additions. Restore them, since this PR targets soltude:main:

- DK9_Workflow.dsa (initial commit)
- DthApplyFrameDatas.dsa
- GP9_prep.dsa (initial commit)
- ScannedFrames.CSV

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFpiyAjBvNqVjT7R7L2NDi
Its ApplyDK9 logic was merged into DthWorkflow.dsa; the standalone copy
is no longer included by anything. Confirmed for removal with the maintainer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFpiyAjBvNqVjT7R7L2NDi
@polynaut
polynaut force-pushed the feature/small-enhancements branch from 15e5801 to 092c1eb Compare June 27, 2026 16:50
Polynaut and others added 2 commits June 27, 2026 19:14
…ToHue-Scripts into feature/small-enhancements

# Conflicts:
#	ElectraG9_FBMs.json
#	MattG9_FBMs.json
processFrameDataJSON: an entry with an invalid (NaN/negative) frame
number is reported and skipped instead of silently corrupting the frame
array and shortening the timeline - every valid frame keeps its slot,
so the Daz timeline stays aligned with the Houdini PoseAsset frames.

createMorphFrames: a morph that can't be resolved no longer `break`s
out of the frame's config loop (which dropped all remaining morphs of
that frame) - it's reported and skipped, costing exactly that one
morph. Failures are printed to the Daz Studio log as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoMAkdAadM4fmMbQZ13htD
@polynaut

polynaut commented Jul 4, 2026

Copy link
Copy Markdown
Author

Added one more fix (4eb1742): a morph that can't be resolved (or a frame entry with an invalid frame number) no longer shifts/shortens the ROM timeline — it's reported to the log and skipped, so only that one morph is lost and the Daz timeline stays frame-aligned with the Houdini PoseAsset import. Previously createMorphFrames dropped ALL remaining morphs of a frame on the first miss (break instead of continue), and a NaN frame number could silently corrupt the frame array.

Polynaut and others added 2 commits July 7, 2026 15:41
Two runtime fixes, mirrored from DTH Character Studio's copy (kept in that
repo's print()-only flavor):

- ApplyDefaultROMs only returned true from inside the bIncludeFAC branch, so
  running JCM without FAC (e.g. a custom base ROM with FAC off) loaded the
  base ROM and then silently aborted the whole workflow. Restructured to
  early-returns; the base ROM alone is a successful load, FAC stays optional.

- Preset block lengths (base/GP/DK/Physics) are no longer inlined at each
  call site. getPresetFrameCount() uses options.presetFrames when provided
  (e.g. by DTH Character Studio, which measures the actual .duf lengths, or
  by a wrapper using a custom-length base ROM) and falls back to the stock
  DTH G9 asset lengths otherwise - so existing setups behave identically.
  New documented `presetFrames: null` option in DthOptions.dsa.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoMAkdAadM4fmMbQZ13htD
DS6's animation engine drifts LINEAR-interpolated ROM keys across the
timeline (mrpdean, June 2026: the G9 DQS JCM FAC cheek poses creeping);
converting every key to Constant after applying is his validated workaround.

- dthDetectDaz6() (JoxiMoon's version-API detection, DS4-safe default).
- setLinearInterp stamps CONSTANT_INTERP on DS6, LINEAR_INTERP on DS4
  (unchanged validated behavior); the session default interpolation matches.
- The final pass now also covers the FAC mouth node - its keys are morph
  modifiers on the MOUTH object, which a root-only pass never touched.

Mirrored from DTH Character Studio's runtime copy (v17).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoMAkdAadM4fmMbQZ13htD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant