Replace v8 bindings with v9 profile-wide node graph#34
Open
Zanges wants to merge 4 commits into
Open
Conversation
Replaces the v8 Binding+Modifier model with a single profile-wide node graph (shader-editor style). New Mouse2Joy.Graph project holds 54 typed node kinds across Sources / Targets / Math / Logic / Conversion / Timing / Curves / Dynamics / Limits / Variables, plus GraphCompiler and GraphEvaluator. Engine routes through GraphRunner; persistence drops the migration chain and FirstRunBackup moves pre-v9 profiles aside. UI ships a hand-rolled Canvas-based graph editor: draggable node cards with inline parameter editors, port-to-port wire dragging with auto- insert converter pickers, side variables panel, marquee/group/copy-paste, undo-redo with drag and idle coalescing. See ai-docs/implementations/NODE_BASED_PROFILES.md for the full design write- up and the deferred follow-ups. 147 tests pass (Graph 21, Persistence 10, Engine 18, Contracts 61, Platform.Abstractions 5, UI 32). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's Build & Test step runs `dotnet format --verify-no-changes` and flagged whitespace in GraphRunner.cs, ConversionEvaluators.cs, SourceEvaluators.cs, and UndoStackTests.cs. Running `dotnet format` locally also normalized line endings across the new Graph project files. No behavior changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses 8 of 10 findings from the multi-angle code review on PR #34. The two unfixed findings (settings v1→v2 migration drop and global KeyDown intercepting TextBox edits) are documented as follow-ups. - Variable-slot staleness (A): GraphCompiler no longer reuses Get/SetVariable evaluators across recompiles. Their cached _variableSlot index was stale if another variable was deleted between compiles, causing IndexOutOfRange on the next tick. - Plan-swap and suppression-set sync (B, C): added _planLock around GraphEvaluator's plan + slot arrays + variable buffers, and a _suppressionLock around GraphRunner's _suppressedKeys / _suppressMouse. Both prevent capture-thread vs tick-thread races when a profile-switch hotkey fires. - Multi-edge into single target-port (F): GraphValidator no longer exempts target nodes from the "one inbound edge per input port" rule. The exemption was meant for multiple node *instances*, not multi-edge into one node. Multi-edges into the same port were silently dropped in the compiler — now they're rejected at validation. - OnEditGraph rollback no-op (D): MainWindow now captures the original profile reference before overwriting _vm.SelectedProfile, so save- failure rollback actually reverts. - FirstRunBackup hardening (G, I, L): backup directory + per-file destination paths get auto-suffixed on collision (same-second re-runs no longer leave legacy files in profiles/); PeekSchemaVersion catches all IO/access exceptions (UnauthorizedAccessException, locked cloud stubs, etc.) and falls back to "leave in place"; non-Int32 schemaVersion is treated as legacy. - Typed slot initials (J): GraphCompiler now threads its per-port-type slotInitials through CompiledGraph.SlotInitials → GraphEvaluator. A first-tick read of an unconnected non-Scalar input port now returns a type-correct Signal.Zero (Vector2/Digital/Delta) instead of a Scalar-tagged zero. - Compile-error guard on Off→Active (K): RequestToggle's Off-side branches (both Soft and Hard) now check HasCompileError, matching the existing SoftMuted→Active guard. Engine no longer silently enters Active mode with an empty compiled plan. - Defensive ProfileStore schema check: DeserializeProfile now rejects any profile whose deserialized SchemaVersion is below current. Belt and suspenders for FirstRunBackup — if a legacy file slips past (e.g. file locked during the backup scan), the user just doesn't see it in the profile list rather than getting an empty-graph profile that overwrites the legacy bindings on next save. Adds 6 regression tests across Graph and Persistence test projects. 153 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements all critical findings from the local 8-angle review and the cheap subset of suggestions. Deferred items live in ai-docs/implementations/NODE_BASED_PROFILES_FOLLOWUPS.md with rationale and concrete next steps for each. Correctness (6 critical NaN/overflow fixes): - MathEvaluators.DivideEvaluator: guard long.MinValue / -1L → MaxValue - MathEvaluators.NegateEvaluator: guard -long.MinValue → long.MaxValue - DynamicsEvaluators.StickDynamicsEvaluator: NaN sanitization on Param1/Param2 and a belt-and-suspenders NaN reset on _deflection - DynamicsEvaluators.Vector2StickDynamicsEvaluator: same for the joint integrator state - CurveEvaluators.ResponseCurveEvaluator: IsNaN || <=0 exponent guard - CurveEvaluators.SegmentedResponseCurveEvaluator: same plus NaN threshold fallback to 0.5 Also: MathEvaluators.CompareEvaluator integer mode catches NaN/Infinity threshold (would silently produce wrong booleans); ConversionEvaluators.ScalarToIntEvaluator explicitly handles NaN→0 / ±Infinity→long extremes. UX (4 critical drop-position / variable-name / rename fixes): - GraphCanvasControl.ScreenCenterInCanvasCoords now reads the actual ScrollViewer offset + viewport (no more hardcoded 500,300) - New NextDropPosition() fan-offset prevents successive Add-Node / Drop-Get / Drop-Set from stacking on top of each other - VariablesPanel "Drop Get" / "Drop Set" use the canvas's NextDropPosition() instead of hardcoded (600,400) / (600,460) - NodeParameterEditors variable-name field replaced with a ComboBox bound to the declared variables (with a stale-name placeholder for references to deleted variables) - VariablesPanel.Rename surfaces inline errors (empty name, duplicate) and reverts the TextBox instead of silently swallowing Plus: explicit Name label on variable rows; "Default value" rather than "Default true"; "Rounding" label on ScalarToInt; Persistent-mode StickDynamics hides the unused Param2; node card × button gains a tooltip; toolbar Save/Delete tooltips mention Ctrl+S / Del; NodePicker auto-selects first row on filter so Enter always commits; NodePicker filter searches port-type names too; PasteVariablesDialog shows explanatory text instead of a disabled ComboBox when "Keep reference" isn't a real choice; DoubleBox/IntBox/LongBox commit on Enter as well as LostFocus and revert on parse failure; MainWindow "(no profile selected)" → actionable "Select a profile…". Convention (5 criticals): - Removed the "good enough for v1" shortcut on ScreenCenter - New per-kind evaluator test files: CurveEvaluatorTests, DynamicsEvaluatorTests, MathEvaluatorTests (18 new tests covering identity defaults, sign symmetry, NaN guards, overflow guards) - Settings v1→v2 migration drop: documented as a conscious convention break in NODE_BASED_PROFILES.md (Key decisions), trade-off recorded - NodeEditorAvalonia 11.3.11 dropped (unused package was shipping in the admin-process app output) Performance (critical): - EvaluationContext exposes ReadInputBySlot / WriteOutputBySlot direct accessors; PortSlotMap made public so evaluators can cache slot ids - Hot-path evaluators migrated: MouseEvaluator (8 ports/tick), KeyEvaluator, StickDynamicsEvaluator, Vector2StickDynamicsEvaluator. Remaining evaluators tracked as a follow-up. Stability: - NodeCard.PropertyChanged handler hoisted into a named method and unsubscribed on DetachedFromVisualTree (fixes the per-edit memory growth proportional to edits × node count per editor session) - InputSnapshot now documents the single-threaded invariant so future refactors don't reintroduce direct cross-thread mutation Regression / docs: - INITIALWORK.md banner updated to point at NODE_BASED_PROFILES.md (was still describing the deleted v8 binding-editor architecture) - NODE_BASED_PROFILES_FOLLOWUPS.md tracks 11 deferred items with rationale and next steps 171 tests pass (was 153, +18 evaluator regression tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Binding+Modifiermodel with a single profile-wide node graph (shader-editor style). NewMouse2Joy.Graphproject holds 54 typed node kinds,GraphCompiler, andGraphEvaluator. Engine routes throughGraphRunner; persistence drops the migration chain andFirstRunBackupmoves pre-v9 profiles aside on first launch.Test plan
dotnet build Mouse2Joy.slnsucceeds.dotnet test Mouse2Joy.slnpasses all 147 tests.dotnet run --project src/Mouse2Joy.App. On first launch with pre-v9 profiles in%APPDATA%\Mouse2Joy\profiles\, confirm the one-time modal lists moved files and the folder is repopulated with a fresh empty state.Mouse → Vector2StickDynamics → LeftStickVecgraph, click Save, click Activate, confirm the virtual pad's left stick responds to mouse motion.Mode), click Save, confirm the engine picks up the new graph without a profile re-activate.Key → Compareedge (expects Digital→Scalar picker), drop aMouse.Motion → LeftStickXedge (expects Vector2→Scalar picker), drop aMouse.Motion → Buttonedge (expects rejection toast).🤖 Generated with Claude Code