Skip to content

Scriptoscope 0.4.0

Choose a tag to compare

@khawkins98 khawkins98 released this 23 Oct 12:27
· 252 commits to main since this release
f3761d9

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. Most wnd# 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, thickness and background. Vertical sliders now put 0 at the bottom.
  • Progress bar on the v2 engine (CDEF 5; #440, #446). It adds integer range and indeterminate/phase (DECISIONS #10, #11). The 8 schemes without progress art now draw the original's own procedural bar, and composeProgress never returns null.
  • Loader: the public loadKaleidoscopeScheme (npm export and the Pages theme-loader/ module) passes the engine's cinf and 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:ready now rejects silent reverts of base commits, and CI builds the demo (#447).
  • Known gaps:
    • collapsed no-title utility windows on the five built-in-frame schemes render expanded (#423);
    • the title shadow (TX7) is not yet drawn;
    • with no condensed face, long titles truncate sooner than in the original (DECISIONS #13).

Changes

  • v2 engine: wnd# windows now lay out and hit-test through the decoded 2.3.1 window recipe (engine family window, docs/spec/engine/window-widgets.md). A new driver (src/engine/families/window.ts, tables in src/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.ts is 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 with W = SW + margins and 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 in tests/engine-diff/expected/window.json with its spec row). Each side now ends at its placed end P[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 exactly SW + margins wide 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.

  • composeWindowChrome options: titleWidthPx is now SW, the bare title width (renderWindow no longer adds padding). New optional titleWidthCondensedPx (TT2; default the TT2a declared value SW − titleLength), titleLength (default 1 when SW > 0), active (drawActive, default true; renderWindow leaves it set), widgetCicn (the WD11 pressed strip, cicn −14330) and pressedWidget.

  • ComposedChrome gains widgets (the reachable widget hit areas), hitWidget(x, y) (the HT rows, mask included) and layout (the engine layout). shadeCleared is removed. titleRegion is now the code-5 plate, not the run of 5/6 cells. New titleRect: the remapped title rect t4p (TX3).

  • resolveTitleWidgetRects(wt, composed) returns composed.widgets for wnd# windows; it no longer remaps from placement. 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-window titled 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.mjs accepts window-widgets.md row 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 composeWindowChrome now defaults SWc to SW (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 to titleWidthPx (SW). It used to default to the TT2a value SW − 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 + margins wide, 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 in src/engine/tables/progress.ts) follows docs/spec/engine/progress.md. It forces the control rect to 14 rows (RECT1). The path is chosen by the cinf gate: inactive (hilite 254 or 255) needs cinf −10077, and without it the whole bar draws with the active ids (GATE1/GATE1a). No cinf −10080 or 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 and tile ≠ 0 bands tiled. The inner rect is inset by k when k > 2, else 2, and by s (INSET1/INSET2). The determinate split is x = 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 own cicn/cinf pair: 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 from cicn −10224, a FrameRect, then dark grey and lavender PaintRects or ppat −10064 (FB1–FB10). The removed behaviours are the per-part inactive→active fallback, disabled drawing as active, null returns, 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-panel ppat −10064 is not shipped, DECISIONS #5). The legacy progress drawing code is removed. The engine diff gate gains a progress family (1,458 cells), and reviewed progress goldens are re-recorded.

Migration notes: progress bar

  • composeProgress now always returns a PixelBuffer (it never returns null): 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 throw RangeError. The normalized value stays (clamped to 0..1, mapped to trunc(v × 10000) of 0..10000). Passing both value and range throws TypeError.

  • New optional indeterminate and phase (integer 0..255, DECISIONS #11). New composeProgressFrame(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). New ProgressIdle helper (interactive layer) keeps the phase and advances it once 3 ticks have passed (IDLE2). Call idle() from a timer and redraw with compose(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 without cinf −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) and progressSummary are deprecated. They no longer describe how the bar is drawn. Removal is tracked in #436.

  • Loads gain engine cinf bytes for the progress ids (−10080..−10075) when engine cinf ids are on (the default for loadTheme and the public loadKaleidoscopeScheme).

  • The public loadKaleidoscopeScheme (the scriptoscope.js export and the Pages theme-loader/ module) now also passes the engine's icon-blit cicn ids by default, the same as loadTheme. 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 traced unimplemented. No pixel changes on the 18 bundled themes (#434 review). Pass iconBlitCicnIds: [] to opt out.

  • Pages: the published standalone decoder is the public wrapper (fixes #431). https://khawkins98.github.io/scriptoscope/theme-loader/loadKaleidoscopeScheme.js used to be a verbatim copy of the conversion core, which leaves engineCinfIds off, so a consumer importing it got no engine cinfs and every slider would hand off to the Platinum fallback once 0.4.0 ships themed sliders. npm run build:demo now builds src/loadKaleidoscopeScheme.ts with the core into one ES module (vite.loader.config.js) and publishes it under the same URL, so existing imports, and theme-loader/index.js, which re-exports it, get the engine cinf ids by default. Pass engineCinfIds: [] for the old behaviour. The .sit path still lazily imports the published sit-wasm/index.mjs. The rest of theme-loader/ is copied unchanged, and the npm package and library build are unchanged.

  • Demo: dropped archives draw engine sliders. demo/_theme-drop.mjs (used by declarative-hostile-css.html) and demo/diagnostic.html imported the conversion core tools/theme-loader/loadKaleidoscopeScheme.js, which leaves engineCinfIds off. A dropped .sit/.rsrc therefore had no engine cinfs and every slider fell back to Platinum. Both pages now import the public wrapper src/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 in src/engine/tables/slider.ts) follows docs/spec/engine/slider.md: the cinf-only gate (G1–G3), the per-control record (ID1–ID9), the track ladder gb + tier → gb + 1 → base + tier → base + 1 with a cinf required 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 slider cinf) 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 at R.left + c + trunc((alongR − tA − 2c) · offset / range) with the vertical minimum at the bottom (S5); and a one-column middle (cinf byte 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 short cinf are 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 a slider family (2,418 cells); reviewed slider goldens are re-recorded.

Migration notes (0.3.0 → 0.4.0): slider

  • composeSlider gains optional pressedPart: 'thumb' | 'pageUp' | 'pageDown' (DECISIONS #9; native 129 / 22 / 23). state: 'pressed' with no part means thumb; a page press draws the pressed track with the normal thumb. interactiveSlider sets 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 for nonDirectional), thickness (control rect cross size, default 16, or 32 with tick marks) and background (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 corner c. Adapters do not pass background, 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 report valueDirection: -1). For the same pointer position, interactiveSlider({ orientation: 'vertical' }) reports onChange values that are 1 − the previous value; invert stored vertical values when upgrading.
  • Floppies now returns null from composeSlider (the original hands it to the System slider), so callers draw the procedural Platinum slider.
  • value 0..1 maps onto native 0..100 (rounded), a documented modern policy row.
  • New loader option engineCinfIds: loads that pass it gain manifest.engineCinfs (raw cinf bytes by id). loadTheme and the public loadKaleidoscopeScheme export pass the engine's ids by default (pass engineCinfIds: [] to opt out); the conversion module under tools/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, composeSlider raises UNSUPPORTED_CINF_ENVIRONMENT when 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.