← Модуль 2/Контроллер и ввод
EN
Модуль 2 · Console Wars

Контроллер и схемы ввода: путь сигнала

От пальца до CPU сигнал проходит дорогу: крестовина из 4 кнопок → последовательный сдвиговый регистр по одному проводу → опрос раз в кадр → конвейер задержки → распознавание жестов. Каждый шаг — обмен цены на отзывчивость и выразительность.
~16 мин
Суть за 20 секунд
D-pad Гумпэя Ёкои даёт 8 направлений из 4 кнопок-переключателей — плоско, дёшево, прочно, влезает в карман (так появились хэндхелды). Прочитать его CPU не может «всеми проводами разом»: контроллер NES — 8-битный сдвиговый регистр 4021, который по сигналу защёлки запоминает все кнопки, а потом по одному проводу выдаёт 8 бит по тактам. Консоль опрашивает пад раз в кадр (в vblank) → ввод квантуется в 1/60 с, а от пальца до пикселя набегает конвейер задержки в несколько кадров. Файтинги превратили 8 направлений в алфавит жестов (↓↘→ = Хадокен), читая скользящее окно последних нажатий — input buffer, который заодно прощает неточный тайминг.

Механизм

Контроллер — это путь сигнала от пальца к процессору. Разберём по звеньям: алфавит → провод → часы опроса → конвейер задержки → парсер жестов.

D-pad: 8 направлений из 4 кнопок

До крестовины был аркадный джойстик — громоздкий, хрупкий, в карман не лезет. Гумпэй Ёкои (Game & Watch, 1982, затем NES) свёл направление к кресту из 4 моментальных переключателей (U/D/L/R). Нажал между двумя лучами — замкнул сразу два → диагональ. Сколько состояний даёт 4 бита и сколько из них легальны:

24=16 сырых, но 3×3=9 легальных

Вертикаль ∈ {U, D, ничего}, горизонталь ∈ {L, R, ничего} → 3 × 3 = 9 состояний (нейтраль + 8 направлений). Противоположные пары (U+D, L+R) физически невозможны/игнорируются — поэтому не 16. Дёшево (4 копеечных свитча), прочно (нет рычага), плоско — ключ к хэндхелду. Философия Ёкои: «латеральное мышление с увядшей технологией» — взять дешёвое зрелое железо и применить нестандартно.

U D L R 4 переключателя → 8 направлений (диагональ = 2 свитча)
Жёлтые — диагонали: два переключателя одновременно. 4 бинарных входа → 9 легальных состояний. Дёшево, прочно, плоско.

Провод: защёлка и последовательный сдвиг

Контроллер NES — это микросхема 4021, 8-битный сдвиговый регистр. CPU не тянет 8 проводов от 8 кнопок. Вместо этого: сигнал latch (защёлка) разом параллельно загружает состояние всех 8 кнопок в регистр; затем CPU тактирует 8 раз, и на каждом такте из одного провода $4016 выпадает один бит — последовательно. Порядок жёсткий: A, B, Select, Start, Up, Down, Left, Right. Итого на опрос:

latch (1 операция) + 8 тактов = 9 обращений к шине → 8 бит кнопок

Зачем последовательно, если параллельно «быстрее»? Потому что дорог не такт, а провод: 8 жил в кабеле и 8 контактов в разъёме стоят денег и места. Один датавывод + latch + clock = 3 сигнала на любое число кнопок. Это тот же приём, что SPI/I²C: сериализуй, чтобы сэкономить пины. (Кнопки на pull-up: ненажатая = 1 в 4021, инвертируется для CPU.)

8 кнопок → latch (параллельная загрузка) A B Sel St U D L R такты clock → A → B → Sel → St → U → D → L → R один провод $4016, 8 тактов, по биту за такт
Параллельно защёлкнул, последовательно вычитал. 3 сигнала вместо 8 жил — сериализация ради экономии проводов. SPI до SPI.

Часы опроса и конвейер задержки

Консоль читает пад обычно раз в кадр, в vblank (через NMI). Значит вход квантуется в 1/60 с — между двумя опросами мир не знает о кнопке. Дальше нажатие идёт по конвейеру: опрос → логика кадра → симуляция → рендер → вывод на экран (scanout). Каждое звено — кадр. Полная задержка «палец → пиксель»:

Tлаг = n·16.67мс

Числовой пример. Палец нажал сразу после опроса кадра N → ждёт ~1 кадр до следующего опроса, +1 кадр на обработку, +1 кадр на scanout до появления на экране:

n=3 ⇒ Tлаг≈ 3·16.67=50мс

