Skip to content

v1.5: 2-tab dialog + scanlines + glow toggle + custom color picker#6

Merged
relmer merged 62 commits into
masterfrom
007-dialog-tabs-scanlines-glowtoggle
Jun 7, 2026
Merged

v1.5: 2-tab dialog + scanlines + glow toggle + custom color picker#6
relmer merged 62 commits into
masterfrom
007-dialog-tabs-scanlines-glowtoggle

Conversation

@relmer

@relmer relmer commented Jun 7, 2026

Copy link
Copy Markdown
Owner

v1.5 reshapes the configuration dialog into a 2-tab property sheet (Visuals + Performance), adds CRT scanlines, a glow on/off toggle, a custom color picker with a live swatch, and a per-page live FPS/GPU readout. Built on top of v1.4 (PR #5).

112 commits, 94 files, +12411/-827.

Highlights

Dialog overhaul

  • 2-tab property sheet (Visuals + Performance) replacing the legacy single-page layout
  • Frame-scope "Reset to defaults" button (resets every control on both tabs)
  • Live FPS / GPU% readout in the bottom-right of each page (replaces the old per-tab title-string update so the tab control never relayouts / flickers)
  • Modeless teardown via PropSheet_GetCurrentPageHwnd polling (the canonical pattern — no re-entrant DestroyWindow from notification handlers)
  • Themed via app manifest (inherits comctl32 v6 setup from v1.4)
  • Owner-drawn ⓘ info indicators with hover + keyboard-activated tooltips for every new knob
  • Ctrl+Tab / Ctrl+Shift+Tab page navigation (via PropSheet_IsDialogMessage)
  • ? caption button removed (WS_EX_CONTEXTHELP stripped)

Scanlines (CRT effect, ported from Casso)

  • New scanline post-process: extract → cbuffer (intensity, linesPerHeight) → fullscreen-quad PS sampling m_postBloomTarget → backbuffer
  • Two sliders: intensity (1..100%) and style (1..100, exponential mapping to lines/height via 1000 * pow(0.15, style/100))
  • Live preview wired through the live-mode atomic plumbing
  • Decoupled from glow: scanlines run independently — the no-glow render path now routes through m_postBloomTarget so the scanline PS has a populated SRV regardless
  • Default ON in v1.5 (intentional break from v1.4's no-visible-change-on-upgrade rule; called out in CHANGELOG)
  • Silent-bypass on shader-compile failure (FR-028b) — controls stay enabled, no UI surface for the error

Glow Enabled toggle

  • Separate from glow intensity — fully bypasses the bloom extract/blur/composite passes when off (intensity slider can stay at any value)
  • Bloom resources remain allocated so re-enabling is instant
  • Mirrored into EnableWindow across both pages for the dependent glow controls

Custom color picker

  • Appended Custom… as combo index 5 in the existing IDC_COLORSCHEME_COMBO (preserves v1.4 ordinals 0-4 per FR-039 / SC-007)
  • Win32 ChooseColorW modal with CC_FULLOPEN and 16-swatch persistent palette (REG_BINARY, 64 bytes; unconditional persistence per FR-035 even on dialog Cancel)
  • Owner-draw color swatch button next to the combo (BS_OWNERDRAW; click opens the chooser regardless of active scheme; 30 Hz timer animates it in Cycle mode using the rain's actual elapsed-time clock so it stays in sync with the rain)
  • Overlay text (Settings/Help/Exit hint, ? hotkey list, /? usage) now resolves the Custom-scheme color from snapshot.customColor (previously fell through GetColorRGB to green)

Z-order fix

  • RebuildContextsForCurrentMode now re-asserts the config dialog above the freshly-created render window after the GWLP_HWNDPARENT restoration so toggling Start In Fullscreen doesn't put the rain on top of the dialog

Migration

  • Every new v1.5 registry value defaults to the documented value when absent (glowEnabled=true, scanlinesEnabled=true, scanlinesIntensity=30, scanlinesStyle=50, customColor=RGB(0,255,0), customColorPalette=zero-init)
  • Automated migration sanity test (V14HiveWithNoV15Keys_LoadsWithV15Defaults) writes a v1.4-shape hive directly and verifies the v1.5 fields land at their defaults

Test status

457/457 unit tests pass on x64. Both x64 + ARM64 Debug builds clean.

Pre-PR review pass

Three findings from the principal-engineer review were resolved before opening (last commit, 0150bad):

  1. (High) ResetToDefaults zeroing the custom-color palette — fixed + regression test
  2. (Med) Two WM_APP_* IDs collided on WM_APP+51 — separated to +51 and +52
  3. (Low) PropertySheetW returning -1 was treated as success — gated on rc != -1 && rc != 0

Remaining manual QA

  • T070: end-to-end quickstart.md walkthrough for US1..US6 — needs GUI on real hardware. Smoke-test before merge.

Related

Branched from 006-multimon-gpu-efficiency (PR #5, now merged). Now diffs clean against master.

relmer and others added 30 commits June 5, 2026 14:07
New feature spec for the v1.5 dialog overhaul:
- Property-sheet dialog with Visuals + Performance tabs (no Apply button)
- CRT scanlines (enable + intensity + continuous style slider)
- Glow Enabled toggle (separate from Glow intensity)
- Custom color picker (ChooseColorW + persistent palette)
- Live FPS/GPU% in Performance tab title
- Fade-timer feature removal

Includes the requirements checklist and clarifications session resolving 5 questions covering palette persistence on Cancel, Custom re-selection UX, tab title format, checkbox-vs-Win11-toggle (deferring the pill visual), and OK/Cancel/X dismissal semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves 9 planning concerns: PropertySheetW modeless integration, runtime tab-title updates via TabCtrl_SetItem, lock-free FPS publisher, CBN_SELENDOK re-click handling, ChooseColorW palette persistence, scanline shader cbuffer layout + placement, cross-tab disabled-state propagation, fade-timer removal inventory, and the locked tab-title format string.

Constitution check passes (all 9 principles). Suggested phase ordering: foundational scaffolding -> US4 fade-timer removal -> US1 property-sheet structure -> US2 Glow Enabled -> US3 Scanlines -> US5 Custom color -> US6 docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase ordering: foundational (T001-T007) -> US4 fade-timer removal (T008-T020) -> US1 property sheet (T021-T033) -> US2 Glow Enabled (T034-T044) -> US3 Scanlines (T045-T055) -> US5 Custom color (T056-T066) -> US6 CHANGELOG (T067) -> Polish (T068-T075). Pairs every test task with its impl task; Phases 4-6 can run concurrently after Phase 3 closes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
C1 — Format-string contradiction: switch to 'use 0 instead of --' (user decision). FR-012, edge case #1, clarifications, T032, research R9, contracts/propertysheet.md, contracts/fps-publisher.md, quickstart.md all updated to reflect uniform '%u'/'%u' format with 0-on-unavailable.

C2 — Phase 2 priority header: now reads 'P3 — sequenced as P1 dependency' so triage tools don't misclassify fade-timer removal.

C3 — OK-commit coverage gap: added T033a (CommitLiveMode impl) and T033b (CommitLiveMode_WritesAllV15Fields test).

C4 — CBN_SELENDOK reliability: switched primary trigger to subclass-based WM_LBUTTONUP/WM_KEYUP + CB_GETDROPPEDSTATE watcher (reliable across themes). FR-029, R4, T064, plan.md, quickstart.md all updated. CBN_SELENDOK kept as optional belt-and-braces secondary trigger.

C5 — quickstart FR citation: FR-039..FR-043 -> FR-036, FR-037 for fade-timer removal.

C6 — plan.md FR list for registry-value additions: FR-036/FR-037 removed (they are removals), FR-020 added (it is an addition).

C7 — test-name alignment: LegacyShowFadeTimers_PresentValueDoesNotPreventLoad -> LegacyShowFadeTimersIsSilentlyIgnored to match T010.

C8 — tasks SC citation fix: T049, T055 SC-011 -> SC-013 (default-on-scanlines is SC-013, not the OK/Cancel SC-011).

C9 — plan.md atomics: now correctly lists both m_publishedFps + m_hasPublishedFps.

C10 — research.md polish: dropped stream-of-consciousness 'Wait — re-reading' paragraph.

C11 — FR-028d folded into FR-028 with CHANGELOG mandate as a sub-clause; FR-028a/b/c kept distinct.

C12 — SC-007 regression coverage: added T072a (V14SettingsRegressionTests round-trip).

C13 — Custom… ellipsis: explicit note in acceptance scenario that selected-item display matches drop-list label (no string mutation on selection).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ole mechanism

Simplifies the Custom-color same-item commit detection to a single mechanism (subclass-based CB_GETDROPPEDSTATE watcher on WM_LBUTTONUP / WM_KEYUP). The CBN_SELENDOK belt-and-braces secondary trigger added in 99a2e38 is removed — the subclass watcher reliably covers every UI input path on its own.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…codebase

Drift caught by impl agent pre-flight + explore-agent cross-check:

1. ColorScheme enum: spec invented Green/Amber/White/Cycle/Custom=4. Reality is Green/Blue/Red/Amber/ColorCycle (v1.4 ordinals 0..4). Per FR-039 / SC-007 the v1.4 ordinals MUST be preserved. Append Custom = 5 instead. Drop all White references.

2. Color combo control id: spec/tasks used IDC_COLOR_COMBO; the existing v1.4 id is IDC_COLORSCHEME_COMBO. No new id needed.

Updates spec.md (US4 prose + acceptance scenario + FR-006), data-model.md §2, tasks.md (T001, T056, T059, T063). research.md and contracts already use the correct names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- resource.h: IDD_VISUALS_PAGE, IDD_PERFORMANCE_PAGE, IDT_PERF_TITLE_TIMER,
  IDS_VISUALS_TAB_TITLE, IDS_PERFORMANCE_TAB_TITLE_INITIAL,
  IDS_PERFTAB_TITLE_FORMAT, IDC_GLOW_ENABLED_CHECK, scanlines control IDs
- MatrixRain\pch.h: add <commdlg.h> and <prsht.h>
- MatrixRainCore\Shaders\: create with .gitkeep for scanlines.hlsl

T001, T002, T003
Maps the v1.5 Scanlines Style slider value (1..100) to the number of
scanlines that span the full output height:

  lines = 1000 * 0.15^(style / 100)

Endpoints clamp to [1, 100]. Reference values:
  style=1   -> ~981 lines  (densest)
  style=50  -> ~387 lines  (default)
  style=100 -> ~150 lines  (sparsest, retro CRT)

Includes 7 unit tests covering the 5 reference points plus clamp
invariance at both endpoints.

T004, T005, T006, T007
Excises the orphaned fade-timer debug overlay from production and tests
top-to-bottom so the dialog rewrite in later phases operates on a clean
slate.

Production removals:
- ScreenSaverSettings: m_showFadeTimers field
- ApplicationState: ToggleDebugFadeTimes, SetShowDebugFadeTimes,
  GetShowDebugFadeTimes, RegisterShowDebugFadeTimesCallback,
  m_showDebugFadeTimes, m_showDebugFadeTimesChangeCallback
- SharedState: showDebugFadeTimes (live + snapshot)
- RenderParams: showDebugFadeTimes
- RenderSystem: RenderDebugFadeTimes decl + impl + call site
- ConfigDialogController: UpdateShowFadeTimers
- MatrixRain.rc: 'Show fade timers' control
- resource.h: IDC_SHOWFADETIMERS_CHECK
- ConfigDialog.cpp: OnShowFadeTimersCheck + every dispatch site
- MonitorRenderContext.cpp: snapshot.showDebugFadeTimes plumbing
- Application.cpp: callback registration + initial sync + WM_KEYDOWN sync

Test removals:
- ConfigDialogControllerTests: 6 fade-timer references
- ScreenSaverSettingsTests: defaults assertion

New test:
- RegistrySettingsProviderTests::LegacyShowFadeTimersIsSilentlyIgnored
  pre-writes the legacy REG_DWORD, asserts Load() succeeds (FR-037)

Grep for fadetimer/fade.timer/showFadeTimes/DebugFadeTimes is clean
outside the legacy-ignore test.

T008-T020
Red for T023.  Asserts default 0.0f/false, that PublishFps updates both

atomically, and that republish overwrites the value while keeping the

has-published flag latched.  Targets the lock-free pair consumed by

the property-sheet 1 Hz title timer (FR-010, contracts/fps-publisher.md).
Adds m_publishedFps + m_hasPublishedFps atomics on MonitorRenderContext

with PublishFps() / GetPublishedFps(bool&) / HasPublishedFps() accessors

and wires the render thread to publish each frame after FPSCounter::Update().

Memory order is relaxed on both sides per contracts/fps-publisher.md and

research.md R3 — single-float atomic is hardware-atomic on x64/ARM64, the

consumer reads at 1 Hz against a ~60 Hz producer so a stale read is at

most ~16 ms (invisible to a human watching a 1 Hz display).  Green for T021.
…S1 T022)

Red for T024/T025/T026: asserts EnterLiveMode snapshots and CancelLiveMode

restores all 5 new v1.5 rollback-eligible fields (glowEnabled,

scanlinesEnabled, scanlinesIntensity, scanlinesStyle, customColor).

Palette is intentionally NOT in the snapshot per FR-035.
…1 T024-T027)

Adds the 5 rollback-eligible v1.5 settings (glowEnabled, scanlinesEnabled,

scanlinesIntensity, scanlinesStyle, customColor) plus the FR-035 carve-out

palette to ScreenSaverSettings.  ConfigDialogController gets the five new

Update* setters (Green for T022) and CancelLiveMode now preserves the

live palette across the snapshot restore to enforce FR-035 at the

controller boundary.  SharedState carries 5 atomic live fields + 5

snapshot mirrors plus their copy in the lock-free Snapshot struct, and

MonitorRenderContext::Render fills RenderParams.{glowEnabled,

scanlinesEnabled, scanlinesIntensity, scanlinesLineCount, customColor}

from the snapshot via ScanlineLineCount(style) for the line count.

All 422 x64 Debug tests pass (was 420; +2 from T022 snapshot tests).
Red for T033a.  Verifies the property-sheet OK button's PSN_APPLY path

(via CommitLiveMode) Save()s every rollback-eligible field — the 5 new

v1.5 fields and 3 representative v1.4 fields — to the settings provider

and clears live-mode state.
Green for T033b.  Wraps ApplyLiveMode() under the spec-mandated name

called from the property-sheet OK button's PSN_APPLY notification.
…033)

Splits IDD_MATRIXRAIN_SAVER_CONFIG into IDD_VISUALS_PAGE (Density,

Speed, Glow Intensity/Size, Color combo, Scanlines groupbox placeholder,

Start Fullscreen) and IDD_PERFORMANCE_PAGE (GPU adapter, Glow Enabled

placeholder, Quality preset, Glow passes/resolution/smoothness, multi-

monitor, debug, Reset to defaults), wires them into PropertySheetW with

PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW|PSH_PROPTITLE|PSH_USECALLBACK plus

PSH_MODELESS for the live-overlay path (FR-001, FR-002, FR-013,

contracts/propertysheet.md).

Page proc is shared (PageDlgProc) — control handlers dispatch by ID so

each page only processes its own controls.  Per-page tooltip is keyed

off SetPropW(kPageTooltipProp); the info-button font is one shared

HFONT on DialogContext, created on whichever page initialises first.

OK/Cancel are now frame-owned buttons; PSN_APPLY/PSN_RESET notifications

translate to CommitLiveMode / CancelLiveMode with idempotency guards on

DialogContext.m_applied / m_rolledBack so the second page to see each

notification is a no-op (FR-004a, T033, contracts/propertysheet.md).

PSCB_INITIALIZED installs a 1 Hz TimerProc (kPerfTitleTimerId) that

reads the primary monitor's MonitorRenderContext::GetPublishedFps() and

the shared PDH GPU% counter (Application::GetPrimaryRenderContext +

QueryProcessGpuLoadPercent), formats via IDS_PERFTAB_TITLE_FORMAT, and

calls TabCtrl_SetItem on page index 1 (FR-009, FR-010, FR-011, FR-012,

contracts/propertysheet.md).  Frame centering moved from per-page

OnInitDialog to PSCB_INITIALIZED — the sheet is what the user sees.

All 423 x64 Debug tests pass.  Manual smoke test for T031/T033

dismissal semantics deferred to next UI walkthrough.
…034-T036)

