Skip to content

Feature/fable facelift + Working - #3

Open
JdMasuta wants to merge 87 commits into
GorillaOfDestiny:mainfrom
JdMasuta:feature/fable-facelift
Open

Feature/fable facelift + Working#3
JdMasuta wants to merge 87 commits into
GorillaOfDestiny:mainfrom
JdMasuta:feature/fable-facelift

Conversation

@JdMasuta

@JdMasuta JdMasuta commented Jul 3, 2026

Copy link
Copy Markdown

I used Claude Fable 5 to do a finish-write of the original repository, then created a branch from there to implement some quality of life features (GUI, installable python package format, etc.) If you are interested in the finish-write (adheres to your original project), check out the other branch on this fork. I'd love to work further on this with you, but more direction would be a great help, such as using the issues section of github to request features and report bugs.

I hope this is in the vein of what you were looking to build! Thank you for being the coolest kind of nerd.

To run a quick test, clone this branch of my fork, navigate to the root directory of the clone, create a venv, activate, and install requirements.txt. Lastly, to run the gui, run "python -m arcane.gui"

claude and others added 30 commits July 3, 2026 04:53
Documents the Arcane Engineering (D&D 5e homebrew) domain background,
audits the current components.py prototype for concrete bugs, and lays
out a prioritized build list (resistors, concentration components,
logic gates, junction-to-junction links, manim converter, tests, docs)
so Fable 5 can pick it up and complete the project.
…step() edge cases

- raise CircuitException in Junction.__init__ instead of discarding it,
  and require only one side to have arity 1 (and, not or) as the
  original comment intended
- fix UnboundLocalError in connect() when the next component does not
  require input (wire_obj was appended without being created)
- fix next_component -> next_comp typo that silently dropped the link
  into an opening junction
- guard division by zero when spreading remainder energy in
  Junction.step()
- gate connect() debug prints behind a verbose flag

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Per-step transferable energy is 1/resistance. A Resistor is the sole
actor on both of its links (same convention as Junction) and wires now
skip Junction-or-Resistor neighbours, since otherwise a downstream wire
would pull at its own unthrottled rate and defeat the resistance.
Wire gains an explicit energy_out_rate (inf, preserving its dump-all
behaviour) so subclasses can throttle output.

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

Concentration is the capacitor/crystal analogue of D&D concentration:
it charges at charge_rate per step while releasing nothing, dumps its
held energy downstream once it reaches capacity, and
break_concentration() dissipates whatever is held (a failed save).

Replaces the growing isinstance checks with an explicit
self_managed_links marker on the base component: self-managed
components (Junction, Resistor, Concentration) are the sole actors on
their links, and when both ends of a link are self-managed the
downstream side pulls. Junction outputs now skip self-managed
neighbours accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Rewrites connect() as a build-nodes-then-link pass that keeps the exact
output structure plot() expects. Adjacent branch lists now link their
closing junction straight into the next opening junction (downstream
junction pulls; upstream skips self-managed neighbours, so energy moves
exactly once per link). Branch sublists no longer grow a vestigial
self-wire, closing junctions wrap around only at depth 0, and junction
name counters actually increment so names stay unique.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
LogicGate extends Junction: n inputs merge into 1 output and energy
only moves while the gate's boolean rule over its active inputs holds
(an input is active when it holds >= threshold energy). Multi-input
gates placed directly after a branch list are adopted by connect() as
that list's closing junction. NotGate is an inline inverter that emits
from an internal reserve while its input is quiet and consumes the
control signal that holds it shut.