На ЭЛТ-телевизоре 80-х это и был весь лаг; современный беспроводной пад + буферизующий ТВ добавляют ещё кадры. Опрос раз в кадр vs прерывание по фронту — классический выбор «poll vs interrupt»: пад опрашивают (просто, синхронно с кадром), хотя теоретически могли бы дёргать прерывание на нажатии.

палец 👇 опрос N+1 логика+симуляция рендер → scanout → пиксель 👁 кадр N+1кадр N+2кадр N+3
Звенья конвейеризованы ради 60 fps: пока кадр выводится, следующий рендерится, через один — симулируется. Цена пропускной способности — задержка в несколько кадров.

Парсер жестов: моушены и input buffer

Файтинги превратили 8-направленный пад в алфавит жестов. Спецприёмы — это паттерны направлений + кнопка:

Движок держит скользящее окно последних нажатий (input buffer, ~8–16 кадров) и сопоставляет с шаблонами жестов. Буфер заодно прощает тайминг: команда, поданная на пару кадров раньше, всё равно сработает. Код Konami (↑↑↓↓←→←→BA) — та же буферизованная последовательность, а комбо — цепочки в окнах кадров. Это распознавание последовательности на потоке: окно = контекст, матч = крошечная sequence-модель.

🕹 В какие игры поиграть — и что заметить

Путь сигнала от «2 кнопки + крест» к «жест как слово» и «6 кнопок + шифты». По каждому: что в звене ввода сделано и что заметить руками.

NES · Contra / код Konami 1986–88 · минимальный алфавит, буфер последовательности

D-pad + 2 кнопки (A/B) + Start/Select. Весь «язык» — 8 направлений и две кнопки. Код Konami (↑↑↓↓←→←→BA) — буферизованная последовательность, считанная тем же сдвиговым регистром; в Contra даёт 30 жизней.

🎮 Сыграй: введи код Konami на стартовом экране Contra. Заметь: важен порядок и окно ввода — это распознавание последовательности из потока сдвигового регистра, а не «волшебная кнопка».

Super Mario Bros · переменный прыжок 1985 · аналог из бинарной кнопки

Кнопка A бинарна (нажата/нет), но высота прыжка зависит от длительности удержания: игра считает, сколько кадров держат A, и кормит этим физику. Из одного бита выжата непрерывная нюансировка — интерпретация ввода, а не новое железо.

🎮 Сыграй: в SMB тапни A коротко и зажми надолго — высота разная. Это «сколько кадров держали кнопку», прочитанное опросом каждый кадр; чистая интерпретация бинарного входа.

Street Fighter II · моушены 1991 · 8 направлений как язык жестов

Пад/стик стал алфавитом: ↓↘→ + удар = Хадокен. Движок сопоставляет окно входов с шаблоном; ширина окна — ручка «лёгкость vs ложные срабатывания». Аркадный стик и пад дают разную точность ввода одних и тех же жестов.

🎮 Сыграй: покрути Хадокены Рю. Поймай, как иногда фаербол вылетает случайно при ходьбе-прыжке (паттерн совпал в окне), а иногда «не читается» резкий моушен. Это буфер распознавания во плоти.

SNES · 6 кнопок + шифты 1990 · рост алфавита → новые глаголы

NES дал 2 кнопки, SNES — 4 лицевых (A/B/X/Y) + 2 шифта (L/R). Больше кнопок = больше глаголов без моушенов: переключение предметов, отдельный «бег», прицел. Шифты позже раскрыли схемы для гонок и Mode-7-игр.

🎮 Сыграй: в любой SNES-игре посчитай, сколько разных действий навешано на 6 кнопок против 2 на NES. Заметь, как шифты дают «модификатор» (как Shift на клавиатуре) — это расширение алфавита, а не скорости.

Game Boy · крест в кармане 1989 · окупаемость плоского D-pad

Главный дивиденд крестовины: направление, достаточно плоское для кармана. Тот же 4-свитчевый крест + 2 кнопки, что на NES, но в хэндхелде — то самое «латеральное мышление с увядшей технологией» Ёкои.

🎮 Сыграй: возьми любой Game Boy-эмулятор и почувствуй, что схема ввода идентична NES. Крест выиграл не выразительностью, а форм-фактором — он влез туда, куда джойстик не мог.

Хардкор · инженерия: почему опрос, а не прерывание, и где прячется лишний лагможно пропустить

Ввод можно читать двумя способами, и выбор не случаен.

Poll vs interrupt

Опрос: раз в кадр прочитал состояние пада — просто, детерминированно, синхронно с симуляцией (вход привязан к тому же fixed-step, что физика, см. m01). Прерывание: дёргать обработчик на каждом нажатии — реагирует мгновенно, но асинхронно, плодит гонки с обновлением состояния и недетерминизм (плохо для реплеев/нетплея). Игры берут опрос именно ради детерминизма: ввод кадра N принадлежит кадру N. Цена — события короче кадра невидимы.

