Skip to content

Add Canadian provinces and territories to the jurisdiction data - #1

Closed
JonathanNus wants to merge 3 commits into
pascalorg:masterfrom
JonathanNus:canada-jurisdictions
Closed

Add Canadian provinces and territories to the jurisdiction data#1
JonathanNus wants to merge 3 commits into
pascalorg:masterfrom
JonathanNus:canada-jurisdictions

Conversation

@JonathanNus

Copy link
Copy Markdown

Bones ships US-only jurisdictions, so a Canadian project either falls back to the INTL generics or, worse, gets pointed at a US state whose frost line is too shallow. The concrete failure: prairie frost penetration runs 1800–2400 mm (71–94 in), deeper than any US state in the data (max 60 in), so footings come out a foot or more short with nothing in the UI to say so.

This adds 16 entries — all 10 provinces, the 3 territories, and a generic NBC 2020 fallback.

Data only

No engine changes. Everything rides the existing path: two JSON rows merged by profileFor(), then the same five levers in applyJurisdiction().

Notes on the choices

Codes are ISO-3166-2 style (CA-ON-S, CA-BC, …) for two reasons. Bare CA is already California, so a "Canada" code there would be a genuine footgun. And jurisdictionOptions() sorts by code, so the CA- prefix groups the Canadian entries together in the dropdown without touching the sort.

Ontario is split three ways (South / East / North). Its frost line runs 1200 mm in the GTA, 1800 mm in the Ottawa Valley and 2100 mm at Thunder Bay — a wider spread than most US states carry in one row, and the Ottawa Valley is also Canada's second-highest seismic zone. One row could not represent it honestly. The other provinces are single rows.

Canada is not an IRC jurisdiction. Every row sets ircBase: null and cites NBC Division B Part 9 ("Housing and Small Buildings"), the analogue of the IRC — the same shape the Wisconsin UDC row already uses for its non-IRC state code. The note field warns inference engines not to assume IRC section numbers or IRC braced-wall-panel methods, since NBC 9.23.13 selects lateral bracing from Sa(0.2) and hourly wind pressure instead.

Unit conversions are documented per row. Canadian codes state ground snow as Ss in kPa and frost depth in mm; converted here to the psf/inch fields this schema uses, at 1 kPa = 20.885 psf.

Timezones: guess.ts gains the Canadian IANA zones, and an en-CA locale now resolves to CA-GEN rather than INTL. One caveat noted in the code: modern tzdata folds Montreal into America/Toronto, so Quebec guesses as southern Ontario. The dropdown still wins, as with every guess.