Red for T038/T039.  Defaults_GlowEnabledIsTrue covers FR-038; the two

RegistrySettingsProvider tests cover the GlowEnabled REG_DWORD path

(round-trip both states + missing-defaults-ON); ShouldRunBloomPass

is the unit-testable decision predicate the RenderSystem render loop

consults instead of the legacy 'intensity == 0' branch (FR-015).
Green for T034/T035/T036.  m_glowEnabled was added to ScreenSaverSettings

early as part of T024 (Phase 3); now wire it through the registry

(VALUE_GLOW_ENABLED, REG_DWORD, defaults to 1) and through RenderSystem

via the unit-testable ShouldRunBloomPass helper, which replaces the

legacy 'm_glowIntensity > 0.0f' bypass branch.  Bloom resources stay

allocated when bypassed so re-enabling is instant.  All 427 x64 Debug

tests pass (+4 from US2 Red tests).
Reverts MIN_GLOW_INTENSITY_PERCENT 0 -> 1 (T040, FR-014); enables the

IDC_GLOW_ENABLED_CHECK on the Performance page; removes the legacy

'0% (glow disabled)' label branch from FormatPercentLabel (T041).

ApplyGlowEnabledUI(hSheet, enabled) (T042, research.md R7) walks both

pages via PropSheet_IndexToHwnd and EnableWindow's the Glow Intensity