Junctions now carry an explicit junction_role stamped by connect(), so
plot() no longer relies solely on the '-' name convention (which
user-named gates would break); the name fallback remains for
hand-built circuits. Switch now honours the self-managed-link
convention so it cannot double-move energy against a junction or gate
neighbour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
flatten/step_all/total_energy/simulate/plot_history reproduce what the
components.py __main__ block does inline, so tests, demos, and the
manim converter can share them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
plot() gains record/show parameters: with a record dict it captures the
exact xy segments each component symbol draws (and now passes ax to
every symbol call instead of relying on plt.gca()). manim_scene builds
on that: trace_layout() replays the plot walk silently and
make_circuit_scene() turns the segments plus a simulate() energy
history into a Scene where each component's stroke brightens and
thickens with its energy, driven by one ValueTracker sweep. manim is
imported lazily so everything else works without it; a DemoScene is
exposed for manim -pql when it is installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
build_circuit()/load_circuit() map a small JSON format onto the nested
lists connect() consumes: objects with a 'type' key become components
(remaining keys go to the constructor), arrays nest exactly like
connect() branch lists. Returns a name -> component registry so callers
can reach switches and gates after loading. Includes an example spec
and a CLI (python circuit_spec.py spec.json --steps N) that draws the
circuit and its energy history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
32 tests: per-component step behaviour (wire, resistor, concentration,
switch, caster, AND/OR/NOT gates), connect() topology including
junction-to-junction links and gate adoption, energy conservation with
cast() consumption accounted exactly, JSON spec loading and its error
paths, layout tracing, and regressions for the five fixed bugs. Adds
pyproject.toml/requirements.txt (manim optional) and stops tracking
__pycache__.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
README now credits GorillaOfDestiny's Arcane Engineering supplement as
the conceptual basis, documents every component and its circuit
analogue, and shows how to build, simulate, JSON-load, and animate
circuits. TODO marks the original four items done and lists the real
remaining gaps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Moves the five modules under arcane/, switches sibling imports to
absolute arcane.* imports, and adds a public API in arcane/__init__.py
(manim-backed helpers exposed lazily so importing arcane never needs
manim). Extracts trace_layout into arcane.layout and a shared palette +
energy gradient into arcane.theme, so the GUI preview and manim export
will draw from one source of truth. Adds arcane.render.render_circuit()
for programmatic manim rendering with explicit fps/quality, console
entry points (arcane-gui, arcane-sim), and a setuptools build config.
Tests updated to import from arcane; all 32 still pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
A dark-themed desktop app (arcane.gui) that shows the circuit schematic
and the energy-over-time graph side by side. A transport bar scrubs
through the simulated timeline: the schematic recolours each component
by its energy at the selected step (using the shared theme gradient, so
it matches an exported video) while a gold cursor tracks the same
instant on the energy graph. Playback advances so a full sweep lasts
the configured render duration.

Controls cover circuit source (demo or JSON spec), step count,
log-scale and plumbing-visibility toggles, per-switch toggle
scheduling, and manim frame-rate/resolution/duration. Rendering runs on
a background QThread so the UI stays responsive, and the Render button
is disabled with a hint when manim is absent. Simulation state lives in
a Qt-free SimulationSession for testability, and repeated runs reset to
initial energies so they are reproducible.

Also brings the original modules up to PEP8 (autopep8 + manual line
wrapping, behaviour-preserving); the whole package now passes
pycodestyle and the 32 existing tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Render tests inject a fake manim module to verify render_circuit
translates fps/quality/duration into the right config keys (quality
before frame_rate so the explicit fps wins), copies the produced file to
the requested path, and guards the no-manim and bad-quality paths. GUI
tests run under the offscreen platform: session reproducibility across
reruns, switch-event scheduling changing the outcome, the load->run->
scrub flow, and the plumbing-visibility filter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
README now leads with the desktop studio (circuit + energy views,
scrubbing, manim export), documents the install extras, entry points,
scripting API, JSON specs, and package layout, with a hero screenshot.
requirements.txt gains PySide6; TODO records the studio as done and
lists the real next steps. Also tidies test import wrapping to keep the
whole project pycodestyle-clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
SpellWave realizes the Theory of Magic formalization: an exact
spreading Gaussian packet times a stationary max-range absorber gives a
non-unitary |Psi|^2 whose raw norm decays, and A(t) = 1/int(|Psi|^2 dx)
renormalizes it so P = A|Psi|^2 stays a valid probability density until
the spell fizzles. Includes the classical-model comparison series that
exhibits the ultra-magic catastrophe, per-step arrays aligned to the
circuit timeline, and waves_from_cast_log() to spawn waves from
simulate()'s cast log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
simulate(..., cast_log=[]) wraps each Caster's cast for the duration of
the run and appends {step, caster, energy} on every firing, restoring
the method afterwards. Non-breaking; callers use it to spawn spell
waves without instrumenting components themselves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
make_spellwave_scene() sweeps a SpellWave's precomputed P(x,t) as a
glowing curve toward a dashed max-range marker, with a DecimalNumber
counter showing A(t) climb as the absorber eats the raw norm and the
curve collapsing at the fizzle. Same ValueTracker/updater pattern and
guarded manim import as manim_scene.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
The tempconfig/copy plumbing moves into _render_scene so
render_circuit and the new render_wave share one path; both keep the
same fps/quality/run_time semantics and guarded error handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
SpellWave and waves_from_cast_log join the eager public API;
make_spellwave_scene and render_wave are exposed lazily like the other
manim-backed helpers. Version bumped to 1.1.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
_style_axes gains an optional ax parameter (defaulting to self.ax) so
multi-axes subclasses like the spell wave view can reuse the theme
styling; single-axes callers are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
SpellWaveView stacks two themed axes: the renormalized energy density
E*P(x) of every wave at the scrubbed step (absorber region shaded, max
range marked, live A(t) and raw-norm readout) above the log-scale
timeline where the classical model blows up toward the ultra-magic
catastrophe while the renormalized total stays finite, with the shared
gold time cursor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
run() records casts via simulate(cast_log=...) and builds one
SpellWave per firing using the session's wave settings (max range,
speed, packet width), keeping waves aligned to the run's timeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
New 'Spell wave' group (max range, speed, packet width, show-classical
toggle emitting waveSettingsChanged) and a 'Render spell wave...'
button alongside the renamed 'Render circuit...' button, sharing the
fps/resolution/duration options via waveRenderRequested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
The worker takes a zero-argument callable returning the output path
instead of hard-coding render_circuit, so circuit and spell-wave
renders share one background-thread path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
The lower half becomes a QTabWidget (Energy over time | Spell
propagation), both driven by the transport scrubber. Runs feed the wave
view from the session's waves, the status bar reports cast counts, and
circuit/wave renders go through the generalized worker with buttons
that re-enable appropriately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
QTabWidget/QTabBar styling to match the arcane palette: rounded
surface tabs with an accent border on the selected tab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Covers the normalization invariant (int P dx == 1 every alive step),
A(t) drifting well past 1 as the absorber bites, the classical model
exceeding 1e5x cast energy while the renormalized total stays finite,
fizzle behaviour, parameter validation, simulate() cast_log recording
and method restoration, and cast-log-to-wave alignment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Extracts the fake manim environment into a helper and verifies
render_wave translates fps/quality and delivers the produced file, plus
the guarded no-manim path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
claude added 30 commits July 18, 2026 21:42
refactor(util): move circuit_spec into the util sub-library

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(manim): manim_wave.py becomes arcane/manim/wave.py
refactor(manim): extract build_wave_mobjects for scene composition

