fix: «Как у пространства» забывает свой цвет комнаты (#581)

Режим заливки комнаты и её цвет — два поля конфига под одним переключателем.
«Как у пространства» снимало только режим; цвет оставался и продолжал
применяться, потому что `roomCustomFillOf` отдавал цвет комнаты независимо от
того, чей режим `custom` действует. Так возникало безымянное состояние «режим
наследую, цвет свой» — Cabinet на даче.

Теперь цвет комнаты участвует в раскраске только вместе с её собственным
`fill_mode: 'custom'` (одна функция — все поверхности: карточка, space-card,
PDF, черновик диалога). Диалог загружает цвет в черновик только при своём
режиме, обнуляет его при уходе с «Свой цвет» и показывает строку цвета только
под этим радио; сохранение пишет `custom_fill` только с `fill_mode: 'custom'`,
иначе удаляет — включая сироту от прежнего редактора. Чтение конфиг не
переписывает: застрявшие комнаты выздоравливают обновлением.

- `test/logic.test.mjs`: AC1 — сирота и любой чужой режим → цвет пространства
- `demo/smoke_room_settings.mjs` шаг 7: свой цвет → «Как у пространства» →
  ни режима, ни цвета, во View цвет пространства; сирота открывается как
  наследование, сохранение её удаляет (проверено красным на базе: 9 фактов)
- `demo/smoke_space_settings.mjs`: override с собственным режимом + сирота
- `demo/golden/harness.mjs`: `roomCustomFill` ставит комнате её режим —
  кадры `lighting-custom-glow-*` не меняются
- мутант `room-orphan-colour-wins-again`
- docs: ARCHITECTURE (#56), CONFIG-COMPATIBILITY, USER-GUIDE ru/en, TESTING;
  отпечаток скриншотов принят попиксельно (11 кадров)

Issue: #581
User-Visible: yes
This commit is contained in:
Claude
2026-09-15 09:48:37 +03:00
parent bae6afaae7
commit 2bec03e6fa
58 changed files with 616 additions and 447 deletions
+9 -1
View File
@@ -268,8 +268,16 @@ export const LOW_HEADROOM_WARNING_BYTES = 15_000;
* общего бюджета. Выбор внутренней или внешней грани окна относится к
* синхронной геометрии лучей в initial View. Измеренный факт 290 629 Б
* оставляет 671 Б сверху и 1 329 Б до нижней границы полосы.
*
* 2026-09-15, #581: потолок перецентрирован 291 300 → 291 700 без изменения
* общего бюджета. Цвет комнаты участвует в раскраске только вместе с её
* собственным режимом «Свой цвет» — одна проверка режима в `roomCustomFillOf`
* и та же проверка на черновике диалога (+16 Б gzip). Факт на `dev` после #580
* был 290 789 Б — 511 Б сверху, у самого края шумовой полосы; измеренный факт
* 290 805 Б под новым центром оставляет 895 Б сверху и 1 105 Б до нижней
* границы полосы.
*/
export const INITIAL_VIEW_GZIP_CEILING = 291_300;
export const INITIAL_VIEW_GZIP_CEILING = 291_700;
export const INITIAL_VIEW_CEILING_BAND = 2_000;
/**
+13
View File
@@ -9863,6 +9863,19 @@ const MUTANT_DEFINITIONS = [
replace: ' "zones_v1": ("zones", ()),',
}],
},
{
id: 'room-orphan-colour-wins-again',
guard: 'node --test test/logic.test.mjs',
because: '#581: a room colour stored without the room\'s own `custom` mode is an orphan — the '
+ 'state "as the space" used to leave behind (Cabinet on the dacha). Without the mode gate the '
+ 'orphan paints again on every surface, and the radio "as the space" lies once more',
patches: [{
file: 'src/logic.ts',
find: " if (settings?.fill_mode !== 'custom') return spaceFill;",
// runtime-false, not statically dead (#568): tsc must still compile the mutant
replace: " if (settings?.fill_mode === 'never-a-fill-mode') return spaceFill;",
}],
},
];
const mutationCardSource = readFileSync(join(repoRoot, 'src/houseplan-card.ts'), 'utf8');
+7
View File
@@ -27,6 +27,13 @@
/** @type {SmokeLink[]} */
export const SMOKE_LINKS = [
{
symbols: ['roomCustomFillOf'],
smokes: ['smoke_space_settings.mjs', 'smoke_room_settings.mjs'],
because: '#581: the room-colour projection (own custom mode carries the colour, an orphan paints '
+ 'the space) is observed through `.room` style variables in View and through the room-dialog '
+ 'draft; neither smoke calls the pure function by name',
},
{
symbols: [
'commitHouseplanViewport', 'isIdentityLiveLayerProjection', 'paintLiveViewport',