«Жизнь канала»: контракт для визуальной разработки¶
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, входные таблицы и лимиты обсуждений не выдаются.