## App 0.27 current additions (schema 18)

This section and RELEASE-0.27.md supersede historical restrictions below. The model supports one to four independent solar inputs, including a single integrated plug-in enclosure with direct PV charging, storage and one shared AC ceiling. The US STREAM Ultra reference uses four 500 W inputs and 1.92 kWh storage; its baseline grid AC ceiling is 800 W. Conversion efficiencies are illustrative. AC charging, appliance bypass sockets, external AC-coupled microinverters, coordinated multi-unit systems and standby consumption are not modeled. Costs for included inputs and storage belong to one inverter bundle.

The Connecticut option records a 1,200 W planning ceiling and flags DC nameplate above 1,200 W for review. It does not certify a product or installation, resolve DC oversizing under the statute, or model meter charges on uncredited excess generation. Other regions use an editable planning ceiling without an inferred legal limit. Sources and qualifications are linked in ../help.html.

Schema 18 reads schemas 1–17 and reanchors balcony panel centers so each panel's back top edge touches the rail at all supported tilts and orientations. Wiring and panel identifiers are retained. Older app versions cannot open schema 18 files. Battery kWh is a convenience conversion of the existing nominal 48 V model, not a claim about a product's actual battery voltage.

Annual graphs show monthly AC energy or solar-derived bill savings. Rate edits reprice the same energy and initial-storage provenance; they do not rerun irradiance. Initial battery energy is excluded from solar-derived savings. Payback still requires positive savings and complete costs, and excludes financing, degradation, replacements, fixed fees and taxes. Printable reports allow both, either or neither graph.

---

## App 0.11 UI contract

Electrical model, equipment constants, battery integration and schema v7 are unchanged. Calendar validation now round-trips YYYY-MM-DD through UTC to reject impossible days instead of accepting Date.parse normalization. Date edits retain solar hour and invalidate the daily scenario; display-only selection, legend, chart choices and dimming do not. Color dimming never changes material opacity, geometry, shadows or sampled irradiance, and restores before model-driven heatmap updates. Panel label anchors use the actual transformed surface center.

## App 0.10 — electrical audit corrections (save schema v7)

This section supersedes older equipment and roof-label descriptions below. Save the project in the open browser **before** Ctrl+F5, then Open that saved file. The app does not automatically persist unsaved browser state. The existing server/launcher and downloaded ZIP are unchanged.

