Перейти к содержанию

«Жизнь канала»: контракт для визуальной разработки

24 октября 2026. Механика и данные развиваются в backend; иллюстрации, анимация и компоненты сайта/OBS могут перерабатываться отдельно. Не менять исходные ID событий, сохранённые результаты и протокол голосования ради оформления. Ориентир продукта — документ по игре.

Совместимые дополнения JSON

Все перечисленные поля необязательны для старых историй. Нельзя рисовать текущую семью, место или профессию вместо отсутствующего исторического снимка.

  • people[]: прежние key, name, role, age, bond, alive и новые education, occupation, interest, goal, place, path, study_started_age (возраст самого близкого на момент поступления). Это игровые факты о близком, не данные реального зрителя. path: general, technical, academic, creative. У умершего состояние заморожено.
  • residence: {country, place, since_age} — нынешнее место жительства героя. character.country и character.place остаются местом рождения. При отсутствии residence использовать их как прежний fallback.
  • career: {track, level, experience, studying, study_years_left}. track: general, technical, academic, creative, business; уровень 0–3 в прежней модели и 0–5 после подключения экономики 3.10. Ноль лет при studying: true означает подготовку к экзамену, а не автоматический выпуск.
  • keepsakes[]: {key, name, description, acquired_age, origin_event_id, event_ids} — памятные вещи и ссылки на настоящие записи летописи. Они не заменяют items[]: не каждое воспоминание является покупкой и не каждый реквизит означает владение предметом.
  • decision.scene, events[].scene, memories[].scene: семантическое описание иллюстрации {key, variant, cast, props, mood, outcome?}. variant — ID сцены или конкретного перехода. cast содержит hero и ключи реально участвующих близких; в самостоятельной сцене близкого героя может не быть. props — смысловые ключи реквизита, не пути к файлам. mood — neutral, warm, quiet, somber. outcome у выбора — pending, у результата — success либо setback (неудача случайной попытки); отсутствие поля у остальных событий допустимо. success означает выполненный ответ, а не моральную оценку выбора.
  • memories[] дополнительно содержат свои residence, career, keepsakes: это независимые снимки на дату события. people, character, items в снимке тоже исторические.

Словарь scene.key: birth, school, study, work, workshop, concert, home, celebration, travel, farewell. Неизвестный ключ должен получать нейтральную сцену; отсутствие scene у старой записи — текстовый fallback. Декорации страны брать из memory.residence.country, а при отсутствии — из memory.character.country.

У ребёнка path появляется после озвученного плана в 16 лет, а не при рождении. Интерес не является обещанием будущего успеха. Самостоятельные события близких тоже могут иметь outcome: setback и спокойное настроение вместо победной анимации.

Начальные ключи памятных вещей: instrument, first_recording, radio_voice, child_note, friend_sketch, family_photo, diploma. event_ids сохраняет исходное событие и до 31 последних связанных записей. Для старой вещи без прежних метаданных началом считается первое сохранённое появление в новом формате: не выдумывать раннюю дату или картинку.

Примеры собственных вымышленных персонажей: life-scene-fixtures.json — школа, работа, концерт и семейная встреча. Они получены из TestSceneExamplesForVisualDevelopment, не из production. Обновление: запустить этот тест с -v, взять JSON после SCENE_EXAMPLES=; приватный State в файл не записывать.

Ни asset URL, ни SVG/Canvas/CSS, ни длительность анимации в backend-контракт не входят. Визуальный агент выбирает собственную реализацию: пиксельные сцены, анимированные спрайты, переходы, комнату с вещами. Выбор изображения не должен требовать разбора русскоязычного текста события.

Границы и совместная работа

Backend: internal/chatgames/life/ и этот контракт. Визуальная поверхность: ../front-pulse-caster/tg-mini-app/src/components/Life*, src/lifePresentation.js, src/server/assets/life/overlay.html. Автор механики в текущей итерации не редактирует эти визуальные файлы.

Вещь в комнате можно связать с origin_event_id и event_ids: нажатие открывает настоящие воспоминания. Ведущий по-прежнему сам отмечает важные события для альбома; наличие scene не добавляет запись в избранное автоматически.

Приватные seed, flags, seen, followups, person_progress не выдаются зрителю. Не рассчитывать по ним картинки и не просить добавлять их в публичный API. Для будущих исходов нет публичных подсказок: показывается только уже происходящий выбор или свершившееся событие.

