Skip to content

Add CSC (Composite Scene) editor - #371

Merged
donkeyProgramming merged 4 commits into
donkeyProgramming:masterfrom
robert-d-schultz:csc-editor
Jul 26, 2026
Merged

Add CSC (Composite Scene) editor#371
donkeyProgramming merged 4 commits into
donkeyProgramming:masterfrom
robert-d-schultz:csc-editor

Conversation

@robert-d-schultz

Copy link
Copy Markdown
Contributor

Summary

  • Adds a full editor for Total War .csc (Composite Scene) files: an ESF binary envelope reader/writer (Shared/GameFiles/Esf, ported from RPFM's ESF handling), CSC-specific grammar detectors for ROOT/ELEMENT/COMPOSITE_SCENE (Shared/GameFiles/CSC), and the editor itself (Editors/CscEditor) — scene graph building, skeletal animation playback with cross-fade blending and bone-splice overrides across overlapping bindings, a custom transform gizmo, a keyframe curve editor, and a look-through-camera porthole preview.
  • Supporting engine changes the editor depends on: Camera.ViewMatrixOverride/ProjectionMatrixOverride, RenderEngineComponent/RenderTargetHelper additions for the porthole preview, GameSkeleton.CreateFromAnimationFile for ad-hoc skeletons, AnimationPlayer.SetManualFrame for cross-fading clips, and a corrected bone-attach/pivot composition order in Rmv2MeshNode.
  • A few small unrelated bug fixes found along the way: a mouse-capture drag freeze, a dropped attach-point name for nested variant-mesh children, and a bind-pose fallback in SkeletonBoneAnimationResolver.

Test plan

  • Full solution build: 0 errors
  • Full test suite green across all 12 test projects, including 22/22 new Test.CscEditor tests and 83/83 GameWorld.CoreTest

robert-d-schultz and others added 2 commits July 22, 2026 08:14
- WpfMouse: a missed hit-test mid-drag no longer stops position tracking
  before the button is released, so fast mouse movement during a capture
  drag doesn't freeze the cursor.
- ComplexMeshLoader: nested <VARIANT_MESH> children under a <SLOT> were
  losing their attach_point name (the recursive call passed the outer
  attachmentPointName through instead of slot.AttachmentPoint).
- SkeletonBoneAnimationResolver: added a bind-pose fallback when no
  animation frame is available and a bounds check against BoneCount,
  so attach_point/rigid-bone meshes don't collapse to the model origin.
- Pin SQLitePCLRaw.bundle_e_sqlite3 2.1.12 in the three
  TreatWarningsAsErrors projects that pull in a vulnerable transitive
  version (NU1903 audit advisory).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a full editor for Total War .csc files: an ESF envelope
reader/writer (Shared/GameFiles/Esf, ported from RPFM's ESF format
handling), CSC-specific grammar detectors for ROOT/ELEMENT/
COMPOSITE_SCENE (Shared/GameFiles/CSC), and the editor itself
(Editors/CscEditor) - scene graph building, skeletal animation
playback/blending/splicing across overlapping bindings, a custom
gizmo, a keyframe curve editor, and a look-through-camera porthole
preview.

Supporting engine changes needed by the editor:
- Camera: ViewMatrixOverride/ProjectionMatrixOverride for looking
  through a scene-defined camera.
- RenderEngineComponent/RenderTargetHelper: HideGrid, SquareRenderSize
  and an alpha-preserving LastFrame for the porthole preview.
- GameSkeleton.CreateFromAnimationFile: builds a skeleton directly
  from an animation's own bone table for "ad-hoc" skeletons that have
  no dedicated skeleton file (e.g. building-destruction rigs) - also
  wired into SceneObjectEditor.SetAnimation for every editor.
- AnimationPlayer.SetManualFrame: lets external code inject an
  already-sampled frame, used for cross-fading overlapping clips.
- Rmv2MeshNode: corrected bone-attach and pivot composition order,
  and a new AttachmentOuterWorld slot so CSC's flat scene graph
  (which must push a leaf's full world into ModelMatrix for picking)
  and Kitbasher's nested scenegraph can share the same attach-bone
  resolver without one clobbering the other.

22 new unit tests (ESF round-trip, channel evaluation, scene
round-trip with hierarchy/keyframes, nested-element handling).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@donkeyProgramming donkeyProgramming left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done!
Have a few questions/changes for the "core" part of the application.

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this added and why is it needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, this definitely is because of that mistaken BMD Editor branch editing (it was way out of date), and then blindly brought over to this new, up to date branch. It was suppressing some security warning (SQLitePCLRaw.lib.e_sqlite3). And your already suppressing it in some other way. So this edit was pointless.

}
}
});
WireAttachmentResolvers(sceneObject);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a set of unit tests to verify that this works?
Attachments are a recurring point for bugs, so lets make sure we dont jump between different versions without having a chance to verify how it should work,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new Test.Editors.Shared.Core project — 10 tests covering bone-name match/no-match, no-skeleton, rigid-bone matrix-override in/out of range, nested meshes under a group, and re-wiring after a skeleton swap.

/// <summary>Directly overrides the sampled frame for this tick (e.g. cross-fade blending
/// across multiple clips) - bypasses SetAnimation/CurrentFrame's normal single-clip
/// sampling. Overwritten again on the next call that touches CurrentFrame/Refresh.</summary>
public void SetManualFrame(AnimationFrame frame) => _currentAnimFrame = frame;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is needed because while scrubbing on the timeline its possible to set the frame to say 1.5, a mix between frame 1 and 2?
Instead of adding a hack/workaround, change the int CurrentFrame to support this behavior by changing it to a float/double.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSC never actually touches CurrentFrame — it drives multiple overlapping clip bindings (cross-fade blends, bone-splice overrides) through its own fractional sampler, and SetManualFrame is how it injects that externally-composed multi-clip result. A float CurrentFrame wouldn't let us drop SetManualFrame, since what's being injected is never "this one player's own clip at time T" — happy to make CurrentFrame a float separately if useful elsewhere, but it doesn't fix this.