Где набегает лаг сверх минимума

  • Опрос не в начале кадра: читаешь пад поздно — теряешь до кадра.
  • Глубокий конвейер рендера: 2–3 кадра «в полёте» ради пропускной способности (как batching в инференсе: throughput ↑, latency ↑).
  • Буфер дисплея/ТВ: постобработка, кадровый буфер овердрайва — ещё кадры.
  • Беспроводной пад: радиостек добавляет переменную задержку.

Минимизация «палец→пиксель» = ранний опрос + неглубокий конвейер + честный дисплей. Это бюджет латентности, тот же, что у любого интерактивного сервиса.

Алиасинг частоты опроса

Опрос 60 Гц ставит потолок на самый быстрый различимый ввод: тап короче ~16.7 мс, начавшийся и кончившийся между опросами, невидим (Найквист для пальцев). TAS эксплуатируют это покадрово; человеку приходится удерживать ввод через границу опроса.

Хардкор · дизайн: input buffer как ручка «точность ↔ прощение»можно пропустить

Буфер ввода — это окно из последних k кадров, по которому распознаётся жест/команда. Ширина окна — единственная, но мощная ручка.

Шире окно — легче ввод, больше ложных

Жест ↓↘→ матчится, если все три направления встретились в пределах k кадров. Большой k: даже грязный, медленный моушен срабатывает (новичкам легче), но растёт ложное срабатывание — фаербол при обычной ходьбе-прыжке. Маленький k: чисто, но требует точности. Это та же кривая precision/recall, что у wake-word-детектора: порог чувствительности гонит компромисс «пропуски vs ложные».

Прощение тайминга

Тот же буфер прощает ранний ввод: команда прыжка за пару кадров до приземления ставится в очередь и срабатывает в первый легальный кадр. В платформерах это «jump buffering» (см. game feel) — но там это выход (ощущение отзывчивости), а здесь — вход (механика распознавания из потока). Одна и та же буферизация, разные стороны звена.

Связь с комбо

Комбо — цепочка приёмов, каждый в своём окне «отмены» (cancel window): попал → в течение N кадров можно «отменить» восстановление в следующий приём. Тайминг комбо = последовательность окон буфера; фрейм-дата (см. кристаллизацию жанров) задаёт, какие окна вообще существуют.

Аналогия
Контроллер — это азбука Морзе по одному проводу: защёлкнул кнопки, и они стучат в линию по биту за такт. Игра — стенографистка: читает поток «буквенных» направлений и буферизует несколько, чтобы распознать целые «слова» (моушены). D-pad — алфавит (8 букв из 4 клавиш), а конвейер задержки — почтовая задержка между «написал» и «адресат прочёл».
Почему это важно
У любой интерактивной системы есть бюджет «палец → пиксель» (или «запрос → ответ») и слой распознавания ввода. Контроллер учит этому в самом чистом виде: горстка переключателей, один последовательный провод, тактовая частота кадра и парсер жестов — это та же анатомия, что у тачскрина, клавиатуры или API-эндпоинта с SLA на латентность. Понял путь сигнала здесь — узнаёшь его всюду.
🔁 За пределами игр — куда это переносится
Урок даёт три переносимых приёма: сериализация ради экономии каналов, опрос с фиксированной частотой + бюджет задержки и буфер-окно для распознавания и прощения.

ML / AI (твой домен): бюджет «палец→пиксель» = бюджет латентности инференса; стриминг LLM меряют TTFT (time-to-first-token) — тот же «запрос→первый пиксель». Input buffer ⇄ action chunking в imitation learning и робополитиках (ACT, VLA: предсказываем буфер из нескольких действий и проигрываем за несколько шагов — буферизация выхода). Распознавание моушена в окне ⇄ классификация последовательности по скользящему окну (окно буфера = контекст; матч = маленькая seq-модель; ширина окна = precision/recall, как у wake-word). А раскладка действий на кнопки ⇄ дизайн action-space в RL: как параметризуешь действия — то и выучиваемо.

Системы / железо: сдвиговый регистр = SPI/I²C и сериализация ради экономии пинов; latch = sample-and-hold; poll vs interrupt = busy-poll против epoll/IRQ и event loop; квантование опроса = частота дискретизации и Найквист.

UX / фронтенд: бюджет ввода = воспринимаемая отзывчивость (input latency); debounce/throttle = тот же input buffer; раскладка действий = affordances и горячие клавиши-модификаторы (Shift = «шифт» геймпада).