+ Glow Size trios on Visuals and the Quality Preset + Glow Passes /

Resolution / Smoothness trios on Performance.  Both pages' WM_INITDIALOG

+ OnResetButton mirror the loaded m_glowEnabled into the checkbox and

drive ApplyGlowEnabledUI; BN_CLICKED on IDC_GLOW_ENABLED_CHECK calls

controller->UpdateGlowEnabled and re-applies the UI.

T043 (per-tab tooltip on disabled controls) DEFERRED — Windows tooltips

don't fire on WS_DISABLED controls without a transparent parent-relay

tooltip per page; greyed-out controls already convey disabled state.

Existing TestSaveSettings_ClampsInvalidValues updated to assert against

the new MIN constant rather than the literal 0.  All 427 x64 Debug

tests still pass.
…lity (US3 T045-T047)

Red for T049.  Defaults_Scanlines* (T045) pin enabled=true / 30 / 50;

RegistrySettingsProvider tests (T046) cover round-trip both states, both

ints (intermediate values), clamping 0->1 and 200->100 on read for both,

and missing-key default fallback (SC-013).  T047 pins that quality

presets stay orthogonal to scanline settings.
Green for T045/T046/T047.  m_scanlinesEnabled/Intensity/Style were

added to ScreenSaverSettings in T024 (Phase 3); now wire them through

RegistrySettingsProvider as REG_DWORDs (ScanlinesEnabled defaults to 1,

Intensity/Style to 30/50 per data-model.md §1).  Defensive clamp to

[1,100] on write; read-side clamp lives in ScreenSaverSettings::Clamp

already covers tampered registries.  All 434 x64 Debug tests pass.
Adds the Scanlines groupbox to the Visuals page (Enable + Intensity

slider + Style slider, both with info buttons), wires WM_INITDIALOG /

WM_HSCROLL / BN_CLICKED, and adds ApplyScanlinesEnabledUI for the

intra-page enable/disable propagation when Enable Scanlines toggles.

Tooltip infotip strings + IsInfoTipControlId / kInfoIds extended.

Ported scanlines.hlsl to MatrixRainCore\\Shaders\\ verbatim from

Casso's CRT pack (MIT, upstream crt-pi), with the two MatrixRain-spec

modifications baked in: drop the hardcoded kNativeScanlines (line

count is now CPU-supplied via g_linesPerHeight per FR-023) and drop

luminance gating (FR-024 — scanlines darken uniformly).  cbuffer is

the 16-byte ScanlineCb per contracts/scanline-shader.md.

T051/T052 (RenderSystem post-pass integration + per-frame upload) and