make_spellwave_scene() now dispatches on the wave's type: 1D waves stay
a glowing curve, SpellWave2D becomes an ImageMobject whose pixel_array
is remapped through the shared theme gradient each frame.
build_wave_mobjects() exposes the mobject-building half at a caller-
chosen offset/scale so the combined circuit+wave scene can reuse it on
a shared ValueTracker. Untested against a real manim install (cannot be
installed in this environment); exercised via the fake-manim harness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
feat(manim): carry gate labels and the NOT-gate circle into scenes
refactor(manim): manim_scene.py becomes arcane/manim/circuit.py
refactor(manim): extract _circuit_mobjects shared by both scene builders

make_combined_scene() stacks the circuit schematic above the wave it
cast, one ValueTracker driving both halves on the circuit-step
timeline. Circuit scenes now include static Text/Circle mobjects from
trace_layout_with_decor(). Untested against a real manim install;
exercised via the fake-manim harness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(manim): render.py becomes arcane/manim/render.py

Same fps/quality/tempconfig plumbing as render_circuit/render_wave.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Documents that 'from manim import ...' inside this sub-package is an
absolute import and always resolves to the third-party package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(api): point the public API at the new sub-library paths

XorGate/NandGate/SpellWave2D join the eager exports;
make_combined_scene/render_combined join the lazy manim-backed ones.
Version bumped to 1.2.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(gui): session imports move to arcane.util

caster_wave_overrides() collects each Caster's wave_range/speed/width
(e.g. from a JSON spec) for waves_from_cast_log; the wave_2d flag picks
SpellWave2D per run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
feat(gui): combined-render and builder buttons, render busy indicator
refactor(gui): render imports move to arcane.manim

The progress bar is indeterminate by design: manim's Python API exposes
no per-frame callback, so an honest busy indicator beats a fake
percentage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
When the session produced SpellWave2D waves the spatial panel switches
from per-wave curves to an imshow of the most recently cast living
wave's energy density, on the shared theme gradient, with the same A(t)
readout and catastrophe timeline below.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(gui): manim imports move to arcane.manim

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Structured tree editor: components (fields generated from constructor
signatures), parallel groups, nested branches, reorder/delete; Build
validates through the same build_circuit()+connect() pipeline as the
JSON loader and hands the studio a ready spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(tests): imports follow the sub-library split

XOR passes with exactly one hot input and closes when both go hot;
NAND passes until all inputs are hot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(tests): imports follow the sub-library split

A low-capacity output fills exactly to max_energy with the excess going
to outputs that still have room; unplaceable energy stays in the
junction instead of vanishing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
2D normalization (double integral == 1 every alive step), A(t) drift,
the classical catastrophe vs the finite renormalized total, fizzle,
parameter validation, wave_cls selection, and override fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
Regression for the check_level crash plus end-to-end coverage: walker
names, trace_layout segments, plot(), and energy conservation through
the doubly nested topology.

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

refactor(tests): manim availability import follows the split

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
refactor(tests): render module imports follow the sub-library split

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
The fake provides enough of manim's API to run construct() and drive
updaters across several tracker values like the real frame loop would,
catching wrong signatures/indices/names that import guards cannot —
while saying nothing about actual pixels (only a real manim install
can). Covers circuit, 1D wave, 2D wave, both combined variants, and
gate-label/NOT-circle decorations, restoring the guarded no-manim state
afterwards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Px2UwirEWHX8WWf1158cM5
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