13 KiB
IMPLEMENTATION — контракт реализации (PEAK HOUR, Godot 4.7.2)
Точный контракт для параллельной разработки. Числа берутся ТОЛЬКО из
data/balance.jsonиdocs/DECISIONS.md(D01–D17). Параметры, не заданные в промпте, вbalance.jsonпомеченыnullи списком_todo— их нужно уточнить до кода, не выдумывать в реализации. Язык: GDScript. UI строится кодом;.tscn— только корень сцены. Комментарии/UI — на русском, идентификаторы — на английском.
1. Автолоады
| Автолоад | Файл | Ответственность |
|---|---|---|
Game |
scripts/autoload/game.gd |
состояния/переходы, сброс забега |
Data |
scripts/autoload/data.gd |
чтение res://data/*.json, строки |
Rng |
scripts/autoload/rng.gd |
сид забега, детерминизм |
Audio |
scripts/autoload/audio.gd |
SFX/музыка, громкости |
Game
signals:
state_changed(next_state: String)
hour_started(hour: int)
hour_finished(hour: int, report: Dictionary)
run_finished(report: Dictionary)
methods:
func start_new_run(seed: int) -> void
func next_hour() -> void # transitions Hour -> Prep/Results
func pause() -> void / unpause() -> void
func esc_pressed() -> void # routes per D10
fields:
var state: String # "menu"|"prep"|"hour"|"modal"|"results"
var day_number: int # счётчик 30+ ч для D02 (склад-поражение)
Автолоады не знают о конкретных сценах; сцены не хранят глобальное состояние.
Data
methods:
func load_balance() -> Dictionary # data/balance.json (валидация схемы)
func name(adjective: bool, seed) -> String # names_ru.json
func string(key: String) -> String # strings_ru.json, стиль btn.*/msg.*/ui.*...
fields:
var balance: Dictionary
var names: Dictionary
Rng
methods:
func set_seed(seed: int) -> void
func pick(ref: Array) -> Variant # детерминированный выбор
func nextf() -> float # [0..1)
Audio
methods:
func play_sfx(id_variants: String) -> void
func music(track: String) -> void
fields/настройки: music_volume: float, sfx_volume: float
2. Классы
| Класс | Файл | Ответственность |
|---|---|---|
FileRegistry |
scripts/core/file_registry.gd |
создаёт/хранит FileItem, выдаёт по id, счётчики буфера, required-карты запросов |
FileItem |
scripts/entities/file_item.gd |
данные файла: id, тип, name, ext, folder, флаги D09 |
Folder |
scripts/entities/folder.gd |
список файлов, capacity 8, отображалка |
Section |
scripts/entities/section.gd |
страницы папок, докупаемые позиции |
Packet |
scripts/entities/packet.gd |
входящий пакет + свой таймер (тики слишком идут из директора) |
Request |
scripts/entities/request.gd |
клиентский запрос: required (id), флаги slot_reserved/connected/minigame_active, таймер |
SlotManager |
scripts/core/slot_manager.gd |
слоты соединений (start→max по D13), резерв/освобождение |
HourDirector |
scripts/core/hour_director.gd |
ЕДИНЫЙ тик tick(delta) всех таймеров часа, спавны, канонический Clock |
AttackDirector |
scripts/systems/attack_director.gd |
выбор атаки, её эффект, отражение щитом |
Shop |
scripts/systems/shop.gd |
9 позиций D13, уровни, цены, применение эффектов |
SaveManager |
scripts/systems/save_system.gd |
save/load между часами, JSON, commit() |
MiniGame |
scripts/minigames/minigame_base.gd |
базовая модалка мини-игры, start(type, difficulty) |
ConnectionGame |
scripts/minigames/connection_game.gd |
полоска соединения по формуле D04 |
FileRegistry
signals:
files_changed()
file_failed(id: String)
methods:
func spawn_packet(type: String) -> String # ID нового пакета
func accept_to_folder(packet_id: String, folder: String) -> bool # (+0.5 SLA)
func attach(file_id: String, req_id: String) -> bool # D06: attach единственный
func detach(file_id: String) -> void # cooldown 20 c (D02)
func destroy(file_id: String) -> bool # D02:folder-only / malicious всегда
func try_reserve(req_id: String, file_id: String) -> bool
invariants:
- буфер = 4 + 2×buffer_upgrades, максимум 8 (D05);
- файл attached не более, чем в 1 запросе (D06);
- `failed` файл → все запросы, где он required, отменяются со штрафом (D06);
- `locked`/malicious файл кликабелен (D08).
HourDirector (единственный тикер)
signals:
packet_spawned(id: String)
request_spawned(id: String)
spawn_frozen(reason: String) # "modal"|"buffer_full" (D03)
methods:
func tick(delta: float) -> void # delta = min(delta, 0.1) (D11)
func set_paused(paused: bool) -> void
invariants:
- сущности не тикают сами; все таймеры замыруются здесь;
- спавн/таймеры пакетов: паез (D03/D05), открытая модалка/панель (D03);
- таймер активного запроса неудержно идёт даже с открытой модалкой (D03);
- после часа 8 таблица сложности — час 8, +5 % к скорости спавна каждые 2 ч (D12).
ConnectionGame
signals:
connected(success: bool)
methods:
func open(request_id: String, hour: int, successes: int, zone_level: int) -> void
func width() -> float # W-Zone по D04
# W = clamp(0.30 − 0.005*(hour-1) − 0.005*successes + 0.05*zone_upgrade_level, 0.18, 0.36)
# скорости: 440/340/280 px/с; окно ~130–200 мс
invariants:
- zone — только эта формула (D04/D05);
- открытая панель останавливает спавн приёма и таймеры пакетов (D03);
- Esc — отмена: слот свободен, запрос остаётся (D10).
Шоп-Shop (D13)
methods:
func can_buy(item_id: String) -> bool
func buy(item_id: String) -> bool
func level(item_id: String) -> int
invariants:
- 9 позиций с ценами/уровнями как в `balance.json → shop`;
- сканер/автосортировка не реализуются (D13, позиций «резерв» в данных НЕТ).
SaveManager
methods:
func commit() -> void # по началу подготовки
func load() -> Dictionary
fields: слот — между часами, JSON, без секретов и без вычислимых полей.
MiniGame
methods:
static func select_type(difficulty: float) -> String:
# coeff: text 1.0, sound 2.0, photo 3.0, video 4.0 (среднее по required, D17)
# <1.5 text, <2.5 sound, <3.5 photo, иначе video
func start(type: String, difficulty: float) -> void
invariants:
- модалка; открытие останавливает спавн приёма и таймеры пакетов (D03),
сама живёт на `PROCESS_MODE_ALWAYS`;
- Esc — no-op; закрытие — только кнопкой/таймаутом (D10);
- заглушка quality = 1.0 (D17).
3. Схемы сцен
scenes/Main.tscn (корень Control + scripts/main.gd)
└─ UI собирается кодом из scripts/ui/*.gd
├─ компоненты: panel90, button90, tree_panel, file_grid,
│ file_card, packet_card, request_card, slot_cell, status_bar…
└─ модалки: main_menu.gd, shop_modal.gd, pause_modal.gd,
settings_modal.gd, hour_result_modal.gd, run_result_modal.gd,
confirm_modal.gd
scripts/game_controller.gd (menu → prep → hour → hour_result → run_result)
scripts/core/hour_director.gd (единый тикер часа)
Main.tscn— единственная сцена игрового потока; отдельных сцен подготовки, часа и базы модалок нет.- В
.tscn— только корень сцены; все контролы строятся_ready()-кодом. - Фазами управляет
GameController, глобальное состояние — автолоадGame; сцены не переключают друг друга. - Модалки и мини-игры —
Control-скрипты наPROCESS_MODE_ALWAYS(D03/D11).
4. Drag & drop
Источник — карточка пакета (приём) или файла (папка/склад).
Цель — папка (приклад в приёме), карточка запроса (приклад в запрос).
Данные, возвращённые _get_drag_data:
return {
"kind": "packet" | "file" | "request",
"id": self.id,
}
- Отмена перетаскивания: ПКМ или
Esc— обработка в_cancel_drag(). - Подсветка:
_can_drop_dataпроверяет допустимость (capacity папки, лока, cooldown);_can_drop_data == true— цветовая подложка boundbox (зелёный/красный). - D06: файл, уже есть во втором запросе — рисуем его серым.
5. Таймеры и тики
- Единственный Clock =
HourDirector.tick(delta); сущности НЕ тикают сами. delta = min(delta, 0.1); авто-пауза приNOTIFICATION_APPLICATION_FOCUS_OUT;Engine.max_fps = 60(D11).PROCESS_MODE_ALWAYS— только на модалках/соединяшке (D03).- Список таймеров, монтируемых в директоре:
packet_timer(с D03),request_timer, спавн-интервалыrecv_interval,send_interval,attack_alert(3 с).
6. Схема data/*.json
Поле ping — только числа и строки, никаких секретов. Схемы — в файл источнике.
Подсказка: _todo по ключам — тег «уточнить», НЕ значение.
data/balance.json— вся аритметика игры (схема внутри файла).data/names_ru.json— пулы для генератора имён/организаций.data/strings_ru.json— ключи:btn.*,msg.*,ui.*,shop.*,attack.*.
7. Тестовые хуки
- Ускоренное время: OS env
PEAKHOUR_TIMESCALE=8.0(или в консоли Godot--seed TIME_SCALE=8.0) — HourDirector умножает время на множитель; deliberate только для тестов, в release НЕ включать (disable-флаг по D11). - Инжект сид: env
PEAKHOUR_SEED=123читает раниеRng.set_seed. - Headless: запуск
/home/eq/techlb/tools/godot/Godot_v4.7.2-stable_linux.x86_64 --headless --path . --script tests/run_tests.gd(или отдельныеtests/test_*.gd). - Web smoke: локальный
python3 -m http.server 8000 --directory build/web.
8. Распределение файлов
| Владелец | Зона |
|---|---|
| Lane A (core) | scripts/autoload/*, scripts/core/*, data/balance.json |
| Lane B (entities) | scripts/entities/* (FileItem, Folder, Section, Packet, Request) |
| Lane C (core/systems) | scripts/core/* (FileRegistry, SlotManager, HourDirector, Economy), scripts/systems/* (AttackDirector, Shop, SaveManager) |
| Lane D (ui/minigames) | scripts/ui/*, scripts/minigames/*, dev-сцены, strings_ru.json |
| Общие (не трогать) | project.godot, scenes/Main.tscn, scripts/main.gd, export_presets.cfg, шрифты, git |