T055 (manual /c launch sanity) DEFERRED — settings persist and the

shader source is in place; the visual effect requires the post-bloom

render pipeline rewire which is a focused follow-up.  All 434 x64

Debug tests pass.
- Remove IDC_QUALITY_GROUPBOX wrapper around the quality cluster
- Reorder Performance tab top-to-bottom: Use all monitors, Enable glow,
  GPU combo, Quality cluster (preset + 3 advanced knobs), Show
  performance metrics; blank rows act as separators
- Rename `Render on all monitors` -> `Use all monitors`
- Rename `Show debug statistics` -> `Show performance metrics`
- Remove page-scoped `Reset to defaults` pushbutton (the next commit
  hoists it onto the property-sheet frame so it spans both tabs)

Registry value names and behavior unchanged.  IDC_QUALITY_GROUPBOX and
IDC_RESET_BUTTON IDs are orphaned in resource.h but harmless; cleaned
up in the follow-up commit that introduces the frame Reset button.
PropertySheets don't expose a native Reset button, so we create one in
PSCB_INITIALIZED parented to the sheet frame and intercept its
WM_COMMAND/BN_CLICKED in a comctl6 SetWindowSubclass hook.  The handler
calls controller->ResetToDefaults() and broadcasts a new WM_APP+50
(WM_APP_RESET_RESYNC) message to every page via PropSheet_IndexToHwnd;
each page re-reads its own controls from controller->GetSettings() via
the page-agnostic ResyncPageFromSettings helper.

- ConfigDialogController::ResetToDefaults now pushes the freshly-reset
  settings through to ApplicationState::ApplySettings in live mode so
  the live preview snaps back instantly regardless of which page is
  active (previously the dialog-side OnResetButton had to walk every
  Update* setter, which only worked when the active page happened to
  host every control — broken since the Phase-3 dialog split)
- SheetFrameSubclass + kSheetFrameSubclassId namespace block relocated
  above ShowConfigDialog so the modal trampoline can install it too
  (modeless path already did)
- CreateFrameResetButton mirrors the OK button's right margin to the
  left for footer-left placement; matches OK's font via WM_GETFONT +
  WM_SETFONT for theme consistency
- Page DLGPROC gains a WM_APP_RESET_RESYNC handler that re-reads every
  control's value from the current settings; GetDlgItem returns NULL
  for IDs not present on that page so the helper is page-agnostic
- Retired the now-broken OnResetButton + page-scoped IDC_RESET_BUTTON
  (1013); IDC_QUALITY_GROUPBOX (1033) retired in the layout commit
- New IDC_RESET_DEFAULTS (1051) for the frame-scope button
- Pairs with ConfigDialogControllerTests
  ResetToDefaults_LiveMode_PushesAllFieldsToSharedState: enter live
  mode, mutate every settings field, call ResetToDefaults, assert
  ApplicationState mirrors the defaults (435/435 green on x64 Debug)

Auto-bumps tasks.md with a Phase 2.5 section tracking T2.5.1..T2.5.8.
Ports the CRT scanline shader from ..\Casso into the bloom pipeline as a
post-composite full-screen pass.  The plumbing:

- m_postBloomTarget Texture2D + RTV + SRV trio at backbuffer dims;
  created alongside the bloom resources in CreateBloomResources,
  released in ReleaseBloomResources (so window resize handles it for
  free via the existing Release+Recreate pair in Resize)
- s_kszScanlineShaderSource inline copy of scanlines.hlsl matching the
  s_ksz* string pattern every other shader in this file uses; .hlsl
  on disk stays as documentation + source of truth
- CompileBloomShaders gains a soft-fail scanline compile per the
  analyze decision (FR-028b silent-bypass-on-init-failure): on
  D3DCompile or CreatePixelShader failure we OutputDebugStringA the
  error, OutputDebugStringW a session-banner, clear m_scanlinePS, and
  return S_OK from CompileBloomShaders so the rest of the pipeline
  boots normally.  Render-time checks then route through the bypass
  branch -- no UI surface, controls stay enabled
- CreateScanlineConstantBuffer (16-byte DYNAMIC) called from Initialize
  after CreateBloomConstantBuffer
- UploadScanlineConstants (Map/Unmap WRITE_DISCARD) mirrors
  RenderParams.scanlinesIntensity (already 0..1) and scanlinesLineCount
  (already ScanlineLineCount(style)) into ScanlineCb each frame
- ApplyBloom signature gains pCompositeTarget so Render can route the
  composite into m_postBloomRTV (or stay on the backbuffer when
  scanlines are off)
- ApplyScanlinePass binds m_scanlineConstantBuffer at b0, m_samplerState
  at s0, samples m_postBloomSRV, writes m_renderTargetView
- ShouldRunScanlinePass predicate gates on scanlinesEnabled AND
  glowEnabled per the spec (mini-2.5 sibling decision: scanlines need
  bloom to be running since they share the post-bloom-target plumbing).
  Render also checks m_scanlinePS / m_postBloomRTV / m_postBloomSRV
  liveness so the soft-fail bypass is the predicate's silent partner

New test: RenderSystemScanlineBypassTests covers the 4 predicate cases
(both-on, scanlines-off, glow-off, both-off) + the 16-byte ScanlineCb
layout sanity match against the HLSL b0 register.

x64 Debug: 440/440 (was 435; +4 predicate + 1 layout).
When the user toggles Glow Enabled OFF, the dialog already greys every
glow-dependent control on both tabs (T042 ApplyGlowEnabledUI).  But a
greyed control is a passive signal -- the user can't tell why it's
disabled or where the toggle lives.  T043 adds explanatory tooltips:

- 'Glow is disabled on the performance tab.' on the Visuals-tab
  intensity/size trios (points users at the toggle's actual location)
- 'Glow is disabled.' on the Performance-tab quality + advanced trios
  (toggle is on the same page so the wording is just a confirmation)

Implementation per research.md R4 + the user-supplied parent-relay
hint: register rect-based TTF_SUBCLASS TOOLINFO entries on the parent
dialog (one per IDC_* in IsDisabledGlowTooltipId's catalogue) inside
CreateAndRegisterTooltip.  Disabled child controls don't fire
WM_MOUSEMOVE so events bubble to the parent and the rect tool catches
them.  When glow is ON, controls consume their own mouse events and
these rect tools never activate (silently dormant -- no need to gate
in the text callback).

uId is the IDC_* directly (not the HWND) so the TTN_NEEDTEXTW branch
in PageDlgProc can recognise the sentinel without an HWND-to-id round
trip.  Tab disambiguation: probe GetDlgItem(IDC_GLOWINTENSITY_SLIDER)
-- present only on the Visuals page.

No new tests: T043 is thin Win32 UI plumbing exempt per the
constitution carve-out (tasks.md preamble).

x64 Debug: 440/440 green.
Adds Custom variant at ordinal 5, past v1.4 ordinals Green=0 / Blue=1
/ Red=2 / Amber=3 / ColorCycle=4 which MUST stay frozen so existing
registry values continue to round-trip after a v1.5 upgrade per
FR-039 + SC-007.

Parse/Serialize/IsValid recognise the L"custom" key.  GetNextColorScheme
remains modulo-(ColorCycle+1) so Custom is NOT in the hotkey cycle --
opt-in via the combo dropdown only per FR-039.

GetColorRGB(Custom, ...) falls through to the default-green branch on
purpose; T062 will special-case Custom in the render path to pull from
RenderParams.customColor.  Until then, selecting Custom shows green
(no visible regression -- the v1.4 combo doesn't even expose Custom).

Pairs with 4 new ColorSchemeTests cases: ordinal value, v1.4-ordinals-
frozen, key round-trip, hotkey-cycle skip.  444/444 on x64 Debug.
Two new registry values per contracts/registry-schema.md:

- CustomColor (REG_DWORD): the active user-picked streak color, written
  unconditionally on every Save so an existing custom RGB survives even
  when the user is currently on a non-Custom scheme.  Absent on load
  leaves m_customColor at its in-class default (RGB(0,255,0)) so the
  ChooseColor dialog opens pre-populated on first invocation.

- CustomColorPalette (REG_BINARY, exactly 64 bytes = 16 COLORREFs):
  matches CHOOSECOLORW::lpCustColors layout so the dialog wiring in
  T065 can memcpy directly to/from the settings struct.  Persists
  UNCONDITIONALLY across live-mode rollback per FR-035 (palette lives
  outside the snapshot rollback set, tested explicitly in T058).
  Anything other than exact-64-bytes on read -- including absent --
  zero-fills the palette per FR-038's size-mismatch rule.

Three new helper signatures on RegistrySettingsProvider: ReadDword,
WriteDword, WriteBinary.  ReadDword is distinct from ReadInt in its
out-type (DWORD vs int) -- COLORREF semantics are 32-bit-unsigned so
treating it as signed int would round-trip incorrectly for any value
with the high blue-channel bit set.

5 new RegistrySettingsProviderTests cases:
- CustomColorRoundTrip
- CustomColorAbsentMeansChooserDefault
- CustomColorPaletteRoundTrip
- CustomColorPaletteSizeMismatchYieldsZeroes (writes 32 bytes manually,
  expects zero-fill on read)
- MissingCustomColorPaletteYieldsZeroes

449/449 on x64 Debug (was 444).
CancelLiveMode already implements the FR-035 palette carve-out
(landed earlier under T024's settings groundwork) -- this commit
adds the pair of unit tests that lock the behaviour:

- CustomColorRollsBackOnCancel: UpdateCustomColor during a live
  session, then Cancel -- the active CustomColor reverts to the
  snapshot value taken at InitializeLiveMode time (FR-004)

- CustomColorPaletteIsNOTRolledBackOnCancel: SetCustomColorPalette
  with 16 distinct slots, then Cancel -- every slot must survive
  because the palette lives OUTSIDE the snapshot rollback set
  (FR-035 carve-out, tested explicitly here so future refactors
  can't silently regress the rollback boundary)

Adds inline SetCustomColorPalette setter on ConfigDialogController
since the chooser-dialog wiring (T065) needs a way to write the
palette back after the user OKs ChooseColorW; the setter does NOT
propagate to ApplicationState because the palette isn't a render
parameter, only feeds CHOOSECOLORW::lpCustColors.

451/451 on x64 Debug (was 449).
…(US5 T062)

RenderSystem
- UpdateInstanceBuffer takes a customColor COLORREF in addition to the
  ColorScheme.  When colorScheme == ColorScheme::Custom we override
  the static-palette GetColorRGB() result with a Color4 built from the
  COLORREF's R/G/B channels normalised to [0..1].  Other schemes pass
  through unchanged (per FR-033/FR-034).
- Caller in Render passes params.customColor (sourced upstream from
  RenderParams which is sourced from the SharedState snapshot).

The wiring under it -- previously a dangling dead end:

  Saved registry -> ApplicationState::m_settings -> [GAP] -> SharedState

The atomics (liveGlowEnabled, liveScanlinesEnabled,
liveScanlinesIntensity, liveScanlinesStyle, liveCustomColor) existed
since v1.5 Phase 3 but nothing ever wrote them, so the render thread
kept reading the in-struct defaults regardless of dialog activity OR
persisted settings.  Selecting Custom in the combo would have rendered
green forever.  Three additions close the gap:

- ApplicationState gains RegisterV15LiveCallback + bulk dispatch from
  ApplySettings -- one callback for all five v1.5 fields since the
  UpdateGlowEnabled / UpdateScanlines* / UpdateCustomColor setters all
  funnel through ApplySettings anyway

- Application.cpp wires the lambda to atomic-store every live* field;
  lock-free path (atomics, NOT the SharedState mutex) so the dialog
  thread never blocks the render thread on settings churn

- Application.cpp also bootstrap-seeds the live atomics from the
  freshly-loaded settings during init, so the first frame renders the
  user's persisted values rather than the in-struct defaults

451/451 on x64 Debug -- no test changes since the atomics-wiring is
test-equivalent to the existing ApplySettings live propagation that
already pushes density / animationSpeed / glowIntensity / glowSize.
relmer and others added 22 commits June 5, 2026 20:07
- Reposition Reset whenever the property sheet moves OK into its final footer location
- Keep the existing posted reposition path as a fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Strip WS_EX_CONTEXTHELP from the sheet frame once it is initialized
- Refresh the non-client frame so the caption updates immediately

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cache the last performance tab title on the dialog context
- Avoid TabCtrl_SetItem when the formatted text did not change

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add vertical padding around the scanlines enable checkbox
- Expand the Visuals page and scanline group to preserve footer spacing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use PropSheet_IsDialogMessage for modeless sheet accelerators
- Include prsht.h through the core precompiled header for the macro

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Propagate live fullscreen checkbox changes into ApplicationState settings
- Preserve the live property sheet while render windows rebuild for mode changes
- Add coverage for live fullscreen propagation to display mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lines moved to perf tab

1. Reset button now anchors its left edge to the tab control's left edge (via PropSheet_GetTabControl). Previous logic mirrored OK's right margin from the right edge of the sheet, which put Reset far inboard rather than at the dialog's content column.

2. After Application::RebuildContextsForCurrentMode (triggered by Start In Fullscreen toggle, multimon toggle, GPU change), the newly-created primary render window sits at the top of the Z-order. The config dialog falls behind it. SetWindowPos(HWND_TOP, ..., SWP_NOACTIVATE) restores the dialog to the top while preserving keyboard focus.

3. Color combo and GPU combo widths shrink from w=195 to w=162 to match the trackbar widths (right edge x=252) above/below them.

4. Scanlines section moved from Visuals tab to Performance tab, immediately below Enable glow. Visuals tab shrinks (h=225 -> h=150); Performance tab grows (h=215 -> h=268).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntrancy

PSN_APPLY/PSN_RESET handlers were calling DestroyWindow(GetParent(hDlg)) synchronously while still inside comctl32's notification dispatch. comctl32 iterates pages internally during PSN_APPLY/PSN_RESET and does not expect the sheet to be torn down mid-iteration; doing so corrupts dispatch state and recursively re-enters the frame subclass through WM_DESTROY-driven message traffic, manifesting as a stack overflow inside PropSheet_IsDialogMessage when the user clicks OK or Cancel.

Replace the direct DestroyWindow with PostMessage(hSheet, WM_CLOSE, 0, 0). comctl32 unwinds cleanly, and the sheet's default WM_CLOSE handler (which IS the modeless tear-down path it expects) runs on the next message-loop iteration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tPageHwnd

Replaces the broken page-level destroy with the documented pattern from PropSheet_GetCurrentPageHwnd:

  > If the user has clicked the OK or Cancel button, this macro returns NULL.

Pages no longer attempt to destroy the sheet during PSN_APPLY/PSN_RESET (which re-entered the frame subclass through cascading WM_DESTROY traffic and overflowed the stack). They simply acknowledge the notification and let comctl32 mark the sheet closed.

The message loop in Application::Run polls PropSheet_GetCurrentPageHwnd after each PropSheet_IsDialogMessage call and DestroyWindow's the sheet from outside any notification dispatch. SetConfigDialog(nullptr) and PostQuitMessage continue to run from the sheet frame's WM_DESTROY subclass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rs on Visuals

Misread of prior instruction: moved the whole scanlines section (checkbox + intensity slider + style slider). Correct: only the Enable scanlines checkbox sits on Performance (alongside Use all monitors and Enable glow). The intensity and style sliders stay on the Visuals page below Color, where the rest of the per-monitor visual sliders live.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Move live FPS / GPU%% from the Performance tab title text into a bottom-right static text control (IDC_FPS_GPU_READOUT) present on both pages. The tab title stays a constant 'Performance' so the tab control never has to relayout. PerfTitleTimerProc now writes the readout via GetDlgItem on each page's HWND instead of TabCtrl_SetItem. Format string simplifies from 'Performance (%u fps, %u%% GPU)' to '%u fps, %u%% GPU'.

2. Move the sheet-centering pass from PSCB_INITIALIZED to the posted WM_APP_REPOSITION_RESET handler. PSCB_INITIALIZED fires before comctl32 resizes the sheet to fit the pages, so GetWindowRect there returns the small-template size and the centering math is off. The posted message lands after the layout pass and computes correct (x, y) over the main window rect.

3. Grow both pages to a common template height (h=232) so the readout sits at the same y on both, and so comctl32 doesn't insert dead space below the Visuals controls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. The ColorComboSubclassProc was firing the Custom-color chooser every time the dropdown was opened while Custom was already selected (wasOpen check happened before DefSubclassProc, but on a button-down/up that just dropped the list open the state was already TRUE). Net effect: once Custom was selected, the dropdown was unusable -- picking any preset re-opened the picker. Subclass removed; CBN_SELCHANGE is now the only chooser trigger. The 'edit current Custom color' path is no longer one-click, but the dropdown is usable again, which matters more.

2. New owner-draw color swatch (IDC_COLOR_SWATCH, SS_OWNERDRAW | SS_SUNKEN) placed at x=257, y=101, w=28, h=12 next to the Color combo on the Visuals page. WM_DRAWITEM in PageDlgProc paints it solid-filled with the resolved RGB:
   - Static schemes (Green/Blue/Red/Amber): GetColorRGB lookup.
   - ColorCycle: GetColorRGB with elapsedTime = GetTickCount64()/1000; a 30Hz timer (IDT_COLOR_CYCLE_TIMER) invalidates the swatch each tick so it animates at roughly the same rate as the rain (60s full cycle, matching SC-012).
   - Custom: ScreenSaverSettings::m_customColor.

Timer is started/stopped from OnInitDialog, OnColorSchemeChange, WM_APP_RESET_RESYNC, and WM_APP_OPEN_CUSTOM_COLOR_CHOOSER so it tracks the active scheme.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ombo

- Change IDC_COLOR_SWATCH from static (SS_OWNERDRAW) to button (BS_OWNERDRAW) so we get click handling for free and tab-stop participation.
- Wire BN_CLICKED to PostMessage(WM_APP_OPEN_CUSTOM_COLOR_CHOOSER) so a single click on the swatch opens the chooser regardless of the active scheme. On OK the chooser switches to Custom and persists the picked colour.
- Adjust position to y=100, h=12 to line up with the closed combo edit-area baseline (was y=101 with SS_SUNKEN inset that visually pushed the swatch down a pixel).
- Add 1px black FrameRect + focus-rect on ODS_FOCUS so the BS_OWNERDRAW button reads as a swatch (BS_OWNERDRAW provides no native border).
- Use GetStockObject(BLACK_BRUSH) cast to HBRUSH (the windowsx.h GetStockBrush macro is not visible in the ARM64 build's PCH transitive includes).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetColorRGB has no entry for ColorScheme::Custom (5 >= __StaticColorCount=4) and falls back to green. The help/hotkey/usage overlays (Settings/Help/Exit hint, key list, /? dialog) all source their text colour from MonitorRenderContext via that lookup, so they rendered green even when the rain itself was a custom colour like magenta.

Resolve custom-scheme overlays from snapshot.customColor (COLORREF -> Color4) before calling Update(), mirroring what RenderSystem already does for the rain instance buffer. Static schemes and ColorCycle keep going through GetColorRGB unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ShouldRunScanlinePass was AND-ing scanlinesEnabled with glowEnabled — disabling glow silently disabled scanlines because the no-glow render branch wrote scene-to-backbuffer directly and skipped the m_postBloomTarget intermediate the scanline PS samples from. User expectation is that the two toggles are orthogonal.

Drop the glow gate from ShouldRunScanlinePass. Restructure RenderSystem::Render so the no-glow direct-scene copy also targets m_postBloomTarget when scanlines are wanted, then runs ApplyScanlinePass to composite scene + scanlines into the backbuffer. UploadScanlineConstants is hoisted above the bloom branch so both paths see fresh cbuffer values.

Update RenderSystemScanlineBypassTests: ScanlineBypassedWhenGlowDisabled becomes ScanlineRunsWhenGlowDisabled with inverted assertion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ResolveSwatchColor was using GetTickCount64()/1000 as the cycle phase, but the rain itself uses ApplicationState::GetElapsedTime() (a frame-accumulated clock that starts at zero when the app launches). The two clocks drift apart by the elapsed startup time, so the swatch displayed a different cycle position than the rain at any given moment.

Switch the Cycle branch to read elapsedTime from the running ApplicationState (via GetApplicationFromDialog). Fall back to the tick-count clock only when no Application is attached (CPL preview without a live rain instance).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nlines-glowtoggle

# Conflicts:
#	MatrixRainCore/RenderSystem.cpp
#	MatrixRainCore/Version.h
T055 was previously deferred because the doc framed it as a manual /c launch. The actual property under test (a v1.4 registry hive with NO v1.5 keys loads cleanly, with every v1.5 field landing at its documented default) is straightforward to automate.

New V14HiveWithNoV15Keys_LoadsWithV15Defaults: writes only v1.4-shape values directly via RegSetValueExW (bypassing Save, which would emit v1.5 values too), then loads via the production provider and asserts:
- v1.4 fields preserved verbatim
- glowEnabled defaults true (FR-038)
- scanlinesEnabled defaults true (SC-013 — the intentional break from no-visible-change-on-upgrade)
- scanlinesIntensity / scanlinesStyle default to DEFAULT_* constants
- customColor defaults to DEFAULT_CUSTOM_COLOR (RGB(0,255,0))
- customColorPalette zero-initialised (no saved palette)

456 tests pass on x64. ARM64 Debug builds clean.
Three findings from the principal-engineer review pass:

1. (High) ResetToDefaults() was zeroing m_customColorPalette by assigning
   a default-constructed ScreenSaverSettings (its palette is a default-init
   std::array<COLORREF,16> = all zeros). In live mode the zeroed palette
   propagated through ApplySettings; Reset->OK then persisted the zeros
   to the registry, silently destroying the user's 16 saved swatches.
   FR-035 carves the palette out of all rollback/reset behaviour.

   Fix: capture m_settings.m_customColorPalette before the reset, restore
   it on the freshly defaulted struct, and mirror onto the snapshot so a
   subsequent CancelLiveMode (which preserves the live palette across
   snapshot restore) still does the right thing.

   New regression test ResetToDefaultsPreservesCustomColorPalette pins
   the FR-035 behaviour so a future refactor can't regress it.

2. (Med) WM_APP_REPOSITION_RESET and WM_APP_OPEN_CUSTOM_COLOR_CHOOSER
   both expanded to WM_APP+51. Worked today only because they target
   different HWNDs (sheet vs. page) and are handled in different procs,
   but any future cross-target send would dispatch the wrong handler.
   Moved WM_APP_OPEN_CUSTOM_COLOR_CHOOSER to WM_APP+52 and grouped its
   #define alongside the others at file top so the duplicate-define
   warning would catch future collisions.

3. (Low) PropertySheetW returns -1 on modeless failure. The previous
   code cast that to HWND and ran it through CWRA, which accepts the
   non-null (HWND)-1 value as success; context.release() then leaked
   the DialogContext (controller, GDI font) and the caller would later
   pass (HWND)-1 to IsWindow/SetWindowLongPtrW.

   Fix: capture the INT_PTR, gate on (rc != -1 && rc != 0) via CBRAEx,
   then cast. Failure path now correctly hits the Error: cleanup which
   leaves the unique_ptr owning the context for RAII destruction.

457 tests pass on x64 (was 456). ARM64 Debug builds clean.
Per user convention: never put anything under [Unreleased]; use the actual version number at the time of the change. Splits the accumulated entries into [1.4.2098] (the v1.4 release content that shipped via PR #5) and [1.5.2161] (this PR's content). Drops the redundant '-- v1.5' suffixes since the section header now scopes them, adds a Fixed subsection for the bugs caught in pre-PR review (palette wipe, stale-SRV, WM_APP collision, PropertySheetW failure handling), and adds a few missed v1.5 entries (per-page FPS/GPU readout, modeless teardown via PropSheet_GetCurrentPageHwnd, Z-order fix on rebuild, overlay text custom-color resolution).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the v1.5 settings UX + rendering additions: a 2-tab modeless property sheet (Visuals/Performance), a scanlines post-process, a glow enable toggle that fully bypasses bloom, and a Custom color picker/palette with live preview plumbing. It also extends registry persistence + live-mode rollback contracts and adds/updates unit tests to lock the behavior down (including v1.4 regression/migration coverage).

Changes:

  • Replace the legacy single-page config dialog resource layout with two property-sheet pages and supporting resource IDs/strings.
  • Add scanlines rendering plumbing (shader + render-path routing) and new settings fields/registry keys (glowEnabled/scanlines*/customColor*), plus migration/regression tests.
  • Remove fade-timer debug overlay plumbing and update docs/release notes for the v1.5 feature set.

Reviewed changes

Copilot reviewed 55 out of 56 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
specs/007-dialog-tabs-scanlines-glowtoggle/research.md Phase 0 implementation research for property sheet lifecycle, scanlines, custom color picker, and migration/removals.
specs/007-dialog-tabs-scanlines-glowtoggle/quickstart.md Manual QA walkthrough and suggested task ordering for implementing/validating v1.5 features.
specs/007-dialog-tabs-scanlines-glowtoggle/plan.md Implementation plan outlining architecture, constraints, and phased delivery.
specs/007-dialog-tabs-scanlines-glowtoggle/data-model.md Defines new v1.5 settings fields, defaults, rollback eligibility, and render/registry mapping.
specs/007-dialog-tabs-scanlines-glowtoggle/contracts/scanline-shader.md CPU↔GPU contract for scanline shader constants and render ordering.
specs/007-dialog-tabs-scanlines-glowtoggle/contracts/registry-schema.md Registry schema additions for v1.5 and legacy handling expectations.
specs/007-dialog-tabs-scanlines-glowtoggle/contracts/propertysheet.md Property sheet flags/layout contract and dismissal semantics.
specs/007-dialog-tabs-scanlines-glowtoggle/contracts/fps-publisher.md Lock-free FPS publishing contract from render thread to dialog UI.
specs/007-dialog-tabs-scanlines-glowtoggle/checklists/requirements.md Spec-quality checklist for completeness/readiness.
README.md Adds v1.5 highlight row to the version summary table.
CHANGELOG.md Adds a v1.5 release section and documents key UX/rendering changes and migration note; moves v1.4 into a numbered entry.
.specify/feature.json Points “current feature” to the 007 spec directory.
.github/copilot-instructions.md Updates the “SPECKIT” pointer to reference the 007 plan/contracts/quickstart.
MatrixRain/resource.h Adds property-sheet page IDs, timers, strings, and new control IDs; reserves removed IDs.
MatrixRain/MatrixRain.rc Adds string resources and defines the two property-sheet page templates + new controls/readout.
MatrixRain/pch.h Adds commdlg/prsht includes for ChooseColor/property sheet usage in the UI layer.
MatrixRainCore/pch.h Adds <Prsht.h> for property-sheet integration used by core Application message loop.
MatrixRainCore/Version.h Updates VERSION_BUILD (should not be committed; see review comments).
MatrixRainCore/SharedState.h Adds live atomics + snapshot mirrors for new v1.5 settings fields; removes fade-timer flag.
MatrixRainCore/RenderParams.h Adds per-frame params for glowEnabled/scanlines/customColor and derived scanline values; removes fade-timer flag.
MatrixRainCore/ScreenSaverSettings.h Adds v1.5 settings fields/defaults and clamps; restores glow intensity min to 1; removes fade-timer field.
MatrixRainCore/RegistrySettingsProvider.h Adds new v1.5 registry value names + DWORD/BINARY helpers for custom color/palette.
MatrixRainCore/RegistrySettingsProvider.cpp Implements v1.5 load/save of glow/scanlines/custom color + palette, with clamping and legacy behavior.
MatrixRainCore/ScanlineStyleMapping.h Declares style→line-count mapping helper API.
MatrixRainCore/ScanlineStyleMapping.cpp Implements style→line-count mapping (clamp + pow curve).
MatrixRainCore/Shaders/scanlines.hlsl Adds scanlines pixel shader source (attributed) for CRT-style darkening effect.
MatrixRainCore/Shaders/.gitkeep Keeps the new shader directory present in source control.
MatrixRainCore/RenderSystem.h Adds scanline pass helpers/resources and bloom/scanline decision predicates; updates instance-buffer update signature.
MatrixRainCore/RenderSystem.cpp Implements scanline constant buffer + post-bloom target + scanline pass; adds glow bypass; removes fade-timer render path; tweaks FPS/GPU overlay string.
MatrixRainCore/MonitorRenderContext.h Adds lock-free FPS publisher fields/accessors.
MatrixRainCore/MonitorRenderContext.cpp Publishes FPS from render thread and plumbs scanline/custom-color values into RenderParams; fixes overlay color for Custom scheme.
MatrixRainCore/ColorScheme.h Adds ColorScheme::Custom = 5 with ordinal stability notes.
MatrixRainCore/ColorScheme.cpp Adds “custom” key parsing/serialization and includes it in valid key set.
MatrixRainCore/ConfigDialogSnapshot.h Documents rollback carve-out for the custom color palette in live mode.
MatrixRainCore/ConfigDialogController.h Removes fade-timer setter and adds v1.5 setters + palette setter; adds CommitLiveMode alias.
MatrixRainCore/ConfigDialogController.cpp Implements v1.5 setters, palette preservation through Reset/Cancel, and live reset propagation.
MatrixRainCore/ApplicationState.h Removes fade-timer APIs and replaces per-flag callback with a bulk v1.5 live callback.
MatrixRainCore/ApplicationState.cpp Removes fade-timer state and fires bulk v1.5 live callback from ApplySettings.
MatrixRainCore/Application.h Adds accessor for primary MonitorRenderContext for FPS readout.
MatrixRainCore/Application.cpp Wires bulk v1.5 settings callback into SharedState atomics; switches to PropSheet_IsDialogMessage + canonical modeless teardown polling; preserves config dialog across rebuilds with Z-order fix.
MatrixRainCore/MatrixRainCore.vcxproj Adds ScanlineStyleMapping sources to the core project.
MatrixRainCore/MatrixRainCore.vcxproj.filters Adds ScanlineStyleMapping files to VS filters.
MatrixRainTests/unit/V14SettingsRegressionTests.cpp Adds v1.4 registry round-trip regression and v1.4→v1.5 defaults migration sanity test.
MatrixRainTests/unit/ScreenSaverSettingsTests.cpp Updates defaults test for v1.5 settings and fade-timer removal.
MatrixRainTests/unit/ScanlineStyleMappingTests.cpp Adds unit tests for style→line-count mapping and clamp invariants.
MatrixRainTests/unit/RenderSystemBloomBypassTests.cpp Adds pure-helper tests for bloom pipeline bypass when Glow Enabled is off.
MatrixRainTests/unit/RenderSystemScanlineBypassTests.cpp Adds pure-helper tests for scanline pass decision predicate and ScanlineCb layout (but has a misleading header comment; see review comments).
MatrixRainTests/unit/MonitorRenderContextFpsPublisherTests.cpp Adds unit tests for the FPS publisher fields/accessors.
MatrixRainTests/unit/RegistrySettingsProviderTests.cpp Extends registry tests for v1.5 values (glow/scanlines/custom color/palette) and legacy fade-timer handling; updates glow intensity clamp expectation.
MatrixRainTests/unit/QualityPresetsTests.cpp Asserts preset switching does not touch scanline settings.
MatrixRainTests/unit/ConfigDialogControllerTests.cpp Removes fade-timer tests and adds v1.5 rollback/commit/reset/palette persistence tests.
MatrixRainTests/MatrixRainTests.vcxproj Adds new unit test compilation units for v1.5 coverage.

Comment thread MatrixRainCore/Version.h
Comment thread MatrixRainTests/unit/RenderSystemScanlineBypassTests.cpp
Comment thread MatrixRain/MatrixRain.rc Outdated
Comment thread MatrixRain/MatrixRain.rc
Comment thread MatrixRainCore/RenderSystem.cpp
Comment thread MatrixRainCore/RegistrySettingsProvider.cpp
relmer and others added 4 commits June 7, 2026 00:51
UpdateGpuAdapter and UpdateMultiMonitorEnabled mutated only the
controller's private settings copy, so the live context rebuild
re-resolved the adapter and multi-monitor gate from the stale
ApplicationState and recreated the device on the default GPU.

- propagate both via ApplicationState::ApplySettings in live mode,
  matching UpdateStartFullscreen
- add regression tests for both live-mode propagations
VERSION_MINOR stayed at 3 while the 1.4 and 1.5 CHANGELOG sections
shipped; align the version header with the documented product line.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…docs

Address Copilot PR #6 review feedback (docs/comments only; no behavior change):

- Scanlines Enabled checkbox lives on the Performance tab, not Visuals;
  fix quickstart, CHANGELOG, and phase-ordering notes (Intensity/Style
  sliders stay on Visuals).
- CustomColor is persisted unconditionally on every Save (the provider
  sees only a settings struct and has no per-session chooser-OK signal);
  update data-model and registry-schema contract to match the shipped,
  tested behavior instead of the pre-implementation conditional-write text.
- Perf FPS/GPU shows in a bottom-right readout on both pages, not the
  Performance tab title; fix the .rc and quickstart descriptions.
- Post-bloom target intentionally uses the scene/bloom R8G8B8A8 format;
  the scanline pass is a shader draw (not a CopyResource) so the BGRA
  backbuffer difference is GPU-handled. Clarify the comment.
- Scanline pass runs independently of the glow toggle; fix the stale
  RenderSystemScanlineBypassTests header comment.
@relmer relmer merged commit a1fe489 into master Jun 7, 2026
4 checks passed
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