- Double-click an MPPT, choose **EG4 6000XP — BOTH trackers + inverter**, read the displayed scope, then Apply. This explicitly replaces both tracker settings, adds MPPT 2 if absent, and sets 6000 W AC output / 125 A nominal battery-charge limits. It preserves panel wiring, panel ratings, battery capacity, SOC, efficiencies and loads. It does not automatically replace existing custom settings on import. Custom edits afterward apply only to the selected tracker and clear its product revision if its ratings change. Battery limits remain separately editable.
- The qualified preset uses EG4 6000XP IN03, specification v1.4.4 / manual v1.7.3: independent 17 A usable input and 25 A array Isc ceilings on each tracker; 4000 W input each / 8000 W combined; 120–385 V tracking. A conservative 480 V design/fault cutoff is used; the manual's 500 V absolute ceiling is not a safe target. Startup 100 ± 10 V is reference information, **not modeled startup hysteresis**. The preset's 98% controller efficiency is illustrative, not a reproduced product efficiency curve.
- Panel Edit now has **Isc**. The Aptos DNA-120-MF10-460W preset is explicitly Ref.121924 (December 2024): Voc 41.97 V, Vmp 35.36 V, Imp 13 A, Isc 13.75 A. Derived Vmp × Imp is 459.68 W; this is nameplate rounding, not a different model. It applies to the selected panel only. Other revisions must use their own data. Legacy/custom Isc assumptions remain visibly labeled rather than silently becoming manufacturer data.
- All warnings coexist. Excess array Isc or conservative nameplate Voc shuts the modeled tracker off and zeros its delivered panel/string/controller A, V, W and conversion/bypass loss diagnostics. Exceeding operating current alone constrains the operating point. Nameplate power oversizing warns of possible clipping, not guaranteed clipping at all irradiances. Array Isc uses a conservative maximum module Isc per series string summed across parallel strings; this is not a detailed fault-current model. Cold Voc remains unchecked.
- Three Aptos panels in series have 106.08 V nameplate Vmp and 125.91 V Voc. A second parallel string does not raise voltage; its array Isc becomes 27.5 A, exceeding 25 A. One three-panel string on each tracker avoids that parallel-current violation, but still has low-Vmp/startup uncertainty. A static IV point above the nameplate MPP may produce an illustrative nonzero estimate; this is not a guarantee that the real inverter starts or supplies loads.
- Roof Live labels are compact separate lines: panel ID (OPT when enabled), `A · V`, then `W`, with `Bypass n/3`, cutout or unconnected when relevant. The shared legend explains that A/V/W are panel outputs or optimizer outputs before controller losses. Potential module input MPP current stays in panel details and is not claimed as loaded input current. Ordinary series current remains equal while voltage/watts may differ. Three shading sections and 0.45 V bypass drops are approximate, **not verified Aptos half-cell topology**. Negative power is a signed modeled loss, not a validated diode thermal prediction.
- Daily labels still show net controller-output Wh and reconcile to system DC energy before battery losses/curtailment. Display-only Live/Daily switching and solar-time scrubbing retain completed results. Configuration edits invalidate them.
- Battery Edit exposes optional AC output W and battery charge A ceilings (blank means unknown/unmodeled). AC demand above the ceiling is unmet AC energy and never becomes fictional DC consumption. Charge A uses fixed nominal 48 V and limits electrical charge input before storage losses; surplus is curtailed. These amperes are separate from PV input amperes. No battery voltage curve, BMS, surge, per-leg split-phase limit, discharge-current cap or actual inverter SOC/voltage cutout is claimed. Existing efficiencies/idle settings remain user values.
- Save schema v7 preserves v1–v6 settings and adds Isc provenance / optional limits. Older versions cannot read v7. Legacy Isc keeps the prior estimate and is marked assumed; unknown array safety limits stay unknown. No silent conversion of an ambiguous old “EG4 8000W” label occurs; old 2D project files remain unsupported.
- Optimizer support remains uniform mode per MPPT, idealized unrestricted DC/DC, 98% conversion. Mixed mode on one tracker is excluded and blocks Run day; safety warnings still appear. No named optimizer compatibility or measured input current is implied.