Дополнение: контекст выбора и рабочие обстоятельства

  • circumstances в снимке жизни и memories[].circumstances: {employment, since_age, search_years, load, recovery_years_left, disruptions}. Занятость: not_started, employed, searching, part_time, break, retired. Нагрузка 0–100 — игровое состояние, не диагноз. disruptions — уже пережитые эпизоды. Поле необязательно для старой жизни; не придумывать её прошлую работу по текущей странице.
  • decision.options[].reason: причина недоступности; chance — необязательное число процентов; chance_basis — человекочитаемое объяснение. available остаётся основным флагом. Показанный шанс сохраняется до конца опроса, не пересчитывать его на frontend. Причина и шанс также включены в hint для старого интерфейса: при новом оформлении можно использовать отдельные поля, избегая дублирования.
  • Новые scene.variant: job_loss, job_found, work_schedule_return, load_warning, job_search, overload, recovery_review, work_friend_return, work_boundaries, partner_load. Сохраняется прежний словарь scene.key: рабочие ситуации — work, разговор о ритме — home. Неудачный отбор имеет outcome: setback.

Графика и анимация по-прежнему принадлежат визуальной итерации. Пользователь выбирает важные кадры для альбома сам.

Дополнение 3.1: оперативное управление временем

  • settings.speed: целое 1 | 2 | 4 | 8 | 16, по умолчанию 1. Отсутствие у старого сохранения означает 1; API нормализует его при выдаче. settings.year_seconds остаётся базовой длительностью, закреплённой при рождении; реальный обычный год длится year_seconds / speed секунд, включая дробную часть. Смена скорости сохраняет пройденную долю года, в том числе на паузе. Длительность голосования от скорости не зависит. Первое раскрытие рождения занимает минимум 12 секунд.
  • settings.auto_decide: boolean, по умолчанию false (в том числе у старых жизней). Новые развилки сразу получают воспроизводимый автовыбор: decision не появляется, результат сохраняется в events[] с kind: "result", outcome: "Авто". scene.outcome по-прежнему описывает успех/неудачу попытки и не меняет значения.
  • При включении авторежима в открытом голосовании уже принятые голоса, усиления и голос ведущего учитываются; оно закрывается с outcome: "Авто · итог начатого голосования" либо "Авто · учтён голос ведущего". Без голосов — "Авто". На паузе переход выполняется при продолжении жизни. В авторежиме бесплатные голоса не принимаются, награда Twitch выключается штатной синхронизацией, запоздавшие усиления возвращаются.
  • remaining_milliseconds: необязательное целое число миллисекунд оставшегося времени на паузе. Для дробной длительности года использовать его, если больше нуля; иначе старое remaining_seconds * 1000. remaining_seconds сохраняется для совместимости, округляется вверх. Эти поля имеют смысл на паузе; для идущего года использовать next_tick_at, для выбора — decision.ends_at.

Ведущий меняет speed и auto_decide через существующий PUT /api/v1/channels/{id}/games/life/settings вместе с остальными настройками. Это полная форма настроек, не PATCH. Старый клиент без новых полей сохраняет 1× и выключенный авторежим. Ответ пульта содержит текущие настройки канала, а session.settings.year_seconds — базу выбранной жизни. Переключение не переписывает завершённые истории.

POST /api/v1/channels/{id}/games/life/skip_to_decision, тело {"session_id":"UUID"}, использует те же права интерактивных игр, что пауза. Возвращает {"status":"success"}, после успеха перечитать снимок пульта обычным GET. Допустимо только для идущей жизни без открытого выбора, на активном эфире либо в тестовом запуске; иначе 409. Проживает до 10 лет с обычными годовыми расчётами, всеми записями летописи, снимками и последствиями. Останавливается на следующей развилке, завершении либо лимите; в авторежиме — после первого автоматически принятого решения. Если достигнут лимит без развилки, кнопку можно нажать снова. Никакой будущий исход или очередь в JSON не добавляются.

Фронт: кнопки скорости, авторежима и «До следующего решения» только в пульте; зрителю и OBS — текущая скорость/режим. Считать полосу текущего года по дробной длительности, исключая начальные 12 секунд раскрытия. На паузе использовать миллисекунды. У отключённой награды в авторежиме объяснять причину; настройка points_enabled остаётся сохранённым предпочтением для ручного режима. Иллюстрации и HTML OBS этим изменением не затронуты.