Принцип: сериализуй, чтобы экономить каналы; опрашивай с фиксированной частотой и считай весь конвейер «вход→отклик»; держи окно-буфер, чтобы распознавать жесты и прощать тайминг.

🔧 Запусти и поковыряй — на домашнем компе
Во что играть — выше (🕹). Здесь — пощупать путь сигнала через эмулятор и измерение лага:
🔧 Поковырять (debug) ~40 мин, Mesen
В Mesen открой просмотр ввода и поставь брейкпоинт на чтение $4016 — поймаешь latch + 8 тактов опроса пада раз в кадр (в NMI/vblank). Включи отображение задержки кадров. Сравни игру, опрашивающую пад рано в кадре, с опрашивающей поздно — разница в лаге видна. В файтинге-эмуляторе найди окно input buffer: введи моушен «грязно» и «чисто», смотри, когда матчится.
🧪 Потестить (глазами QA) ~15 мин
Ищи артефакты ввода: пропуск суб-кадровых тапов (нажал-отпустил быстрее кадра — не сработало), ложные спецприёмы при ходьбе (буфер поймал паттерн), «проглоченный» ввод при лаг-спайке (опрос пропустил кадр). Прикинь «палец→пиксель»: нажми и считай кадры до реакции на экране.
Чеклист: увидел latch+8 тактов на $4016; поймал ложное срабатывание моушена из буфера; оценил лаг в кадрах.
Связи
основа
Железо-ограничения — та же бережливость «не трать дефицитное»: сдвиговый регистр экономит провода, как тайлы — память. Сериализация = фругальность по пинам.
основа
Игровой цикл — опрос привязан к кадру и fixed-step; задержка считается в кадрах × dt. Детерминизм ввода = детерминизм цикла.
смежное
Game feel — jump-buffer/coyote оттуда — это выходная сторона буферизации (ощущение отзывчивости); здесь буфер на входе (распознавание жеста). Одно звено, две стороны.
дальше
SNES Mode 7 — следующий урок модуля: шифты и доп. кнопки этой эпохи раскрыли схемы управления для Mode-7-гонок (псевдо-3D).
Вопросы пытливого ума
Зачем читать пад последовательно (1 провод, 8 тактов), если параллельно «быстрее»?
Потому что узкое место — не время, а провода. 8 параллельных жил = толстый кабель, дорогой разъём, дорожки на плате. Один датавывод + latch + clock = 3 сигнала на любое число кнопок, а лишние такты у CPU и так есть. Это ровно логика SPI/I²C: сериализуй, чтобы экономить пины. Бутылочное горло было физическим (контакты), а не вычислительным.
Если ввод опрашивается раз в кадр, что будет с тапом короче 16.67 мс?
Его могут не заметить вовсе: нажатие, начавшееся и кончившееся между двумя опросами, невидимо. Это алиасинг — частота опроса (60 Гц) задаёт самый быстрый различимый ввод. TAS эксплуатируют покадровость; человеку для гарантии надо удерживать ввод через момент опроса. Поэтому «фрейм-перфект» трюки требуют попасть именно в кадр опроса.
Как игра отличает намеренный Хадокен (↓↘→) от случайного прохода этих направлений при ходьбе?
Идеально — никак. Она матчит паттерн в пределах временного окна (буфера), поэтому «ходьба → прыжок» иногда выстреливает случайным фаерболом, и сильные игроки это учитывают/избегают. Ширина окна — ручка «точность vs прощение»: шире = легче ввод, но больше ложных срабатываний. Тот же компромисс, что у детектора ключевого слова: чувствительнее → больше ложных.
Почему D-pad не сделали аналоговым сразу — разве аналог не строго лучше?
Нет. Цифровой 8-way дешевле, прочнее и точнее для дискретного движения: платформеру нужно однозначное «влево», а не «0.37 влево» с проблемой мёртвой зоны. Аналоговый стик пришёл (N64/PS), когда 3D потребовало непрерывного направления. Фиделити должна соответствовать задаче — тот же принцип, что fixed-point против float: бери минимально достаточное представление, а не максимально точное.
Конвейер задержки — несколько кадров; почему не «опросил-обработал-показал» в один кадр, убив лаг?
Потому что звенья конвейеризованы ради пропускной способности: пока кадр N выводится, N+1 рендерится, N+2 симулируется. Схлопнуть их в строгую последовательность = простаивающее железо и просадка fps. Латентность против пропускной способности — фундаментальный трейд конвейера: меняешь задержку «палец→пиксель» на стабильные 60 fps. Тот же выбор, что batching в инференсе: больше батч → выше throughput, хуже задержка на запрос.
Что почитать