/// <summary>When true, skips the editing-grid render bucket (see Render3DObjects) - for
/// consumers that want the normal-editing grid hidden, e.g. a "look through a scene camera"
/// preview where nothing but the in-game content should show.</summary>
public bool HideGrid { get; set; }

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can already be controlled by RenderSettingsModel

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — replaced with the existing GridComponent.ShowGrid (same thing RenderSettingsModel already drives). HideGrid is gone.

/// preview where nothing but the in-game content should show.</summary>
public bool HideGrid { get; set; }

/// <summary>When set, renders at this fixed square pixel size instead of the live (and

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the AI generated comments and replace them with a better description.
For the "Core" parts of the application its important to keep things clean and understandable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up.
Beep boop.

var spriteBatch = CommonSpriteBatch;
var screenWidth = device.Viewport.Width;
var screenHeight = device.Viewport.Height;
var screenWidth = SquareRenderSize ?? device.Viewport.Width;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the SquareRenderSize needed? Is it possible to just override the viewport instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with SquareViewport — restricts the rasterization viewport instead of resizing/reallocating the render targets, so it no longer reallocates GPU resources every time look-through toggles.

(Human ChaosRobie here, if your asking a general question about square 3d view: It's needed for the look-through-camera 3d porthole previewer. It prevents the 3d view from being resized into weirdness)

(ButtonState)e.LeftButton, (ButtonState)e.MiddleButton, (ButtonState)e.RightButton, (ButtonState)e.XButton1,
(ButtonState)e.XButton2);
// only release if LeftMouse is up
// Once capture is held (left-button drags only - see CaptureMouse() below),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you verified this behavior across multiple use cases and editors?
I think I have already reverted 4 AI generated fixes for this, as they all have caused more issues then they actually solved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added when I was editing my BMD Editor branch, which was very out of date (my mistake). It was legitimately an issue from then, but it's likely it was just blindly brought over when I made the CSC Editor branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still seems to be an issue.
https://github.com/user-attachments/assets/696744e9-a760-486f-bb50-510e9ed1bf92
This is with it reverted. I'm alt click-dragging like a mad man, trying to orbit the camera and there's only a few frames of orbiting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I pushed Claude for a real explanation/solution. And it seems to have found it (I helped test!). CSC Editor was special since it had an overlay sharing the 3d view's frame/cell thing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude's official response:

VisualTreeHelper.HitTest ignores visibility, so CSC's hidden porthole overlay was still registering as a "hit" and breaking mouse capture — only happened in CSC because it's the only editor with that overlay. Confirmed it live with some temp logging rather than just guessing.

Fix is just swapping those two hit-tests for InputHitTest, which does respect visibility. Ditched the old position-tracking workaround entirely — everything else matches master now. Different bug than the ones you'd reverted before, this one's for real.

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLitePCLRaw.lib.e_sqlite3 supression thing, unnecessary. Reverted.



namespace Shared.GameFormats.Csc;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some unit tests for this?
Just a few tests for saving and loading that verifies that it works.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 8 isolated tests for DetectGroups/DetectEntrySection/DetectFooter directly, including malformed-input cases — complements the existing round-trip test that already exercises save/load through the full scene pipeline.

<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="LZMA-SDK" Version="22.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.7" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.12" />

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLitePCLRaw.lib.e_sqlite3 supression thing, unnecessary. Reverted.

robert-d-schultz and others added 2 commits July 26, 2026 07:08
- Replace RenderEngineComponent.HideGrid/SquareRenderSize with GridComponent.ShowGrid and a SquareViewport rasterization-viewport restriction, avoiding the render-target reallocation churn of the old approach
- Clean up AI-sounding doc comments in RenderEngineComponent.cs
- Fix WpfMouse.cs's real bug: VisualTreeHelper.HitTest ignores Visibility/IsHitTestVisible and was matching CSC's collapsed porthole overlay instead of the 3D view, causing near-total loss of mouse tracking during a drag; switched both hit-test call sites to UIElement.InputHitTest, which respects those properties. The previous "keep tracking position on a hit-test miss" workaround is no longer needed and has been reverted, so this file now matches master except for the InputHitTest swap
- Add unit tests for SceneObjectEditor.WireAttachmentResolvers (new Test.Editors.Shared.Core project) and for CompositeSceneDetector's grammar parsing (Test.CscEditor)
- Remove the SQLitePCLRaw.bundle_e_sqlite3 pin from the three csproj files that had it - turned out to be unnecessary, since the one advisory it addressed is already suppressed solution-wide via Directory.Build.props

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Embedded Composite Scene (ROOT_REF) contents are now hidden as a whole while their host element is outside its own Begin/End window, cascading through nested root-refs - unlike a normal element's own children, which never inherit a parent's aliveness
- Toolbar gizmo buttons (Move/Rotate/Scale/off) now use the same icons as Kitbasher instead of text labels
- Fixed ComputeSubSceneTime: a negative ELEMENT_PERIOD speed on a ROOT_REF host now plays the referenced scene in reverse (looping continuously) instead of permanently freezing at frame 0

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@donkeyProgramming
donkeyProgramming merged commit 6df65ad into donkeyProgramming:master Jul 26, 2026
1 check passed
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.

2 participants