Territories carry an explicit permafrost warning in frostLineNote (so it surfaces in the panel's notes, not just the data file) — a spread-footing depth is not a meaningful output where pile or space-frame foundations are the norm.

Sources

Values are typical provincial figures, not site-specific: each row carries a caveat saying so, matching the existing disclaimer style. Quebec is flagged as still on the NBC 2015 base.

Tests

src/jurisdiction/canada.test.ts adds 10 tests. They pin the things that would regress silently — a missing data row degrades to the INTL fallback rather than throwing, so "it still renders" is not evidence a province landed. Also asserts profileFor('CA').name === 'California' so the collision stays fixed.

Full suite: 444 pass, 0 fail. tsc --noEmit clean.

One thing I found but did not fix

insulationByClimateZone is keyed "1""8", but wall-layers.ts looks it up with the raw stateClimateZone value, which reads like "5A (4A NYC/LI, 6A Adk)". Zero of the 51 existing US entries resolve, so the R-value label never renders for anyone. I left it alone — it is orthogonal to this PR and fixing it would change output for every US state. Happy to send a separate PR if useful.

@JonathanNus
JonathanNus force-pushed the canada-jurisdictions branch from 814a0c0 to 193cc97 Compare August 16, 2026 03:50
Bones ships US-only jurisdictions, so a Canadian project either falls back
to INTL generics or, worse, picks a US state whose frost line is too
shallow. Prairie frost penetration runs 1800-2400mm (71-94in) — deeper
than any US state in the data — so footings were being under-dug by a
foot or more with no warning.

Adds 16 entries covering all 10 provinces, the 3 territories, and a
generic NBC 2020 fallback. Ontario is split three ways because its frost
line runs 1200mm in the GTA to 2100mm at Thunder Bay, which is a bigger
spread than most US states carry in a single row.

Data only — no engine changes. Canada is not an IRC jurisdiction, so
every row sets `ircBase: null` and cites NBC Division B Part 9 (the
analogue of the IRC for housing and small buildings), the way the
Wisconsin UDC row already does for its non-IRC state code.

Codes use ISO-3166-2 style (CA-ON-S, CA-BC…) for two reasons: bare `CA`
is already California, and `jurisdictionOptions()` sorts by code, so the
`CA-` prefix groups the Canadian entries together in the dropdown
without touching the sort.

Also maps the Canadian IANA timezones in guess.ts, and sends an `en-CA`
locale to CA-GEN rather than INTL. Note tzdata folds Montreal into
America/Toronto, so Quebec guesses as southern Ontario — the dropdown
still wins, as it does for every guess.

Conversions are documented per row: Canadian codes state ground snow as
Ss in kPa and frost depth in mm, converted here to the psf/inch fields
this schema uses (1 kPa = 20.885 psf).
@JonathanNus
JonathanNus force-pushed the canada-jurisdictions branch from 193cc97 to 6861d07 Compare August 16, 2026 03:52
…ss engine

Two changes a host needs:

1. The wall-mode takeover ('down' while X-raying) moves from the 3D
   renderer to the panel. The renderer lives as long as the bones:framing
   node does, so leaving the Bones tab kept the host's walls hidden — and
   because wallMode is a persisted viewer preference, the stuck view
   survived reloads, and merely OPENING a scene that contained an X-ray
   node blanked the walls. The panel unmounts exactly when the user
   leaves, which is the lifetime this intent actually has. The state
   machine lives in view-takeover.ts (viewer-import-free, unit tested:
   restore on leave, never stomp a user-picked mode, no ownership when
   walls were already down). Also aligns with the plugin appearance
   contract (#625): the renderer no longer writes host presentation state.

2. index.ts exports the pure derivation surface — computeLevel,
   computeLevelUncached, computeTakeoff, cutList, the row/member types,
   extractLevels/extractRoofs — so a host estimator can compute the same
   quantities the panel shows without mounting anything.
   computeLevelUncached is exported because a whole-building rollup loop
   would otherwise thrash the 1-deep memo the panel and renderer share.
White-label commit for the PreSell deployment — intentionally NOT sent
upstream (their product name is theirs). Panel heading and badge become
"Structural Framing" / "Beta", and the 'Pascal project' fallbacks on the
printed plan set become 'Project'. Keep this as the TOP commit when
rebasing onto upstream bumps.
Snoopy147 added a commit that referenced this pull request Aug 18, 2026
F1 (M2+S1 regression): unit #1's PAD inherited the cabinet's oblique
equip-bearing rotation after an RO slide — a 45° square pad reaches
(|sin|+|cos|)·half toward the wall and punched through the assembly
(TX wythe, FL CMU). Pads now always pour parallel to the row wall;
only the cabinet keeps the legacy facing. Gate: slid-anchor scenario,
yaw multiple of 90° + clearance.

F2 (E5+S8): a device override committed against a DROPPED colinear
twin silently re-targeted the derived wall with the twin's wallT.
applyDeviceOverrides now resolves through duplicateOf and re-projects
the twin-relative t onto the kept centerline; unknown/unframed walls
warn instead of silently falling back. Gate: twin-committed override
lands at the world spot the user dropped it.

F3 (S1+S6+E5): 'device blocking' embedded inside per-wall batts —
splitBattsAroundBlocking notches the batt (below+above pieces, the
installer's cut). Gate: no insulation×blocking overlap end-to-end.

Byte-equality pin re-captured (F1's pad rotation is the intended diff).
858 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoopy147 added a commit that referenced this pull request Aug 18, 2026
…nchor

buildServicePointNodes seeded at raw placeHeatPumpSpot, which can front
a window RO the engine's condenserRow had already slid unit #1 away
from — the sign stood apart from its unit (narrow-round finding).
placeCondenserSeedSpot runs the same slide; gate pins seed == cabinet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoopy147 added a commit that referenced this pull request Aug 21, 2026
#1)

Four loop rounds: the 0.4m stub replaced by a wall-following suction
3/4" (insulated, cold-blue) + liquid 3/8" (warm-red) pair — ONE
shared route (pair-envelope detour decisions), uniform +/-2cm
vertical offset, risers ROLL 90 degrees perpendicular to their own
wall (triplet-index axis — geometric lookup was ambiguous at
corners), corner-cancel kills phantom down-up risers at junctions;
routePipe reused from plumbing (bandHalf param, byte-pure); paper:
schematic +/-2.5px nudge so both colors print, MEP legend rows,
takeoff suction/liquid/insulation lf + >15m oil-return advisory.
Examiner PASS on pixels round 2; merge-gate attacks run inline
(skeptic agents stalled on API errors) — all four corner scenes
zero SAT hits, no duplicates.

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

# Conflicts:
#	src/framing/master-baseline.json
#	src/plans/plan-set.test.ts
Snoopy147 added a commit that referenced this pull request Aug 23, 2026
The fence+RO compound broke A4 seed parity: placeCondenserSeedSpot's
corner-flip guard still consulted nearestExteriorExit(slid) — the exact
wrong-oracle the row fix retired. A garden fence 0.4 m from the slid pad
always beats the elected wall (0.6 m by construction), so the guard bailed
to the RAW anchor; the seeded node fed back as a verbatim override and the
post-seed compose drew the unit dead-center on the window the engine had
slid past, disconnect re-hosted to the fence, zero warnings. Every
activated scene seeds automatically — the wrong recompose was the DEFAULT
lifecycle.

(a) the guard now tests the slid spot's along-wall projection against the
    ELECTED wall's span (condenserRow already anchors there); off-wall
    slides keep the raw anchor (bail arm pinned);
(b) DECISION — machine-seeded override coherence: layoutHvac always runs
    the election; an override within ε (1e-9, float round-trip tolerance,
    not a snap radius) of the election spot or the engine's own slid
    unit-#1 spot is the machine's point and keeps the ELECTED row wall —
    post-seed compose == auto compose, byte-equal. Anything farther is a
    real user drag and stays verbatim-nearest (A4: never silently
    re-anchored). Chosen over board-noting the class because auto-seeding
    makes the recompose path the default, not an edge.

Gates: fence+RO compound (engine unit #1 == seed; post-seed byte-equal;
disconnect stays w_south), fence-only (no flip w_south→w_fence + user-drag
arm stays verbatim-nearest), off-wall bail arm. Mutants bite both ways:
guard old-oracle, guard removed, ε-anchor removed, ε blown open (4/4).
Byte gates re-held: 16 hunt shapes, exhibit override path, E5 recapture
byte-identical. Suite 1621 + 1 todo, tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gorkking pushed a commit to gorkking/Pascal-Editor-plugin-bones that referenced this pull request Aug 23, 2026
… threading

Anonymized minimal repro of the exhibit (misclassified interior walls +
covered zoneless void): the walk skips the Bathroom spot AND the covered
mid-plan void, lands 0.6 m outside the true south wall — outside every
zone + outside coverage, unflagged, silent, disconnect present, line-set
continuity to the AH on wall rails (E2). Pins:
 - coverage probe is load-bearing (blind walk validates the void at 5,1.9);
 - verbatim override still wins + warns (fast-follow unchanged);
 - seed parity: placeCondenserSeedSpot(coverage) == engine unit pascalorg#1;
 - walk exhaustion → least-bad spot kept + ⚠ flag + warning, distinct
   from the legacy no-exterior-wall class;
 - courtyard DECISION: outdoor zone legitimizes over a patio slab; no
   zone → covered court reads inside, walk continues; slab hole = court;
 - row anchors to the ELECTED wall (garden-fence divergence);
 - computeLevel threads probeSlabs (F3, mep-honesty) and the seed action
   threads them too (place.test).

All 9 mutation probes bite (walk removal, coverage drop, outdoor-blocker,
null-on-exhaustion, warning/flag drops, compute/place thread drops,
elected-wall ignore).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoopy147 added a commit that referenced this pull request Aug 23, 2026
…d gesture)

Julien 2026-08-23 overrules the day-10 sign-as-only-handle trade: in xray
with hvac ON the heat-pump service node mounts a near-invisible pick proxy
(bones:device convention — opacity ~0, no depth write; the host outline
mask pass re-renders silhouettes with its own material) sized to the
engine cabinet ×1.04 at the unit center height and unit-#1 yaw (memoized
computeLevel, zero extra derivation). Unit #1 is verbatim at the node (A4)
so the proxy rides the node group — live drags track, engines re-route on
commit via the proven sign-drag flow. Sign plates keep picking; visible
bodies stay the handle (no proxy off/basement/toggle-off/no-framing);
engine silence mounts nothing (no phantom hover volume); WH/meter stay
proxy-less (stated follow-up candidates). The one stated exception to the
X-ray raycast-no-op convention — a SERVICE mesh, documented at the mount;
framing/asset meshes stay raycast-disabled (A6).

- placement.ts: ServicePresentation.pickProxy column (suppression matrix)
- service/proxy.ts: pure resolver + doctrine; renderer mounts the mesh
- gates: place.test.ts matrix updated as INTENDED; proxy.test.ts
  footprint/height/yaw/A4 parity + silence + composition arms
- CHECKLIST: A6 unwarp clause, new A7 row; HP-UNWARP expected-diff manifest

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoopy147 added a commit that referenced this pull request Aug 24, 2026
…, headless exports todo, takeover superseded

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

Copy link
Copy Markdown
Collaborator

This is an excellent PR — genuinely. The data discipline here is exactly what this repo runs on: NBC citations per row, the kPa→psf conversions documented where they happen, honest per-row caveats, the CA=California collision guard, permafrost surfaced as a real warning instead of a silent number, and tests that pin the silent-INTL-fallback failure mode rather than just 'it renders'. The Ontario three-way split is the right call — one row couldn't carry that frost/seismic spread honestly. 👏

How it'll land: the branch is stacked (it carries the #2 takeover commit plus a white-label commit whose own message says it wasn't meant to ride upstream 🙂), and master has moved a lot since Aug 16 — the suite went 444 → 1,783 tests and the jurisdiction data grew substantially. So rather than merge the branch, we'll cherry-pick your jurisdiction commit with your authorship preserved, rebase it onto the current schema, and run it through the adversarial verify loop every change here goes through. The seam we'll attack hardest: with ircBase: null, no IRC-section label may ever fire on a CA row, on the panel or the printed sheets — your NBC 9.23.13 note is exactly the right warning and we'll gate it.

Also noted: the Quebec NBC-2015 flag, and the Montreal→America/Toronto tzdata caveat you documented in the guesser.

Closing the PR to track the landing internally — the commit arrives with you as author. Thank you for this one, it's the kind of contribution that raises the bar. 🙏

@Snoopy147 Snoopy147 closed this Aug 24, 2026
Snoopy147 added a commit that referenced this pull request Aug 24, 2026
…ine seeds — survives the default lifecycle

The editor auto-seeds the bones:service node at the machine spot on
first load; the next compose runs verbatim and a verbatim row never
snaps, so unit1OffGrid came back false and the flag vanished after ONE
compose on every activated exhaustion-class scene (skeptic + visual,
scene fbd31d0a5faf). The epsilon-anchor already proves the point is the
machine's own — the FULL snap outcome now rides recognition:
machineSeedOffGrid = autoRow.unit1OffGrid composes the flag on unit #1's
pad + cabinet, so post-seed == auto is byte-equal ON the exhaustion
class too (first JSON diff was the flag field). A real user drag — any
position that is not a recognized machine spelling — stays flag-free
(A4). Gates: exhaustion-class byte parity + 3x seed->compose persistence
+ genuine-drag clearing; the round-1 'dragged at the byte-identical
machine point' assertion inverted (that point IS the machine's), the
genuine-drag arm uses an off-machine point. Mutation (launder on seed):
1 fail, restore green. Suite 1772 + tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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