В life-scene-fixtures.json раздел live_controls содержит публичные снимки running_16x, paused_fractional_year и automatic_result. Они сгенерированы TestLiveControlExamplesForVisualDevelopment (лог LIVE_CONTROL_EXAMPLES=), без production-данных и приватного состояния. В примере паузы 313 мс округлены вверх из 312,5 мс.

Дополнение 3.10: деньги, имущество и своё дело

Все суммы — условные игровые единицы. Поля добавлены в JSON версии 1; отсутствие означает «нет данных», а не нулевое имущество в прошлом.

Поле Тип и смысл
career.title string, нынешнее название ступени; для новых правил уровни 0–5.
career.salary необязательное integer, фактически рассчитанный заработок от работы за последний игровой год, до бытовых расходов; 0 при поиске, перерыве, пенсии или основном собственном деле. Подработка при учёбе включена. Отсутствие — год ещё не рассчитан по новым правилам. Это не обещание будущей зарплаты.
career.promotions integer, число реальных повышений после подключения модели; прошлые повышения не восстанавливаются из уровня. Смена трека сохраняет счётчик, но сбрасывает ступень.
finance.inflation необязательное number процентов: используемый игровой годовой ориентир страны проживания. Среднее WDI за 2019–2023, ограниченное диапазоном −2…20%. Это не фактическая инфляция будущего календарного года.
finance.inflation_basis {country, from_year, to_year, historical, assumed, source, url}: код страны, период наблюдений, последнее историческое число, ориентир модели и первичный источник.
finance.price_index number: накопленный индекс с начала новой модели, начальное значение 1. Зарплаты, обычные расходы, подарки, цены магазина и ответы с оплатой индексируются; уже взятые долги остаются номинальными.
finance.real_cash / real_base_age integer / integer: свободные деньги в ценах возраста отсчёта. Для новой жизни возраст 0 («в ценах года рождения»); у старой — возраст явного подключения модели. Не подписывать старой жизни «в ценах рождения».
finance.debt_payment integer, погашение основного долга за год. Проценты и ипотечный основной платёж входят в expenses. income также включает разовые поступления от продажи имущества/вывода вложений, income_source объясняет их; это денежные поступления, не только прибыль.
finance.market необязательный {observed_year, stocks_percent, bonds_percent, source, url}. Уже применённый исторический сценарий рынка США, NYU Stern, 2019–2023. Появляется после годового расчёта существующей позиции. Не называть его доходностью рынка страны героя или прогнозом.
assets необязательный {properties: [], investments: [], net_worth: integer}. Состояние = cash + стоимость действующего имущества, вложений и бизнеса − весь debt; ипотеку повторно не вычитать. Отрицательное состояние допустимо.
stats.net_worth, years[].stats.net_worth необязательное integer. Уже известная оценка состояния и исторические годовые точки для графика; пропущенные годы не заполнять нулями.
business необязательный {name, kind, since_age, closed_age?, value, yearly_profit, employees, status}. kind: services, workshop, studio, consulting; status: growing, stable, struggling, closed. yearly_profit — поступившая в семейный бюджет прибыль либо убыток; в растущем деле 30% положительного результата остаётся в оценке дела, 70% выводится. closed_age появляется после закрытия/продажи, value тогда 0.

assets.properties[]: {key, kind, country?, bought_age?, recognized_age, price?, value, rented, rent_income, rented_years, mortgage_left, mortgage_payment, mortgage_years, mortgage_rate, status, legacy?}.

  • kind: home или rental; status: owned или sold. key — устойчивый ID для выбора объекта; не использовать порядковый номер карточки. country — страна объекта, не автоматически нынешняя страна героя.
  • price — полная цена приобретения, value — текущая оценка (у проданного — оценка на выходе). Проданные объекты остаются для истории, но не входят в капитал и больше не обновляются.
  • mortgage_payment — фиксированная годовая доля основного долга, к ней добавляются mortgage_left * mortgage_rate / 100 процентов. Взнос 20%, основной долг на 20 лет, ставка 6% — игровая модель. При погашении платёж становится 0. finance.debt уже содержит ипотеку. Если денег на год не хватило, дефицит становится обычным долгом; выселение/просрочка отдельно не моделируются.
  • rented означает, что жильё выставлено в аренду; доход может быть 0 из-за простоя. rent_income — уже поступившая аренда за год, rented_years — число лет с поступлениями. Есть содержание и ремонт. Скидка на бытовые расходы действует для собственного жилья в нынешней стране; иностранное жильё сохраняет оценку, но не оплачивает нынешний быт.
  • У старого известного items: ["home"] при явном подключении новой модели создаётся home-legacy, legacy: true, recognized_age — нынешний возраст, а цена, дата и страна прежней покупки не выдумываются. value — игровая оценка при подключении. Старое уменьшение расходов сохраняется, пока принадлежность жилья не изменится.

