
Живой эмулятор в браузере — amesk.github.io/yaPDP
Исходный код и десктопная версия — GitHub
Каждый программист «за сорок» рано или поздно проходит через этот кризис среднего возраста. Кто‑то покупает мотоцикл, кто‑то уходит в монастырь функционального программирования… а потом ты становишься «программистом за пятьдесят», и тут‑то вспоминается детство.
У меня оно было интересное (для меня). Запах озона, мигание лампочек на инженерном пульте советских клонов DEC — машин СМ-4 и СМ-1420, за которыми я впервые увидел живой компьютер, — и та самая первая любовь: язык C и старинный компьютер с не менее старой ОС. Любовь, которой уже сорок лет. Естественно, мне захотелось вернуть свой 1984-й.
Но современный ретро‑компьютинг — это секта для сильных духом. Чтобы просто пощупать Unix V5 или RT-11 в каком‑нибудь классическом SIMH, вам нужно: скачать эмулятор, найти на полумертвых FTP‑архивах образы дисков, написать километровый конфигурационный файл и… обнаружить себя перед унылым черным окном стандартного терминала Windows…
Где гул вентиляторов? Где грохот телетайпа, от которого закладывает уши? Где, черт возьми, романтика? В общем, я решил сделать свой эмулятор — yaPDP (Yet Another PDP-11). И сделать его так, чтобы все решения были осознанными, а не «так получилось, потому что я нашел этот кусок кода на StackOverflow потому что мне это нагенерил ИИ».

Исторический экскурс: что такое СМ-4 и СМ-1420 для советского инженера?
Для тех, кто не застал эпоху ЕС ЭВМ и СМ ЭВМ, поясню. В СССР существовала «Система Малых ЭВМ». И если машины СМ-1 и СМ-2 были вещью в себе, то начиная с СМ-3 и СМ-4 (разработанных в ИНЭУМ), советская промышленность пошла по проверенному пути реверс‑инжиниринга и полного копирования архитектуры PDP-11 от Digital Equipment Corporation (DEC).
СМ-4 (1979 год): это был аналог PDP-11/40. Огромные стойки размером с пару платяных шкафов, требующие отдельного помещения с кондиционированием. Она работала на частоте около 1–2 МГц, поддерживала до 256 КБ оперативной памяти (в базовых версиях и того меньше — 128 КБ на магнитных сердечниках!) и имела общую шину интерфейс ИМК (советский аналог UNIBUS).

СМ-1420 (1983 год): мой личный фаворит. Это уже был полноценный функциональный аналог мощной PDP-11/34 и частично 11/45. Здесь появилась аппаратная поддержка плавающей запятой, диспетчер памяти, расширяющий адресное пространство до 4 МБ, и — вершина инженерной мысли тех лет — возможность подключения «винчестеров» ИЗОТ (советские аналоги дисков DEC RK05 или RM02) объемом целых 5 или 29 Мегабайт! Диск представлял собой тяжелую «кастрюлю» со сменными магнитными блинами, которую нужно было аккуратно вставлять в накопитель.
Именно на этих машинах в СССР крутились операционные системы РАФОС (клон RT-11) и ОС РВ (клон RSX-11M). Запустить на СМ-1420 многопользовательскую систему, сесть за алфавитно‑цифровой терминал вроде ВТА-2000 или ДВК, запустить компилятор Си и написать код, который управляет каким‑нибудь станком на заводе — это был чистый драйв. Я хотел вернуть именно это ощущение управления настоящим железным шкафом.
ДВК-2 — советская PDP-11-совместимая машина, часто работавшая терминалом к СМ ЭВМ (Wikimedia Commons)

