Repository navigation
Scriptoscope 0.4.0
Third incremental release from the v2 engine branch (#378). Three more families move onto the decoded Kaleidoscope 2.3.1 engine: the window recipe for the 13 schemes with a wnd# recipe, the slider and the progress bar.
- Window recipe on the v2 engine (#432, #444). Layout, title, widget remap, draw and hit test follow the decoded kDEF window widgets (
docs/spec/engine/window-widgets.md), fixing runtime divergences F1–F10. Mostwnd#windows change slightly; see the migration notes.- Accessibility divergence (DECISIONS #12): title-bar widgets on inactive windows stay focusable and keyboard-operable, although the original makes them inert.
- Font substitution (DECISIONS #13, #453): the runtime can't draw the original's condensed title face, so titles are sized and truncated to the plate for the font we draw. They also move down when our taller font would clip their tops.
- Slider on the v2 engine (CDEF 3; #419, fixes #407). It adds
pressedPart,variant,tickMarks,thicknessandbackground. Vertical sliders now put 0 at the bottom. - Progress bar on the v2 engine (CDEF 5; #440, #446). It adds integer
rangeandindeterminate/phase(DECISIONS #10, #11). The 8 schemes without progress art now draw the original's own procedural bar, andcomposeProgressnever returnsnull. - Loader: the public
loadKaleidoscopeScheme(npm export and the Pagestheme-loader/module) passes the engine'scinfand icon-blit ids by default (#428, #434, #439; fixes #431). Dropped archives get the same output as bundled themes. - Fidelity ledger: 340 verified, 0 mismatched, 6 unsupported and 1,486 unknown of 1,832 obligations. In 0.3.0 it was 257 verified and 33 mismatched.
Also in this release
- Specs:
- Tooling:
lane:readynow rejects silent reverts of base commits, and CI builds the demo (#447).
- Known gaps:
Changes
- v2 engine:
wnd#windows now lay out and hit-test through the decoded 2.3.1 window recipe (engine familywindow,docs/spec/engine/window-widgets.md). A new driver (src/engine/families/window.ts, tables insrc/engine/tables/window.ts) follows the spec rows: part-code verdicts (PV), layout sums and placed ends (LS), the too-short step (TS), the allocation path (AP), per-cell widths with the unclamped budget (AW), the titled side (TT), cell rects (CR), the widget remap (RM), the pressed-widget draw (WD9) and the widget hit (HT).composeChrome.tsis now a facade over it; the legacy classifier, too-short negotiation, half split and placement-based widget remap are deleted. It fixes the window-widgets §7.2 findings F1–F10: a collapse box inside a dropped code-10 cell is now empty (not drawn, not hit, RM6); below the minimum the budget goes negative instead of clamping (AW8); the title plate is placed on the content centre withW = SW + marginsand drops at ≤ 2 px of room (TT2–TT9); close, zoom and collapse are separate flags (F7); the collapse gate is re-read on every layout (F5, LC3); widget hits need the pressed-strip cicn and its mask (HT3–HT7, F8; inactive windows keep keyboard-reachable buttons, a runtime policy where HT2 would make them inert); the cell bands use the template cicn bounds (CR2/CR4, F6).
Migration notes (window recipe)
-
Pixels change on most
wnd#windows (engine diff: 1530 of 1656 existing window cells, plus 156 new popup-family cells, each blessed intests/engine-diff/expected/window.jsonwith its spec row). Each side now ends at its placed endP[N] = S[N] + content delta(LS7, after the AW7 walk), so growers no longer absorb template slack past the last border. Untitled windows no longer draw the code-6 title bezels or plate (TT4, AW5); titled windows get a plate exactlySW + marginswide on the content centre instead of the measured width plus 18 px of padding, and at the template size a too-narrow side drops its title (TT3) rather than overflowing the frame. -
composeWindowChromeoptions:titleWidthPxis nowSW, the bare title width (renderWindow no longer adds padding). New optionaltitleWidthCondensedPx(TT2; default the TT2a declared valueSW − titleLength),titleLength(default 1 whenSW > 0),active(drawActive, default true; renderWindow leaves it set),widgetCicn(the WD11 pressed strip,cicn −14330) andpressedWidget. -
ComposedChromegainswidgets(the reachable widget hit areas),hitWidget(x, y)(the HT rows, mask included) andlayout(the engine layout).shadeClearedis removed.titleRegionis now the code-5 plate, not the run of 5/6 cells. NewtitleRect: the remapped title rectt4p(TX3). -
resolveTitleWidgetRects(wt, composed)returnscomposed.widgetsforwnd#windows; it no longer remaps fromplacement. Behaviour change: a cleared or emptied collapse box has none, and a press inside a widget rect but outside the strip's mask falls through to the window (HT7). renderWindow loads the pressed strip through the engine resource chain; without it no widget is hit or drawn pressed. -
The title text follows the decode (TX2–TX5, TX8): left-aligned in the remapped title rect, baseline 4 px above its bottom, clipped to that rect + 2 px, truncated only when it does not fit; it is centred on the content (the layout centres the rect, TT6/TT7), no longer on the plate. Untitled windows and an empty title rect draw no text. The runtime has no condensed face, so TX5 only truncates, and TX7's shadow string and sampled colours are not drawn (runtime colour rule kept).
-
popup-windowtitled sides now follow the decoded popup placement (AP1, PP1–PP7) with the creation values of the popup data (pdMode 0,pdLen 33): a 13 px plate 10 px in from the content's left edge, whatever the title; the titled-side rows do not run there. This default visibly truncates every popup title to the 13 px title rect (PP6/PP7 declare the creation values; who changes the popup data later is not decoded). Popup windows also draw the WD6 raw template blits: the type-5 and type-6 rect-list art at the content's top-left and top-right corners and the 15×15 corner ending at the body's bottom-right at the content's bottom-right (under the DOM content). -
Sides now draw in the decoded order (top, bottom, left, right) with CopyMask (WD4, qd-primitives Q5), so transparent template pixels no longer overwrite earlier sides.
-
The leading region
[0, S[0])and the trailing region past a side's end are left unpainted, as in the original (no full-draw routine paints them, window-widgets §6 Q3): the content behind the window shows through there. The placement map no longer reports them (no segment 0 or −1 slices). -
Inactive windows keep focusable, operable widget buttons, although the decode makes them inert (HT2), so keyboard and assistive-technology users can act on background windows; widget art still follows the decode (DECISIONS #12).
-
Pressed
wnd#widgets draw the scheme's pressed-strip cicn (WD9) instead of darkening the template; a scheme without it draws nothing. -
The L4 parity lock
src/engine/windowChromeParity.*is removed. The engine diff covers its archives, window types, states and titles at native, 97×53 and 320×160, but not its 240×120 size.tests/engine-diff/cases.mjsacceptswindow-widgets.mdrow ids for the window family. -
Window titles fit our title font (DECISIONS #13). Two runtime limits of #432's decoded title draw are fixed. The runtime has no condensed face and draws titles at full width, so
composeWindowChromenow defaultsSWctoSW(TT2 no longer narrows the plate below the drawn text). Our title font is taller than the original's, so when its ascent would start above the remapped title rect, the baseline moves down by exactly that overflow, and no glyph is cut at the top. The decoded clip and baseline are kept otherwise.
Migration notes (window title fit)
-
composeWindowChrome({ titleWidthCondensedPx })now defaults totitleWidthPx(SW). It used to default to the TT2a valueSW − titleLength. Pass a measured condensed width to get TT2's narrowing. -
Titled-utility titles on 1984, 1990, floppies, windows-31 and windows-95 now draw 1 px lower, inside the title rect: their letter tops were cut before. No title's descent leaves the rect (checked on every wnd# scheme with a descender title). Document-window titles are unchanged.
-
Titles truncate to the plate: the truncation room is the title rect + 2 px (TX5) but never past the plate's right edge, so on dolphin-som and evolution, whose title rect ends flush with the plate, a truncated title no longer draws 1 px past it. The clip stays the decoded title rect + 2 px. The room never drops below the title rect's own width, so templates whose plate is narrower than the title rect (floppies, 1138) keep titles that fit whole.
-
Unchanged by design: titles are left-aligned in a plate exactly
SW + marginswide, so on schemes whose title rect fills the code-5 cell (evolution, dolphin-som) the text starts at the plate's first column. BeOS tabs follow the TT rows (no extra plate padding). -
v2 engine: the progress bar now draws through the decoded 2.3.1 progress CDEF 5. A new driver (
src/engine/families/progress.ts, tables insrc/engine/tables/progress.ts) followsdocs/spec/engine/progress.md. It forces the control rect to 14 rows (RECT1). The path is chosen by thecinfgate: inactive (hilite 254 or 255) needscinf −10077, and without it the whole bar draws with the active ids (GATE1/GATE1a). Nocinf −10080or a missing frame cicn means CDEF 5's own procedural bar (GATE2/GATE3). Otherwise the frame is drawn as a body-less cinf border (FRAME1–FRAME8), with one-column bands as lines andtile ≠ 0bands tiled. The inner rect is inset bykwhenk > 2, else 2, and bys(INSET1/INSET2). The determinate split isx = I.left + trunc(w · offset / range), unclamped (DET1–DET4). The fill[I.left, x)and the track[x, I.right)are each sliced from their owncicn/cinfpair: halves, caps, a one-column or tiled middle anchored at the start or end, with the art keeping its own height (SLICE1–SLICE7). Indeterminate mode uses pattern frames or a 4 px-per-phase shift (IND1–IND7). Art is copied through its mask (BLIT1–BLIT8) and nothing is erased (ERASE1–ERASE4). This fixes every §8 divergence: the eight procedural schemes (animals, apple-platinum-2, beos-r503, black-platinum, monkey-paradise, platinum-8, slimes, system7-nostalgia-silver) no longer use the "lavender 2-part" cicn −10223/−10224 model. They draw the procedural bar: bevel lines sampled fromcicn −10224, a FrameRect, then dark grey and lavender PaintRects orppat −10064(FB1–FB10). The removed behaviours are the per-part inactive→active fallback, disabled drawing as active,nullreturns, rounding, the pixel-scan inset, the track-under-frame draw order, the frame's centre cell overwriting the track, stretched bands and art, and the unmasked copies. Monkey-paradise's indeterminate procedural bar draws nothing inside the frame (FB9, an undecoded rect; the control-panelppat −10064is not shipped, DECISIONS #5). The legacy progress drawing code is removed. The engine diff gate gains aprogressfamily (1,458 cells), and reviewed progress goldens are re-recorded.
Migration notes: progress bar
-
composeProgressnow always returns aPixelBuffer(it never returnsnull): schemes without progress art get CDEF 5's procedural bar. The buffer is always 14 px tall. Pixels nothing draws stay transparent. -
New optional
range: { value, min, max }(DECISIONS #10): 16-bit signed integers, unclamped. A value outside[min, max]moves the split past the track, and the trace records the overflow. Non-integers or values outside −32768..32767 throwRangeError. The normalizedvaluestays (clamped to 0..1, mapped totrunc(v × 10000)of 0..10000). Passing bothvalueandrangethrowsTypeError. -
New optional
indeterminateandphase(integer 0..255, DECISIONS #11). NewcomposeProgressFrame(theme, opts)returns{ buffer, phase, path }: one drawn frame plus the phase after the draw (frame mode resets an out-of-range phase to 0). NewProgressIdlehelper (interactive layer) keeps the phase and advances it once 3 ticks have passed (IDLE2). Callidle()from a timer and redraw withcompose(theme, opts)when it returns true.progressTickCount()is its 60 Hz TickCount stand-in. -
Pixels change:
state: 'disabled'now draws as inactive;'pressed'draws as normal. An inactive scheme withoutcinf −10077(apple-lisa) draws the active bar. Fill and track art keep their height, so 14-row art covers the frame rows (floppies, windows-31, windows-95). -
inspector.resourceRoles.progress(ThemeProgressModel) andprogressSummaryare deprecated. They no longer describe how the bar is drawn. Removal is tracked in #436. -
Loads gain engine
cinfbytes for the progress ids (−10080..−10075) when engine cinf ids are on (the default forloadThemeand the publicloadKaleidoscopeScheme). -
The public
loadKaleidoscopeScheme(thescriptoscope.jsexport and the Pagestheme-loader/module) now also passes the engine's icon-blit cicn ids by default, the same asloadTheme. Archives loaded through it get the icon-blit depth and opaque companion for the scroll-bar arrow cells and the built-in frame's corner, stripe and sample cicns. Without them the icon blit fell back to the masked copy and tracedunimplemented. No pixel changes on the 18 bundled themes (#434 review). PassiconBlitCicnIds: []to opt out. -
Pages: the published standalone decoder is the public wrapper (fixes #431).
https://khawkins98.github.io/scriptoscope/theme-loader/loadKaleidoscopeScheme.jsused to be a verbatim copy of the conversion core, which leavesengineCinfIdsoff, so a consumer importing it got no enginecinfs and every slider would hand off to the Platinum fallback once 0.4.0 ships themed sliders.npm run build:demonow buildssrc/loadKaleidoscopeScheme.tswith the core into one ES module (vite.loader.config.js) and publishes it under the same URL, so existing imports, andtheme-loader/index.js, which re-exports it, get the engine cinf ids by default. PassengineCinfIds: []for the old behaviour. The.sitpath still lazily imports the publishedsit-wasm/index.mjs. The rest oftheme-loader/is copied unchanged, and the npm package and library build are unchanged. -
Demo: dropped archives draw engine sliders.
demo/_theme-drop.mjs(used bydeclarative-hostile-css.html) anddemo/diagnostic.htmlimported the conversion coretools/theme-loader/loadKaleidoscopeScheme.js, which leavesengineCinfIdsoff. A dropped.sit/.rsrctherefore had no enginecinfs and every slider fell back to Platinum. Both pages now import the public wrappersrc/loadKaleidoscopeScheme.ts, which passes the engine cinf ids by default. No other demo page imports the loader. The library itself is unchanged. Leftover legacy slider-layout code is tracked in #427. -
v2 engine: the slider now draws through the decoded 2.3.1 slider CDEF 3 (fixes #407). A new driver (
src/engine/families/slider.ts, tables insrc/engine/tables/slider.ts) followsdocs/spec/engine/slider.md: thecinf-only gate (G1–G3), the per-control record (ID1–ID9), the track laddergb + tier → gb + 1 → base + tier → base + 1with acinfrequired at each id (TR3–TR6), cap/halves/tiled slicing with one-column middles drawn as lines (SL1–SL6, BL4–BL7), the thumb cell map 0 inactive/disabled, 1 normal, 2 pressed, 3 ghost (TH1–TH5), tick marks (TK1–TK5), the 16 px hit band (HT1–HT4), and tracking (messages 5, 6, 26). It fixes the six disagreement classes pinned in #407: floppies (slider cicns, no slidercinf) now hands off to the procedural Platinum slider (S1); disabled and inactive draw thumb cell 0, not the ghost cell (S2); the thumb is copied through its mask (S3); the whole control rect is erased to a background and copied opaque (S4); the thumb sits atR.left + c + trunc((alongR − tA − 2c) · offset / range)with the vertical minimum at the bottom (S5); and a one-column middle (cinfbyte 2 = 0) is drawn as lines, not stretched (S6). One-column track lines take the BL8 colour-table colour (the loader's sampler companion, or the decoded colour where the loader found them equal; otherwise the shortcut is traced, BL12); a cicn with a non-zero bounds origin and a shortcinfare traced (BL11, CF3). A composition bound to a caller signal stops decoding when the signal aborts or the theme's assets are released, including between the archive and base tiers. The legacy slider drawing code is removed. The engine diff gate gains asliderfamily (2,418 cells); reviewed slider goldens are re-recorded.
Migration notes (0.3.0 → 0.4.0): slider
composeSlidergains optionalpressedPart: 'thumb' | 'pageUp' | 'pageDown'(DECISIONS #9; native 129 / 22 / 23).state: 'pressed'with no part meansthumb; a page press draws the pressed track with the normal thumb.interactiveSlidersets the part from the hit under the pointer (part 0, outside the 16 px band, paints the normal state).- New optional
variant: 'nonDirectional' | 'pointsDownOrRight' | 'pointsUpOrLeft'(HISlider.h variation bits 3 / none / 2). The default,nonDirectional, is the slider every earlier release drew. - New optional
tickMarks(directional variants only; the original draws none fornonDirectional),thickness(control rect cross size, default 16, or 32 with tick marks) andbackground(the colour the control rect is erased to, default white). - Pixels change: the output is now opaque (the control rect is erased to
background; it used to be transparent outside the art), the thumb is masked, disabled/inactive thumbs use their own cell, and the thumb is inset by the scheme's cornerc. Adapters do not passbackground, so a themed slider in a themed window body shows a white control rect. - Behaviour change (vertical sliders): value 0 is now at the bottom for every vertical slider, themed and Platinum fallback (
platinumSlider/platinumSliderWithLayout, which now also reportvalueDirection: -1). For the same pointer position,interactiveSlider({ orientation: 'vertical' })reportsonChangevalues that are 1 − the previous value; invert stored vertical values when upgrading. - Floppies now returns
nullfromcomposeSlider(the original hands it to the System slider), so callers draw the procedural Platinum slider. value0..1 maps onto native 0..100 (rounded), a documented modern policy row.- New loader option
engineCinfIds: loads that pass it gainmanifest.engineCinfs(rawcinfbytes by id).loadThemeand the publicloadKaleidoscopeSchemeexport pass the engine's ids by default (passengineCinfIds: []to opt out); the conversion module undertools/theme-loader/stays off by default, so lint and decoded fingerprints are unchanged. A theme loaded without engine cinfs hands every slider to the Platinum fallback. - With a declared
sliderLayoutEnvironment,composeSliderraisesUNSUPPORTED_CINF_ENVIRONMENTwhen the declared corner differs from the corner the resource chain resolves, or when the thumb's cross cell is not 16 px.
Runtime size (npm run loc:src -- HEAD --base v0.3.0)
| src/ lines (non-test .ts) | v0.3.0 | 0.4.0 | delta |
|---|---|---|---|
| logic (all src except engine tables) | 15399 | 16112 | +713 |
| engine tables (cited data) | 1258 | 2145 | +887 |
| total | 16657 | 18257 | +1600 |
By family: the window recipe removes 405 logic lines (the title fit adds 11 back) and composeChrome.ts drops from 924 to 255 lines. The slider (+648) and the progress bar (+456) grow because they add behaviour the legacy drawers never had (procedural bar, indeterminate mode, page parts, tick marks). The legacy slider and progress drawers are deleted.