Sources: [EG4 specification v1.4.4](https://eg4electronics.com/wp-content/uploads/2024/04/EG4-6000XP-Inverter-Spec-Sheet.pdf), [EG4 manual v1.7.3](https://eg4electronics.com/wp-content/uploads/2024/04/EG4-6000XP-Manual.pdf), [Aptos Ref.121924](https://signaturesolar.com/content/Solar%20Panel%20Spec%20Sheets/Aptos%20DNA-120-MF10-460W_Datasheet_Ref.121924.pdf). Retrieved 2026-10-21. These are qualification references, not an installation approval.


---

Historical model notes follow; the app 0.10 contract above takes precedence.

# Educational solar model

This is a deterministic, browser-native proof of concept, not a validated PV design, annual-energy, safety, financial, or purchasing tool. No numerical accuracy percentage is claimed. Its purpose is to make geometric shading, substring bypass, series mismatch, parallel connections, and module optimizers visible and explainable.

## Current optimizer and chart contract (app 0.9; solver introduced in app 0.7)

Schema v6 uses per-panel optimizer booleans. Legacy global flags migrate across every existing panel; new panels default false. Application calculations pass perPanelOptimizers=true to the controller adapter. A controller with all flags false uses the ordinary series/parallel shared-current/voltage IV solver. All true retains the preceding idealized unrestricted optimizer calculation (98% module conversion, common demonstrative bus constrained to the controller window/current/power limits, then controller conversion efficiency). Each string's optimizer outputs share current; each panel's module-side standalone MPP current may differ. The inputCurrent field in this idealized branch is MPP potential before controller limiting. It is not a measured module draw under curtailment. Module output voltage differs with allocated power. Product conversion limits are not modeled.

A controller containing both flag states is unsupported, including optimized and unoptimized parallel branches on the same bus. The entire controller gets estimateExcluded=true, explicit unsupported status and zero-valued excluded contributions; these are placeholders, not a physical prediction. Other controllers remain independently computed, with total output prominently PARTIAL. Daily simulation is blocked until the unsupported case is resolved, preventing false battery/heatmap forecasts. Changing flags invalidates prior runs. Disconnected panels are excluded from the mode check. Core calculate still accepts its legacy global options for deterministic reference tests/counterfactual comparisons; the application uses the per-panel adapter.

Dashboard PV/controller series are sampled instantaneous W: total is summed controller DC output; controller input is rawInputW before controller efficiency, after any optimizer conversion. Controller output series include controller losses. Battery charge power is charged stored Wh / charge efficiency / interval duration; discharge power is withdrawn stored Wh × discharge efficiency / duration. Step plots duplicate interval boundaries so their integrals match modeled DC-bus flows. SOC percent is a separate panel, never overlaid on watts. Shared mini-chart axes span solar hours 0–24; partial status identifies incomplete runs. Expanded overlays use one watts scale for selected power series. Viewing charts or scrubbing completed environment time does not alter results.

## Current app 0.6 model additions

This section supersedes fixed-rating/no-battery statements in the historical revision notes below. No real-world accuracy or installation approval is claimed.

Panel curves use each panel's Voc/Vmp/Imp; Pmax = Vmp × Imp, Isc = 1.05 × Imp. Three equal idealized substrings share its voltage. Validation requires 0 < Voc/2 <= Vmp < Voc, Voc <= 200 V, Imp <= 100 A and Pmax <= 5000 W, keeping the piecewise model's maximum at the supplied Vmp/Imp point. Real cell layouts, measured curves, voltage changes with irradiance/temperature, degradation and dimensions tied to nameplate are absent. Default values preserve prior 400 W behavior.

For ordinary wiring, maximumPower intersects the piecewise shared-bus IV curve with the configured voltage window, operating-current ceiling and input-power ceiling. Candidate points include original breakpoints, quadratic extrema, current-limit crossings and power-limit roots. Thus clipped power corresponds to a feasible modeled operating point. String and signed panel contributions are calculated at that point. Controller efficiency multiplies every signed contribution exactly once; net panel contributions sum to controller output. A no-feasible-point state produces zero current/output. Independent controllers remain partitioned before calculation and summed afterward.

Compatibility checks use connected raw string nameplates. String Voc >= absolute maximum produces unsafe-cutout for that controller. Vmp outside its window warns; a usable tail of the IV curve may still produce output. Parallel current uses a conservative sum of the largest assumed Isc in each string, warning above the configured operating-current limit; the real absolute short-circuit rating is unknown. Nameplate above power limit warns; clipping is determined from current light and the curve. Cold Voc is explicitly untested without temperature coefficients/design temperature. The unrestricted optimizer branch takes each module's own MPP at 98% optimizer conversion, chooses a demonstrative common bus inside the MPPT window and scales electrical contributions for controller current/power limits. Its unknown real conversion envelopes are explicitly warned; raw Voc checks remain conservative. Optimizer compatibility is not validated.

Battery: one fixed 48 V nominal store; capacityWh = 48 × Ah. Default 100 Ah, 50% initial SOC, charge/discharge efficiency 0.95, inverter efficiency 0.90, idle 15 DC W, appliance 200 AC W disabled. Equipment/load ratings are constant over a run. Each PV sample now records the appliance switch for the following interval; a toggle updates only the final sample, so completed intervals are immutable. New runs reset events to the current switch at midnight. Completed history is unaffected by switch changes. Settings edits still invalidate the run. PV uses the same trapezoidal average net controller power at 0.2-hour intervals as the heatmap integral. Each interval serves DC demand = idle + AC appliance / inverter efficiency from PV first. Surplus charges up to capacity, with charge loss; remaining surplus is curtailed. Deficit withdraws stored energy through discharge efficiency, bounded by stored Wh. Idle demand is served before appliance demand; shortages produce unmet DC/appliance totals. No simultaneous charge/discharge occurs within an averaged interval.

Each interval and cumulative run obey:

`PV Wh + initial stored Wh = final stored Wh + served DC Wh + battery charge loss Wh + battery discharge loss Wh + curtailed Wh`

`served DC Wh = served idle Wh + served AC appliance Wh + inverter loss Wh`

No controller contribution is counted twice. Net panel Wh sum to PV bus Wh, before curtailment and battery losses. Stored charge and withdrawal curves represent stored-energy changes, not AC power. SOC remains 0–100%. Empty-storage load shedding is an energy bound, not a physical battery low-voltage cutoff. Battery charge/discharge current, BMS behavior, reserve SOC, voltage dynamics, temperature, aging, inverter rating/surge and wiring/protection remain unmodeled. Constant-efficiency and 12-minute averaging are explicit approximations. Rendered automatic wires are illustrative; no installation validity follows from them.

Schema v5 adds per-panel spec, controllerSpecs keyed by existing controller IDs and battery settings. v1–v4 migrate to explicit defaults while preserving topology/orientation. Import validates before replacing active state. Run curves/heatmap are transient; changes invalidate them and reset storage to configured starting SOC for the next run.

## API

`sunPosition(latitude, date, solarTimeHours)` returns the unit sun direction `{ x, y, z }` and `altitude`, `azimuth`, `declination`, `aboveHorizon`, and `solarTime`. Latitude is degrees north. The coordinate system is x east, y up, z south. Angles are degrees; azimuth runs clockwise from north. Pass a `YYYY-MM-DD` date or a JavaScript `Date` whose UTC calendar date is used. Hours are decimal **true solar time**, or `HH:MM`. Solar noon is 12:00. These hours must not be labelled local clock time: longitude, civil time zone, daylight saving, and the equation-of-time conversion are not part of this API.

`calculate(panels, { bypassDiodes: true, optimizers: false })` takes:

```js
[
  { id: 'P1', stringId: 'A', irradiation: [1, 1, 1] },
  { id: 'P2', stringId: 'A', irradiation: [0.25, 1, 1] },
  { id: 'P3', stringId: 'B', irradiation: [1, 1, 1] }
]
```

The three irradiation entries represent three equal-area series-connected substrings inside each module. Each value is a fraction of the reference irradiance, including whatever incidence and occlusion factors the geometry supplies. Values outside 0–1 are clamped with warnings; malformed or non-finite inputs are rejected. Panels with the same string ID are connected in series. Different string IDs are connected in parallel on **one common DC bus**, not on independent MPPT inputs. Missing string IDs default to `1`. Inputs are never modified.

Returned quantities:

- `nowW`: instantaneous simulated DC watts at the array's global maximum-power point.
- `idealW`: installed nameplate total, 400 W × module count. This is a full-reference-irradiance ceiling, not a shade-free output estimate for the current sun position.
- `availableW`: sum of 400 W × each module's mean substring irradiance. This is a separate sunlight-limited upper bound before electrical mismatch and losses.
- `perPanelW`: signed module contributions keyed by ID. A bypassed dark module in a live series string can be slightly negative because its diodes dissipate power. Contributions sum to `nowW`. A display may label these modules “bypassed” rather than suggesting they generate negative sunlight.
- `diagnostics`: common operating voltage/current, per-string and per-module results, bypass counts/losses, standalone module MPP sum, and a loss breakdown. `bypassLossW` is a supplementary diagnostic already included in the other electrical losses; do not subtract it twice.

## Electrical assumptions

Every module is nominally 400 W, 40 V at maximum power, 10 A at maximum power, 48 V open circuit, and 10.5 A short circuit. Its three substrings each have one third of the module voltage and the same current. These are invented representative parameters, not a particular product datasheet.

A substring uses a piecewise-linear voltage–current curve through open circuit, maximum power, and short circuit. Irradiance scales current linearly while voltage is held fixed. This deliberately omits the real logarithmic low-light voltage dependence, detailed diode equations, temperature effects, cell differences, and aging. A uniformly illuminated module consequently has exactly 400 × irradiance watts at its own MPP.

With bypass enabled, a shaded substring can pass the string current through a modeled diode. A short linear transition beyond its short-circuit current leads to a 0.45 V forward drop. With bypass disabled, a conservative hard ceiling limits the string current to that of its weakest substring. Below the corresponding terminal voltage, that current is held constant and module terminal voltages are allocated proportionally. This is an educational current-limiting approximation, **not a reverse-breakdown, hotspot, thermal, or damage prediction**. Never use the “bypass off” control as advice to remove real safety devices.

Series module voltages are added at the same current. Parallel string currents are added at the same voltage. The solver combines their piecewise-linear curves and checks every voltage breakpoint plus each interval's quadratic power maximum. Thus it finds the global maximum of this simplified model, including multiple bypass peaks, without a random or coarse sampling search. It assumes an ideal MPPT, no wire losses, and ideal blocking against reverse current between parallel branches. Unequal-length parallel strings are allowed for demonstration, not recommended as an installation design.

### Optimizers

When enabled, every module operates at its **own** modeled MPP, after its internal substring mismatch and bypass behavior. The optimizer passes at most 98% of that power to the DC bus. It cannot recover missing irradiance, repair a fully dark substring, or make a dark module generate power. An already matched unshaded array therefore loses 2%, rather than gaining power.

The converters have idealized unrestricted conversion ratios and no minimum voltage, current, startup, or string-length limits. Their output voltages adjust in series, while parallel branches still share one demonstrative bus voltage. This is an optimistic bound on module-level mismatch recovery, not a simulation of a named optimizer or inverter. Real hardware limits can reduce the benefit considerably.

## Sun and geometry boundary

The sun uses NOAA's low-order fractional-year declination approximation and standard solar hour-angle geometry, including a leap-year denominator. It omits atmospheric refraction, terrain horizon, and a high-precision ephemeris. A returned negative altitude means the sun is below the horizon; the caller must provide zero direct-light irradiation at night. The electrical solver does not infer a date or daylight status.

Geometry belongs to the 3D application, not `model.js`. The application samples illumination within each substring and passes the resulting effective irradiance here. Treating a substring as one effective-irradiance value does not resolve individual cells, half-cut layouts, cross-ties, diffuse sky light, reflected light, or the exact physical mapping of a commercial module's cell wiring. A hard shadow over a small cell area can behave differently from an equal average reduction across a substring.

## Checks and examples

### Local interaction version 2 (historical topology; superseded below)

`scene-model.js` owns shared canopy dimensions, panel placement, project validation and ordered string topology. A canopy has center `[x, 0.72 * height, z]` and radii `[crownDiameter / 2, 0.32 * height, crownDiameter / 2]`. Visible branches run from points on the trunk to crown clusters. Visible density controls 60 deterministic foliage clusters; branch and trunk geometry remains present at zero density.

For a ray crossing the canopy, normalized chord depth `L` gives transmission `exp(-density * foliageFactor * 3.2 * L * (0.65 + 0.7 * seededVariation))`. This smooth optical-depth approximation replaces the original binary sample threshold. It provides deterministic, monotonic responses to density for intersecting rays; the coefficient and variation are illustrative, not measured tree optics. Wider canopies change ray intersections. Density cannot shade panels when the tree is outside the sun path. The renderer uses cluster geometry; the solver uses the envelope. They intentionally do not agree pixel-for-pixel. Branches/trunks have rendered shadows but are not separately ray-tested by the electrical model. Diffuse assumptions and other original limitations still apply.

Wiring drafts are transient lists of distinct panel IDs. Only an explicit commit to MPPT 1 reassigns string IDs. The ordered panel list controls visual positive-to-negative links; string ID grouping still controls `calculate`. All completed strings terminate at a single common bus, including unequal-length example strings. Drawn crossings create no junctions; arbitrary freehand circuits are not accepted. Panel fields `controllerId` and `wireOrder` plus the topology adapter provide a seam for later controllers, but any controller other than `MPPT1` is currently rejected. Real controller voltage/current limits, protection and cable losses are not modeled.

Project schema v2 preserves v1 site and geometry fields and adds those panel fields. Valid v1 projects migrate in memory; saving writes v2. Validation rejects overlaps, duplicate IDs, invalid coordinates and unsupported controllers without mutating the active project. Older v1-only apps cannot open v2 projects. No original Solar Tinker compatibility is implied.

Run all local checks with `node --test --test-isolation=none tests/*.test.mjs`. The application-event harness uses real Three.js geometry and raycasting with a simulated DOM/renderer. It verifies handler/state behavior, not WebGL pixels, actual browser pointer delivery, responsive layout or performance. Those still require browser QA.

Run `npm test` or `node --test tests/model.test.mjs` from this folder. Tests cover full light, complete shade, uniform quarter irradiance, quarter-area localized shade, bypass and no-bypass cases, series/parallel mismatch, unequal-length strings on a common bus, optimizer bounds, conservation, deterministic repeated calls, input errors, seasons, hemispheres, direction, and polar day/night. Fixed-seed shade cases exercise the power bounds across varied inputs. They verify internal consistency, not agreement with measured hardware.

Expected examples:

- One fully lit module: 400 W. Uniform 25% irradiance: 100 W.
- One dark substring, bypass enabled: approximately 262.17 W, including 4.5 W diode dissipation.
- Full + uniformly quarter-lit modules: 386.5 W in series versus 500 W in parallel; module optimizers yield 490 W under their idealized conversion assumptions.
- A two-module string parallel to a one-module string: 810 W, not the 1,200 W obtained by incorrectly adding independent string MPPs.
- Latitude 40° N at solar noon: approximately 73.45° altitude on 2026-06-21 and 26.58° on 2026-12-21.

## References and further validation

- [NOAA: General Solar Position Calculations](https://gml.noaa.gov/grad/solcalc/solareqns.PDF): source for the fractional-year declination approximation and solar-time hour angle.
- [Sandia PV Performance Modeling Collaborative: DC Array IV](https://pvpmc.sandia.gov/modeling-guide/3-dc-array-iv/): explains the shared-current and shared-voltage constraints behind series and parallel mismatch.

The custom curve and optimizer assumptions above are not sourced from, validated by, or endorsed by these references. Engineering-grade work would require measured module curves, the actual cell/submodule wiring, temperature and weather inputs, validated geometric irradiance, manufacturer inverter/optimizer constraints, and comparison against an established PV simulation and field measurements.


## Version 3: independent controllers, disconnected panels and weather

The core single-bus solver in model.js is unchanged. scene-model.js adds calculateControllers(panels, controllers, options), which partitions connected panels by controllerId, calls calculate independently for each partition, and sums nowW, availableW and per-panel contributions. Each controller has its own operatingVoltage/operatingCurrent diagnostics; there is no single aggregate input voltage. Strings within one controller still share one bus. Equal string letters on different controllers cannot merge. Duplicate panel IDs, missing controller references and half-connected assignments are rejected. Panels with both controllerId and stringId set to null are disconnected and contribute zero. Installed nameplate remains separately available as installedW.

Deterministic example: two fully lit modules in series on MPPT 1 and one fully lit module on MPPT 2 yield 800 + 400 = 1,200 modeled W. The same two-module and one-module strings on one common bus yield 810 modeled W under the existing solver. This tests independent optimization, not real product performance. Bypass behavior and idealized 98% optimizers are unchanged. No controller voltage/current limits, DC conversion losses, batteries, output combining design, clipping, temperature or product compatibility are modeled.

Schema v3 adds a controllers list containing MPPT1 and optionally MPPT2. Each connected panel carries controllerId, stringId and wireOrder; disconnected panels carry null controllerId/stringId and wireOrder 0. Valid v1/v2 files migrate to MPPT1. v3 saves require this app revision. Drafts do not affect saved topology or power. Controller removal is refused until its strings have been explicitly moved or cleared. Clearing a saved string uses the pair (controllerId, stringId), not just its letter. One-level saved-wiring undo restores connection fields/controllers only; geometry and canopy settings are retained.

SKY_PRESETS is shared by irradiation sampling and lighting: clear direct/diffuse 0.88/0.12; hazy 0.55/0.22; overcast 0.12/0.28. Daytime directional intensity is 3 * direct / 0.88 and hemisphere fill is 1.1 + 4 * diffuse. These are artistic mappings, not calibrated radiometric light units. PCF shadow filtering uses radius 1 for clear and 4 for haze. Heavy overcast disables sharp directional preview shadows while retaining its existing small numerical direct fraction. Night sets directional intensity to zero and disables its shadows. Diffuse electrical and foliage assumptions remain unchanged. Screen shadow pixels never drive electrical calculations.


## Placement / inspector pass (app 0.4, project schema remains v3)

New-panel placement raycasts the actual roof slab meshes, accepts only their top faces, converts the hit through the cabin transform, then checks the existing x/z boundaries and overlap rule without clamping or searching another slot. The ghost is transient and excluded from electrical inputs and save files. A valid commit adds one disconnected 400 W panel; cancelling changes no project data. Existing-panel dragging retains its prior clamping behavior. This is visual placement within a simplified roof, not a structural installation survey.

Double-click / Edit shortcuts expose only existing per-object placement and string/controller assignment controls. All panels still share the same model ratings; no new electrical equipment parameters were introduced. MPPT summaries show current strings and idealized assumptions. The existing v3 save schema, weather presets, independent-controller calculation and bypass/optimizer behavior are unchanged.


## App 0.5 / project schema v4: rotation and signed daily energy

Schema v4 adds orientation = 0 (portrait) or 90 (landscape). v1-v3 projects migrate to 0. Invalid orientations, rotated overlap and rotated roof-edge violations reject an import atomically. The roof's conservative usable envelope is preserved from the prior portrait placement bounds; the rotated 1.18 x 1.70 m module footprint and roof-plane cosine projection determine new center bounds. Neighbor checks use the sum of the two half-footprints plus the existing clearance. Mixed orientation is supported. A panel's three representative substring strips rotate with its geometry, before roof tilt and cabin heading transforms. This remains a simplified sampling layout rather than an actual product cell map.

The unchanged core electrical solver provides signed perPanelW at each controller's chosen common-bus operating point. Every daily sample stores those contributions, controller power and total power. integrateDay applies identical trapezoidal time weights to each at 0.2 solar-hour steps, including initial hour 0 and final hour 24. Thus sum(perPanelWh) = sum(perControllerWh) = system Wh to floating-point tolerance. Negative bypass contributions are retained; there is no irradiance-based attribution or post-hoc reallocation. Display rounding to 0.1 Wh can create tiny visible sum differences. No battery, inverter or cable losses are added.

Heatmap color is relative to the minimum/maximum panel net Wh within this run. Equal positive contributions are green; all-zero contributions grey; equal negative contributions red. Disconnected panels have zero and remain listed. Partial runs are explicitly marked; edits stop the run and invalidate its samples. Wiring visibility, selection and camera movement do not change topology or energy. Modal configuration currently supports geometry and explicit whole-string reassignment only; nameplate ratings and controller limits remain fixed/idealized pending the separately requested equipment/battery phase.

## Playback and canopy follow-up
Run integration uses its last recorded sample time independently of viewing time. Completed review returns to noon without reintegration; slider scrubbing pauses and preserves completed/partial samples. Clock formatting uses rounded total minutes. Twilight is an uncalibrated smoothstep: direct 0–3 degrees altitude, diffuse -6–3 degrees. Evergreen preview uses five overlapping conical tiers with density-filtered sectors, and rays intersect a finite conical envelope. The envelope approximates foliage; rendered sector/branch shadows are not sampled individually. Leaf-off never removes evergreen foliage.



## App 0.8 overlay measurements (schema remains v6)

livePanelValues reads the active controller's actual ordinary/idealized optimizer panel diagnostics: terminal watts, common series output amps and voltage. Optimized module input amps are explicitly standalone MPP potential before controller limiting, not a claim of measured converter input current during curtailment. Terminal watts are before controller conversion loss; Daily Wh use signed net controller-output contributions after that loss. The two are deliberately labeled different quantities. Disconnected and cutout output is zero; unsupported mixed estimates return null and display EXCLUDED. Daily/live switching and viewing-time scrubbing never integrate new samples or invalidate stored day energy. Physical edits invalidate; completed day automatically selects Daily at noon. Material colors and formatted label strings update on solver/time/metric/run changes, not per animation frame; geometry is retained during scrubbing.


## App 0.9 switching and optimizer UI boundary

simulateBattery uses each left-end sample's applianceOn boolean over [a.t, b.t), while net PV remains the trapezoidal mean of a.w and b.w. Legacy samples without a flag fall back to the battery specification. A switch event changes demand only prospectively; it does not interpolate between switch states or smear an event over the preceding interval. Full and partial sums, chart battery-power integrals and per-step conservation retain their previous definitions. This is a deterministic 12-minute-resolution scenario, not sub-minute load metering. Toggles after completion do not modify samples. New runs start from the current switch and initial SOC with a fresh event sequence. Saved projects contain settings, not recorded events/results.

Manual-time live battery flow is a one-second scenario starting from configured initial SOC, explicitly separate from retained daily history. Running live flow uses the recorded SOC. It never updates daily energy. No appliance duty-cycle replay or battery-state reconstruction from arbitrary viewing time is implied.

The quick optimizer tool now previews and explicitly applies a uniform flag to all connected panels on the selected MPPT. It does not silently pretend that a single optimized string can share the bus with unoptimized strings. That topology remains excluded. The unchanged unrestricted idealized DC/DC branch exposes module-side standalone MPP potential and common external series current; equal module inputs are legitimate for identical irradiance/specs. No new hardware compatibility or numerical accuracy claim is introduced.




Results → Annual estimate & simple payback → choose year → Estimate year. The selected scene, tree geometry, shading, latitude, heading, dimensions, wiring, optimizer settings and electrical limits generate 12 distinct profiles on each month's 15th at 12-minute solar intervals. Each immutable profile is reused for that month's actual days. Battery SOC and grid-mode initial-energy provenance carry continuously. No daily/monthly charge reset, no credit for energy merely left in storage. Saved appliance programs repeat daily, manual mode repeats its saved switch state; live playback overrides are not replayed. Night loads and within-step program boundaries remain included. All months use the selected global sky; leaf season follows hemisphere/date.

The asynchronous batch yields during profiling and daily integration. Cancel or a relevant edit prevents incomplete results being presented as complete. Daily results/design stay intact; time/unit display changes preserve annual results. Year changes invalidate their annual result. Cost-only edits refresh payback without rerunning physical profiles.

Monthly/annual table: available PV after input losses, household load, imports, exports, DC curtailment, grid-mode solar self-consumption, unmet load, savings, excluded initial-storage benefit and end SOC. Off-grid self-consumption attribution is N/A (legacy energy results remain available); no off-grid bill savings/payback. CSV has readable rounded values; printable HTML includes assumptions, rates, cost worksheet and simple payback. Use browser Print for paper/PDF; no PDF is pre-generated.

Solar-derived savings = same-load no-solar variable bill − (imports × import rate − eligible exports × export rate) − bill reduction from used initial battery energy. Simple payback = complete configured cost / positive annual savings. Signed bills/savings are preserved. This is a representative-day scenario projection, not historical weather, validated production forecast or full financial ROI. No financing, degradation, replacements, incentives, tax, shipping, time-of-use or rate changes. Existing electrical/optical approximations, uniform-per-tracker optimizer limitation, incomplete cold/startup checks and utility approval caveats remain.


## Shade study refinements (0.22.6)

Schema 14 stores independent crownDiameter in metres (0.5–20 m). Legacy width multipliers migrate as height × 0.56 × width, preserving initial geometry. Height edits no longer change diameter. Legacy width remains for import compatibility but the explicit diameter takes precedence.

Seasonal estimate uses smoothstep transitions from foliage factor 0.2 to 1 throughout April and 1 to 0.2 throughout October in the northern hemisphere, with a six-month phase shift in the south. Explicit leaf-on/off and evergreen behavior are unchanged. This is a calendar approximation, not calibrated phenology; monthly/annual estimates during transitions change.

Shade loss (%) = 100 × (1 − sampled irradiance / unobstructed irradiance), averaging the same three electrical sections. The reference retains orientation, sky, date and time, and excludes both structure and tree obstructions. Diffuse light follows the existing unoccluded approximation in both samples; trunk/branch geometry is not ray-tested. This is an optical ratio, not power or daily-energy loss. Zero reference light shows no daylight (grey); the scale remains fixed at green 0%, red 100%. The renderer remains illustrative, including no sharp shadows under overcast. Selection dimming remains a separate presentation effect.


## Schema 20: beginner estimates and weather mix

The guided balcony entry builds a normal validated project: one 400 W illustrative panel per independent generic input, an 800 W shared inverter, 96% conversion, no battery, and no credited export. Monthly use is divided by 30 × 24 hours for a constant household baseline; additional appliance load is zero/off. The building/overhang remains in the optical model when trees are removed.

`annualWeather.mode` defaults to `single` for existing files. In `mix`, each month has clear and overcast solar profiles; the actual daily sequence uses the change in rounded cumulative expected overcast days. The entered annual percentage is rounded to whole days, and cloud days are evenly spread rather than seasonally calibrated. Storage and initial-energy provenance carry continuously across days. Reports retain the requested percentage, realized day counts and sequence assumption.

`equipment.costMode` defaults to `itemized`. In `total`, `totalBudget` replaces the worksheet total and its missing-cost list for payback, while preserving itemized entries. Null budget means incomplete; zero is an explicit zero cost. Budget-only changes do not invalidate the energy scenario. Four report snapshots can be pinned in memory for comparison; they are not part of project serialization.