ВТА-2000 — тот самый алфавитно‑цифровой терминал, за которым я работал в машинном зале
Проектирование от хотелок, или железная логика компромиссов
Когда создаешь проект в одиночку, главное — не утонуть в собственном перфекционизме (или разгильдяйстве). Поэтому каждое архитектурное решение принималось через жесткую триаду: «Хочу» — «Надо» — «А как иначе?».
1. Атмосфера против сухого консольного вывода
Хотелка: передать физический опыт работы в машинном зале.
Решение: хороший эмулятор + богатые выразительные средства. Что у нас лучше всего умеет в мультимедиа и доступно везде? Связка JS + HTML. В качестве «сердца» я взял отличный цикл‑точный эмулятор pdp11-js Пола Нанкервиса. Но обернул его в визуальный и звуковой ряд, заодно расширив его под свои нужды и поправив ряд ошибок.
Впрочем, «взять готовое ядро» — это только половина дела. Чтобы все гостевые ОС стабильно грузились и жили в браузере, пришлось глубоко залезть и в сам эмулятор. Самое показательное:
— CPU: исправил поведение при двойном прерывании — CPU.mmuEnable теперь корректно сбрасывается в 0, как на настоящей PDP-11;
— Загрузка: убрал бесконечную рекурсию прерываний при старте RP1 в супервизорном режиме и гонки в асинхронном diskIO (очередь отложенных callback‑ов);
— Web‑специфика: вынес передачу данных diskIO в контекст CPU и привез fzstd локально — это устранило «двойной трап» на GitHub Pages, главный баг веб‑сборки;
— Образы: .zst‑образы дисков и лент грузятся напрямую (меньше мусора в консоли), в том числе на хостах, присылающих Content-Encoding. Последнее, кстати, является основной причиной, почему в оригинальном проекте в своё время перестали грузиться образы у самого Пола (сейчас все работает).
Полноценная передняя панель с тумблерами скрещена с легендарной эмуляцией телетайпа от Норберта Ландштайнера (проект Google60).
Правда, честно говоря, после самых первых опытов оригинальный телетайп пришлось выкинуть, а на его обломках разработать уже настоящий — если к имитации это слово вообще применимо. Но именно это заимствование на начальном этапе позволило быстро стартовать и не утерять интерес к проекту.
Телетайп честно рендерит трехмерные клавиши, двигает виртуальную бумагу и поддерживает nroff/man overstrike (когда для жирности шрифта литера бьет по бумаге дважды).


Погружение в эпоху — это когда адекватный видеоряд подкреплен правильно подобранным звукорядом. У меня фоном дрожит блок питания, телетайп стрекочет, а строчный принтер завывает строго в такт выводу данных. Закрываешь глаза — и тебе снова четырнадцать, а на дворе 1984-й.
2. Юзабилити против хардкора
Хотелка: дать людям поиграться в один клик. И чтобы они открыли это чудо больше одного раза.
Решение: веб‑приложение формата SPA (Single Page Application). Открыл вкладку — и ты стоишь перед стойкой PDP.
Современный айтишник при виде приглашения Boot> впадает в ступор. Поэтому я сделал встроенную систему подсказок и Wizard (“Волшебная палочка”) для нетерпеливых. Один клик — и скрипт сам выбирает одну из 16 гостевых ОС, на лету переконфигурирует железо, щелкает тумблерами на передней панели, вводит команды логина в консоль. Секунда — и у вас запущен рабочий UNIX V5 или 2.11 BSD.
Скрытый текст

При этом для истинных извращенцев (как я) оставлена возможность реально запустить машину с нуля. Отключаем автоматику, берем шпаргалку с бутстрап‑кодом, которую я заботливо «приклеил» скотчем рядом с панелью, и вручную нащелкиваем тумблерами загрузчик в память.


Один клик по «волшебной палочке» — и ОС загружена: RT-11 и Unix V5.
Ностальгия по C: почему телетайп, а не VT100 из Xterm.js
Я — любитель языка C, и мне хотелось поработать именно так, как это делали Керниган и Ричи (этот момент даже попал в графический фон эмулятора). Отсюда и выбор терминалов: телетайп и самопальный эмулятор VT52. Нормальный человек, конечно, взял бы Xterm.js — и, возможно, я возьму его, когда мне понадобится эмуляция VT100. Но пока хочется именно ощущения «железа», а не пиксельно идеального терминала.
Скрытый текст

Та самая книга, под которую всё и затевалось. Переведённая, распечатанная на АЦПУ и сброшюрованная, она до сих пор хранится где‑то у родителей, в моей бывшей комнате.

VT52 в yaPDP вместо Xterm.js

DEC VT100 — если понадобится его эмуляция, возьму Xterm.js
Перфоратор: правдоподобие до последней дырочки
Раз уж появился телетайп — хочется сделать его максимально правдоподобным. Так появился перфоратор, который работает примерно так же, как оригинал. А ленты, экспортированные с него, прекрасно заправляются в перфосчитыватель PDP-11 — и в симулированный, и, при желании, в настоящий (если они ещё остались живые).
Правда, для этого перфоратор пришлось починить: в оригинальном эмуляторе он был сделан в минимальном объёме, достаточном для запуска загрузчика. RT-11 его уже не видела и нормально работать с ним не могла. А как только появилась возможность принимать ленты — захотелось эти ленты и «пробивать». Это тоже пришлось писать с нуля.