assets.investments[]: {key, kind, invested, value, since_age, status, last_return?}. kind: deposit, bonds, stocks, hot; status: active, sold, burned. invested — внесённая сумма, value — текущая или зафиксированная при выходе оценка, last_return — уже применённый процент. До первого следующего года доходности нет. Проданная позиция больше не растёт; сгоревшая остаётся с нулём. Обычные акции и облигации используют один исторический сценарий на год для всего портфеля; эти наблюдения не являются прогнозом. Фиксированные 3% вклада и рискованная компания (18% списания за год) — авторские коэффициенты. Вариант hot показывает 82% шанса пережить ближайший год, но не разрешает его немедленно при ответе.

decision.asset_key связывает текущий выбор с конкретной собственностью/позицией. decision.options[].cost — необязательное integer, зафиксированная полная цена ответа в текущих единицах, включая 0. Брать её с сервера; у прежнего открытого решения без поля сохраняется прежняя стоимость. Цена магазина приходит в shop[].cost. Нельзя повторно умножать суммы на индекс на фронте.

В memories[] добавлены свои assets, business, finance, а career содержит исторические название, заработок и повышения. Это снимки события; не подставлять туда сегодняшнее имущество. Годовые финансовые сцены сохраняют уже завершённый расчёт года. Новые варианты сцен: career_growth, business_start, business_review, business_first_employee, business_hard_year, business_closed, property_buy, property_manage, mortgage_paid, rental_vacancy, rental_repair, investment_start, investment_review, market_crisis, investment_burned. Словарь scene.key прежний: работа/вложения — work, жильё — home.

Открыть финансовую тему из пульта

POST /api/v1/channels/{id}/games/life/economy с правами интерактивных игр. Ответ {"status":"success"}, затем перечитать пульт. Тело: {"session_id":"UUID","item":"property","asset_key":"property-2"}. Вместо немедленной покупки открывается обычное голосование; прямой выбор ведущего и авторежим работают по прежним правилам.

item Условия
career 20–64 года, работа по найму, без учёбы. Повышение, другая команда, подготовка.
business Первое открытие 20–60 лет; для действующего дела — сохранить/продать, до 95 лет. После закрытия второго дела в этой версии нет.
housing 21–80 лет: своё жильё/арендное, наличные/ипотека.
property 21–95 лет, asset_key принадлежащего герою объекта. Продать, сдать или жить самому (в нынешней стране).
investments 18–90 лет: выбрать новое вложение.
investment 18–95 лет, asset_key активной позиции: продать/сохранить.

Нужна идущая жизнь без открытого решения и активный эфир либо тестовый запуск. Иначе 409; чужой/несуществующий объект — 404. Одна тема на объект доступна не чаще раза в игровой год, чтобы повторные открытия не давали бесконечные повышения; для новой покупки/карьеры лимит на саму тему. Для темы без объекта asset_key не передавать. Неактивные ответы имеют серверную причину, всегда остаётся бесплатный «Пока ничего не менять». Лимиты: четыре объекта одновременно / восемь приобретений за жизнь, четыре вложения одновременно / двенадцать за жизнь. Продажа — голосование, магазин ведущего сохраняет прежние покупки, включая жильё за наличные.

Фронт: панель денег и имущества, график известных годовых точек, карточки объектов с обязательствами и вложений с источником применённого сценария, кнопки финансовых тем в пульте. Покупки/продажи на публичной странице не выполнять. Графика, кнопки и HTML OBS разрабатываются отдельно.

Примеры economy[] в fixtures: обёртка {name, memory, year, decision?} над настоящими публичными Memory, YearRecord, Decision. Источник TestEconomyExamplesForVisualDevelopment, лог ECONOMY_EXAMPLES=. Приватные economy, входные таблицы и лимиты обсуждений не выдаются.