Перфоленты: 8 дорожек для данных, 5 — для телетайпа.
За время игр с реальными ОС того времени стало понятно
откуда родилась концепция пайпа, оказывается, что изначально Керниган и Пайк просто перфорировали выдачу одной программы и заправляли её на вход другой, потом сделали это программно через буфер (пруф истории успел потеряться);
почему Unix V5 такая «молчаливая», а утилиты, если всё хорошо, не говорят ничего — за время загрузки 2.11 BSD можно успеть сходить налить себе чаю, и даже его выпить;
почему экономили каждый бит и байт, тут сразу вспомнилась история про скрытые файлы Unix, любой напечатанный не по делу символ — это твоё потраченное впустую время.
Эмулятор, который собирает сам себя
После успешной модификации появилась возможность обмениваться данными с хостовой машиной и модифицировать начальный загрузчик. То, что сделал Пол, было великолепно для демонстрации: в его bootstrap входили сам загрузчик, мигалка светодиодами на панели, небольшой HELP и ODT — DEC On‑line Debugging Tool, который в те годы прилинковывался в виде .OBJ к чему угодно, давая функциональность отладчика. Но мне это подходило не совсем.
Поэтому берём созданный эмулятор, грузим RT-11, превращаем boot.mac Пола в перфоленту, в RT-11 копируем её на диск и запускаем MACRO-11 и LINK. Результат выгружаем обратно и с помощью скрипта превращаем в дамп, который встраивается в JS (для работы автозагрузчика). С этого момента проект начинает помогать себя разрабатывать.
Более того, из‑за специфики платформы получилось сделать и Node.js‑скриптик, который на вход принимает ассемблерный файл, в фоне выполняет всю работу, а на выходе отдаёт собранный образ — или сразу готовый дамп. Сейчас это tools/rebuild-bootcode.js (npm run rebuild-boot): он гоняет host‑сторонние macro11.exe и pclink11.exe и пересобирает src/bootcode.js из macro-asm/boot.mac.
Техническая изнанка и примеры кода
А теперь перейдем к реализации: как упаковать все это добро и заставить автоматику работать на нас.
Как заставить Tauri работать без знания Rust
Вы спросите: «Если ты хотел десктопное приложение, почему Tauri, а не старый добрый Electron?» Ответ прост: Electron тащит с собой Chromium и Node.js, раздувая пустой Hello World до 150–200 МБ. Tauri использует системный WebView (WebView2 на Windows, WebKit на macOS), поэтому бинарник весит копейки.
Но для Tauri нужно писать бэкенд на Rust. А я не знаю Rust и, честно говоря, в рамках этого проекта учить его не хотел. Мой проект — это чистый Vanilla JS. Как быть?
Решение: мы полностью игнорируем экосистему Rust и используем Tauri исключительно как ультралегкую нативную обертку. Весь наш SPA‑код упаковывается внутрь, а вся конфигурация — это один обычный JSON‑файл без единой строчки кода на Rust. Вот вариант Full (tauri.conf.full.json).
Обратите внимание: вместо wildcard‑папок мы явно перечисляем каждый файл в bundle.resources — диски, ленты и перфоленты из каталога media/: RK/RL/RP/RA, TM и перфоленты). Для Full‑версии Tauri упаковывает все образы в ресурсы приложения, а наш JavaScript в фоне запрашивает их через Rust‑команду load_bundled_image, распаковывает Zstandard‑сжатие и монтирует в DataLoader — эмулятор полностью работает офлайн. Минимальная сборка (tauri.conf.minimal.json) включает только базовые образы rk0, rk1 и bootcode и весит около 3 МБ; остальные образы можно просто перетащить в окно приложения. Electron нервно курит в сторонке.
За это, правда, как и всегда, придётся платить — мы используем тот браузер, который у нас стоит в системе, с вёрсткой могут быть сюрпризы. Но оно того стоит.
Ещё один плюс Tauri — платформа сама собирает простенькие инсталляторы через NSIS и WiX, что сильно упрощает выкатку продукта наружу (в конфиге выше как раз видны nsis.sidebarImage, wix.bannerPath и dialogImagePath). Правда, инсталляторы именно что простенькие: нормально модифицировать UI‑sequence — та ещё задача, а WiX мне хватает и на основной работе, чтобы заниматься этим ещё и добровольно, так что я просто не стал заморачиваться. Единственное, что стоит держать в уме, развести идентификаторы сборок (у нас org.yapdp.emulator.full и org.yapdp.emulator.minimal), чтобы установка полной версии поверх минимальной не создавала проблем в базе MSI, и заодно поправить минимальную символику инсталлятора.
Автоматизация скриншотов через Puppeteer: дизайн для бедных
Раз уж я выпустил десктоп‑версию, будь любезен предоставить сайт, с которого её можно загрузить. А раз сделал сайт — сделай его интересным, с красивой галереей запущенных ОС и максимумом материалов, которые позволят зашедшему на ресурс решать — а стоит ли двигаться дальше.
Я работаю один, рисовать UI и обрабатывать скриншоты вручную мне лень. Но раз уж у меня под капотом Node.js, пускай компьютер работает за меня. Зачем вручную запускать каждую гостевую ОС и нарезать скрины? Напишем скрипт на puppeteer-core, который программно откроет наш эмулятор в headless‑режиме, по очереди нажмет «волшебную палочку» для каждой ОС, дождется реальной готовности системы и сделает попиксельный снимок экрана.
Вот реальный кусок моей автоматизации — tools/screenshots-os.js:




Галерея гостевых ОС с лендинга — результат работы этого скрипта
Он экономит часы и дни работы. Изменился шрифт телетайпа или текстура тумблеров на панели? Запускаем скрипт, и через две минуты вся галерея на сайте обновлена свежими, аутентичными скриншотами.
Что же получилось
Основную цель — сделать «иммерсивный симулятор», у которого хочется не только работать, но и просто стоять рядом, разглядывая бегущие огоньки, — считаю достигнутой. Код во многих местах, конечно, говнокод: то, что собиралось «на коленке», в выходные, ночью и по принципу «работает — не трогай». Зато теперь есть точка, от которой можно плясать: рефакторить, чистить и улучшать проект, не добавляя новых фич, — именно этим я и намерен заняться.
Единственное исключение из правила «новых фич не добавляем» — работающий перфосчитыватель на телетайпе: заправляешь ленту в считыватель, и её содержимое побайтово уходит в машину (это режим AUTO: компьютер в старт‑стопном режиме принимает то, что записано на ленте, режим START для этого не годится — данные просто «молотятся в воздух», их можно не успеть принять).
Второе направление — документация и средства развёртывания. А после рефакторинга, кто знает, может, удастся задонатить исправленные ошибки эмулятора и автору оригинальной системы — если он ещё в этом заинтересован. Судя по тому, что последний коммит был полгода назад, вероятно, да.
Небольшое отступление. Эмулятор Пола — далеко не единственный подходящий донор. Есть, например, PCjs с куда более продуманной архитектурой и более гибким механизмом монтирования накопителей. Но мне это не нужно: PCjs сам вырос из эмулятора Пола, переработанного в сторону универсальности и расширяемости. Для этого проекта концепция «вся Вселенная в JS» пока кажется излишней — примерно как «640K ought to be enough for anyone» ©. Время покажет.
Заключение
Проект yaPDP создавался не для того, чтобы решать современные бизнес‑задачи. Он создавался для того, чтобы вернуть чувство машины. То время, когда компьютеры объявляли о своих мыслях щелканьем реле и миганием лампочек, а не тихим жором оперативной памяти в фоне.
Если вам тоже хочется поймать этот вайб, скомпилировать программу на древнем K&R cc в Unix V5 или просто послушать, как матерится телетайп при неверной команде — добро пожаловать на борт.
Потыкать в браузере: amesk.github.io/yaPDP
Посмотреть код и скачать десктоп: GitHub Repository
Буду рад вашим звездам, пул‑реквестам и ностальгическим историям в комментариях о том, как вы что‑то делали на тех самых СМ‑ках!
P. S. Когда доведу до ума эмулятор, попробую добраться и до СЫНТАЫ EППОП с ИНВАЛИД ДЕЖИЦЕ (для тех, кто в теме;‑) )

