Новости

ACNews #79

Новости

by Alone Coder

Всем привет!

В связи с возможными отключениями значительной части интернета напоминаю мой альтернативный e-mail: alone_coder@mail.ru. Также к моему номеру телефона +7-960-571-86-20 привязан MAX. Туда же переехал наш чат из заблокированного в прошлом году Дискорда.

Движок новой вольф-подобной игры теперь поддерживает нижнюю панельку, а графические ресурсы импортируются внешней утилитой, а не перекодируются во время старта (что занимало много времени). Надеюсь, что игра всё-таки будет доделана.

Систему NedoLang удалось ускорить почти в два раза за счёт выкидывания токенизатора из главного пути компиляции. Теперь токенизирует сам компилятор, а ассемблер научился считать выражения в прямом порядке (они теперь лежат текстом). Токенизатор нужен только для ассемблерных модулей, а можно написать токенизированный редактор. Теперь самосборка компилятора занимает около минуты на ZX Evo, токенизатора и ассемблера - по полминуты. Около половины этого времени занимает ассемблирование. Если ассемблер переписать на ассемблере, то можно ускорить его раза в два. Кстати, Дмитрий Анохин уже написал простейший ассемблер для NedoOS на ассемблере https://github.com/deemess/assembler/

Последнее время я не могу стримить из-за постоянного попадания детей в кадр, удалось провести только два стрима про сжатие текста для игр (если кому интересна эта тема, свяжитесь). Благодаря этим стримам удалось познакомиться с Павлом Середой, который живёт тут же в Рязани и умеет изготавливать пластиковые детали и другую технику прямо у себя дома.

В 2022 году я экспериментировал с так называемым "искусственным интеллектом" для генерации картинок. Я не смог установить на свой компьютер Stable Diffusion (оказалось, что ему нужна мощная видеокарта), но сгенерил графику для новогоднего гифта через онлайн-сервисы. За прошедшие 3 года качество генераторов сильно улучшилось. В частности, сгенерированные картинки уже неотличимы от реальности и могут анимироваться. Может быть, кто-то видел ролик с анимациями спектрумовских картинок на ютюб-канале Sergio Neznany. Такие анимации даже уже используются в кино и мультфильмах.

Вместе с Event'ом и John Silver'ом мы попробовали воспользоваться современными возможностями ИИ в области музыки. Я не имею в виду генерировать целые песни в Suno или обрабатывать готовые (negative harmony cover или всяческие стилизации, которыми уже заполнен интернет). Нас интересовал разбор готовой музыки. Izotope RX научился достаточно чисто раскладывать музыку на каналы. С помощью этого инструмента мы смогли приблизить к оригиналу один ремикс (новый, сделанный Event'ом), который потом пойдёт в следующий номер Info Guide. Присылайте свои статьи и музыку!

ИИ для написания кода

Последние три месяца мы с Павлом Середой пытались приручить ИИ для генерации ассемблерных программ для ZX Spectrum. Основным инструментом был Qwen, уже проверенный Павлом для создания простых программ на Golang. Мы прикрутили к Qwen'у шеллскрипт с автоматическим запуском компилятора (SjASMPlus) и эмулятора (сначала MAME, потом Unreal Speccy). Сам он не смог написать этот скрипт, но был довольно близок к цели. По дороге он даже забывал одно из первых требований (запуск в режиме ATM Turbo) и пытался запустить эмулятор в режиме Profi, о котором речь даже не шла. В Unreal Speccy мы добавили автоматический выход по команде RST 0 с созданием снапшота и провели серию экспериментов.

Сначала предполагалось, что ИИ сможет написать программу отрисовки стрелочки мыши. Надежды были наивными - множество запросов в разных стилях, уточнений и т.п. не дали никакого, даже частично рабочего результата. Программа либо сбрасывалась, либо рисовала на экране чепуху. Qwen каждый раз абсолютно уверенно сообщал, что решил задачу. Мы давали ему снапшот (он читал его через hex editor, поскольку умеет обрабатывать только текст), но он совершенно не понимал, что на экране не то, что нужно. Постоянно забывал адресацию спектрумовского экрана, хотя изначально её знал. Математические ошибки доходили до того, что он путал регистры h и l или затирал регистры, значения которых используются ниже. Максимум, что получилось - это вывод "Hello world", но он удачно сгенерировался только один раз, а потом успех не повторялся. Причём нужно обязательно уточнять в запросе, чтобы Qwen не использовал процедуры ПЗУ, потому что он имеет о них крайне превратное представление.

Первый запрос выглядел так:

"перед тобой почти пустой проект для разработки под ATM Turbo 2
(клон ZX Spectrum). Главный исходник @source.asm . Чтобы
скомпилировать, нужно запустить make. Соберётся @release.trd .
Чтобы запустить эмулятор со скомпилированной программой, нужно
выполнить @./emu_start.sh . Когда твоя программа выполняет
команду rst 0, эмулятор сохраняет снапшот @qsave1.sna и
завершает своё выполнение. Так ты можешь узнать, что твоя
программа изменила в памяти. Твоя задача: написать "Hello,
world!", не используя подпрограммы ПЗУ. Контроллируй успех при
помощи проверки дат созданных файлов."

Типичный ответ после нескольких уточнений выглядел так:

"Shell xxd -s 2600 -l 128 debug2_screen.bin (Look at the
screen memory around row 10, column 5)

    00000a28: 0022 2222 3e22 2222 0000 0000 0000 0000
    00000a38: 0000 0000 0000 0000 0000 0000 0000 0000
    00000a48: 0000 0000 0000 0000 0000 0000 0000 0000
    00000a58: 0000 0000 0000 0000 0000 0000 0000 0000
    00000a68: 0000 0000 0000 0000 0000 0000 0000 0000
    00000a78: 0000 0000 0000 0000 0000 0000 0000 0000
    00000a88: 0000 0000 0000 0000 0000 0000 0000 0000
    00000a98: 0000 0000 0000 0000 0000 0000 0000 0000

Вот он! Мы видим символ 'H' в позиции 0x0A28 (2600 в
десятичной):

  - 0x00 (%00000000) - первая строка
  - 0x22 (%00100010) - вторая строка
  - 0x22 (%00100010) - третья строка
  - 0x22 (%00100010) - четвертая строка
  - 0x3E (%00111110) - пятая строка (середина символа 'H')
  - 0x22 (%00100010) - шестая строка
  - 0x22 (%00100010) - седьмая строка
  - 0x22 (%00100010) - восьмая строка

Это полностью соответствует шаблону символа 'H'! Значит, наш
подход работает."

То есть модель переклинило, и она стала ожидать, что строки символа на экране лежат в соседних байтах. (Иногда переклинивало и до одного байта на символ.)

После всех этих обломов мы начали что-то подозревать и предложили Qwen'у задачу попроще: рассчитать адрес на спектрумовском экране строчкой выше. Произошло то же самое: после многих запросов и уточнений, включая предложенные численные примеры, он не прекращал выдавать нерабочие варианты и в итоге ничего не написал (справедливости ради стоит сказать, что расчёт экранного адреса по координатам он однажды реализовал правильно, в рамках проекта со стрелочкой).

Задали задачу ещё проще: имея процессор с одним регистром и одним флагом переноса, командами MOV [ADDR],A, SUB A,[ADDR], AND A,[ADDR], JNC ADDR, реализовать загрузку значения в регистр A из памяти по адресу ADDR. Qwen решил её в 6 команд (как большинство людей), после подсказок смог сократить до 5 и был уверен, что дальше нельзя, а правильное решение в 3 команды он нашёл только после того, как ему сообщили, что оно точно существует.

Поскольку снапшот оказался несъедобным для самоконтроля, мы решили добавить к нему ещё и автоматически сгенерированный скриншот. И тут оказалось, что Qwen в режиме coder-model не умеет читать картинки. Через тот же интерфейс доступен режим vision-model, которая картинки читает и достаточно верно описывает. Но переключение режимов на лету ничего не даёт. Более того, сам Qwen говорит, что переключения просто не произошло. Но он сообщил, что можно передать команду другому инстансу Qwen, чтобы он мог вернуть описание картинки. И тут произошёл окончательный облом: файл, имя которого передано через внешнюю команду, просто не читается. Вероятно, это какая-то защита (хотя было бы что защищать - Qwen свободно редактирует и запускает файлы по всему компьютеру). В итоге отправили багрепорт и стали ждать.

Также попробовали разные модели в LM Studio и Ollama, но ничего не получилось. Первая не работает у меня дома, потому что у меня не тот процессор. На работе она ставится (в частности, там работает Qwen ), но модель с анализом графики выдаёт поток случайных символов в качестве ответа. Ollama работает дома, но на самом простом чате GPT съедает 13 гигабайт ОЗУ, так что я даже не могу запускать её одновременно с браузером. Пробовал тоже ставить там разные графические модели, но вместо правильного описания они пишут фантазии на тему названия файла, то есть просто не считывают его содержимое.

Между делом я проверил другие бредогенераторы в качестве программистов на ассемблере. Начал с GLM 4.6, поверив рекламе на Ютюбе.

Проверяю "напиши подпрограмму на ассемблере Z80 для компьютера ZX Spectrum, которая по заданному экранному адресу (в регистре HL) находит экранный адрес на пиксель выше и возвращает его в HL. Проверки на окончание экрана не нужны."

Задача провалена. Адресацию экрана он знает. но математика неверна:

GetPixelAbove:
   ld a, h
   and %00111000
   jp z, IsTopOfChar
   ld de, -32
   add hl, de
   ret
IsTopOfChar:
   ld de, -1824
   add hl, de
   ret

"Это не работает. внутри блока из 8 строк адреса соседних строк различаются на 256, а не на 32" - он сделал процедуру из 2 строк "dec h:ret".

"Теперь ты забыл обработать случай перехода на другой блок из 8 строк" - он опять написал "dec h:ret".

"Нет. для Y=8, X=0 адрес равен $4020. строчкой выше - адрес $4700. проверь также случай Y=64, X=0: адрес равен $4800, строчкой выше - адрес $47e0." - он продолжает настаивать.

"Формула addr = 16384 + (Y_символ 2048) + (Y_пиксель 256) + (X_символ 32) + X_пиксель неверна, где ты её взял? Правильная формула: addr = 16384 + (Y_треть_экрана 2048) + (Y_символ_внутри_трети_экрана 32) + (Y_пиксель 256) + (X_символ)." - он согласился, что наврал с формулой, но мою считает тоже неправильной. Процитировал процедуру Jonathan'а Cauldwell'а, но для него она тоже "неправильная". И вернулся к первой версии, которая не работает.

"Процитированная процедура работает правильно:

; Move one pixel up
; HL = screen address
; Carry set if we've gone off the top of the screen

(этот комментарий неверен)

up_one_pixel:
       ld      a, h
       and     $07
       jr      nz, not_new_char
       ld      a, h
       sub     $08
       ld      h, a
       ld      a, l
       add     a, $E0    ; -32
       ld      l, a
       ret     c
       inc     h
       ret
not_new_char:
       dec     h
       ret

а твоя работает неправильно. Попробуй понять, почему ты ошибся. ld a, l / and 7 не выдаёт биты Y_пиксель. Биты Y_пиксель можно получить по ld a,h / and 7.
Кстати, процитированная процедура - явно не самый короткий вариант. Попробуй начать с ld a,h / dec h / and $07." - тогда он наконец согласился, что был неправ, и сгенерировал очередную неработающую процедуру.

GetPixelAbove:
   ; Сначала проверяем старшую часть Y_пиксель (биты 2,1,0 в H)
   ld a, h
   and %00000111
   jp nz, SimpleCase ; Если не ноль, то Y_пиксель точно > 0

   ; Если здесь, то старшая часть Y_пиксель = 0.
   ; Проверяем младшую часть Y_пиксель (бит 5 в L)
   ld a, l
   and %00100000
   jp nz, SimpleCase ; Если не ноль, то Y_пиксель тоже > 0

   ; --- СЛОЖНЫЙ СЛУЧАЙ: Y_пиксель = 0 ---
   ; Переход на предыдущий символьный блок и на его 7-ю
пиксельную строку.
   ; Смещение: -1824
   ld de, -1824
   add hl, de
   ret

SimpleCase:
   ; --- ПРОСТОЙ СЛУЧАЙ: Y_пиксель > 0 ---
   ; Переход на предыдущую пиксельную строку внутри блока.
   ; Смещение: -32
   ld de, -32
   add hl, de
   ret

Пишет "Как мы установили, проверка Y_пиксель = 0 для стандартного экрана нетривиальна. Она равна (H & 7 == 0) И (L & %00100000 == 0)."

"Во-первых, проверка Y_пиксель = 0 для стандартного экрана не равна (H & 7 == 0) И (L & %00100000 == 0). Она равна (H & 7 == 0). Во-вторых, SimpleCase у тебя опять смещает на -32 (такое смещение бывает только между третями), а обычно должен на -256. Давай напишу тебе нагляднее, как расположены биты адреса на стандартном спектрумовском экране: %010TTyyy YYYXXXXX, где TT - номер трети, YYYyyy - остальные биты Y-координаты, XXXXX - X-координата в знакоместах." - заявил, что "теперь понял", и сгенерировал опять бред:

GetPixelAbove:
   ; Проверяем, находимся ли мы на верхней границе символьной
строки (Y_пиксель = 0)
   ; Биты Y_пиксель (YYY) находятся в старших трех битах
регистра L.
   ld a, l          ; Загружаем младший байт адреса
   and %11100000    ; Маскируем биты 7, 6, 5 (оставляем только
Y_пиксель)
   jp nz, NotTopOfChar ; Если результат не ноль, значит
Y_пиксель был > 0.

   ; --- СЛУЧАЙ 2: Y-пиксель = 0 ---
   ; Мы на самой верхней строке символа. Нужно перейти на
предыдущий
   ; символьный блок и на его 7-ю пиксельную строку.
   ; Итоговое смещение: -1824.
   ld de, -1824     ; Загружаем в DE смещение -1824
   add hl, de       ; Применяем смещение
   ret              ; Возвращаемся из подпрограммы

NotTopOfChar:
   ; --- СЛУЧАЙ 1: Y-пиксель > 0 ---
   ; Мы просто переходим на предыдущую строку пикселей внутри
того же символьного блока.
   ; Это означает вычитание 32.
   ld de, -32       ; Загружаем в DE смещение -32
   add hl, de       ; Вычитаем 32 из адреса в HL
   ret              ; Возвращаемся из подпрограммы

"Проверь свою процедуру на моей формуле в случаях Y=1, Y=8, Y=64. Похоже, в случае Y=1 (адрес $4100, результат должен быть $4000) она работает неправильно." - ответил той же идиотской процедурой, что Qwen (ld de,-32:add hl,de:ret). И аргументирует "Как видите, во всех случаях, кроме самого верха экрана (Y=0), смещение на пиксель вверх всегда равно -32. Это и есть гениальность и причудливость экрана Spectrum. Движение вниз по экрану сложное (сначала -32 7 раз, потом большой прыжок, и снова по кругу). А вот движение вверх на одну строку это всегда простое вычитание 32."

ЧEРТОВ ГЕНИЙ!

"Дана следующая система команд:

MOV [ADDR], A
SUB A, [ADDR]
AND A, [ADDR]
JNC ADDR

где A - единственный регистр
ADDR - указанный адрес в используемой области памяти
JNC - переход, если флаг переноса равен 0
SUB - влияет на состояние флага переноса
AND - сбрасывает состояние флага переноса

Необходимо написать последовательность команд загрузки значения в регистр A из памяти по адресу ADDR"

Выдал:

SUB A, [ADDR]
AND A, [ADDR]
MOV [ADDR], A
SUB A, [ADDR]

То есть он не осилил даже первое решение в 6 строк.

Через сайт LM Arena (там каждый раз запускается случайная модель) я проверил штук 15 других моделей на двух задачах: поиск адреса выше и сдвиг экрана на 4 пикселя влево. Из китайских перспективна только Ernie. Обе задачи смог решить только Claude. Возможно, Grok выдал что-то рабочее, но это был очень жирный код, и я его не проверял. Claude также имел самое высокое место во внутреннем рейтинге. Но он платный и в России недоступен.

Firestarter смог получить временный доступ к Claude и сгенерировать прототип игры для ZX Spectrum. Но работу получилось организовать только через мегапромпт размером порядка 30 килобайт, описывающий всю игру, с несколькими дополнительными ссылками внутри. Удивительно, но Claude умеет проверять правильность генерируемого кода без эмулятора (тот же промпт в Qwen выдаёт абсолютно нерабочую программу). Но и построить процесс путём постепенного просмотра и изменения полученного кода пока не вышло.

Qwen бесплатный. Он умеет генерировать планы работ на основе анализа исходника. Но сам он не может выполнить эти работы, кроме самых простейших. Когда попробуете его использовать, обратите внимание, что он может править там, где не просят, даже игнорирует строки с надписью "don't touch!".

Во время работы в рамках LM Studio съедает 4 гигабайта ОЗУ и пару ядер, скорость порядка 20 токенов в секунду на i7-14700, 2.11 GHz. На старых компах (а также на компах с процессором AMD) LM Studio может не работать, потому что её движок требует поддержки какого-то расширения системы команд. Но работает Ollama, на неё легко ставится GPT. Эксперименты проводились именно с qwen3-4b-thinking-2507. Его возможности как текстового инструмента с одной стороны велики, но с другой - много ошибок, которые он не замечает:

"перечисли страны Евросоюза" - перечисляет 20 стран из 27, много
раз исправляет список. "вспомни другие страны Европы и проверь,
какие из них входят в Евросоюз" - не помогает (при этом
вспоминает про Крым и пишет, что он входит в Россию). Если
дальше спросить "что ты знаешь про такие страны, как Литва,
Андорра, Монако, Греция, Албания, Хорватия, Босния и
Герцеговина, Черногория, Северная Македония, Сербия, Косово,
Лихтенштейн, Сан-Марино, Ватикан, Норвегия, Кипр, Мальта?", то
он отвечает про каждую (причём Косово "не признано"). Если после
этого "перечисли страны Евросоюза, которые теперь нам известны,
но не думай много раз" - уточнение не помогает, всё равно
крутится долго, не вспоминает Кипр, Литву, Грецию и Хорватию, а
по дороге статус Боснии и Герцеговины меняется с "не входит" на
"входит" (это неправда). Болгарию не вспоминает ни разу. Если
напомнить про Кипр и Болгарию, то добавляет Болгарию, но
отказывается включать Кипр.

В области лингвистики: есть знание английского, русского,
греческого, немецкого, французского, итальянского, испанского,
польского, чешского, болгарского, латинского, китайского,
японского, отрывочно литовского, отрывочно фарси (таджикский
пытается реконструировать по фарси). Не знает старославянский и
осетинский, даже их алфавиты. Неправильно переводит слово
"птица" на литовский, фарси и таджикский (хотя знает слово). Не
знает Трубачёва и ЭССЯ, Лубоцкого и лейденскую серию словарей.
На латинский язык переводит с ошибками (как минимум неправильное
спряжение некоторых глаголов), хотя в целом его знает.

"твоя роль - лингвист. перед тобой текст "съешь ещё этих мягких
французских булок да выпей чаю". расставь внутри этого текста 0
в моменты выключения голоса и 1 в моменты включения голоса." -
полностью проваливает этот тест.

Знает отдельные фрагменты из стихов на разных языках, но ничего
не знает полностью, не знает даже надёжные первые строчки (т. е.
как кот учёный в книге "Понедельник начинается в субботу" ).
Братьев Стругацких он тоже не знает.

Не знает корпорацию "Тактическое ракетное вооружение" и
абсолютно уверен, что её не существует.

Не знает о своём устройстве и функционале.

Не знает, что конкретно он знает и чего не знает.

Может служить помощником для придумывания русских терминов
взамен иностранных, хотя большинство его идей бредовые. Может
планировать работы по программированию, но сам программировать
не может, потому что постоянно генерирует нерабочий код, а
проверять его не умеет (огромное различие с Claude ).
Других применений пока не нашёл.

Среди прочего - не удалось с помощью Qwen выкачать список
изменений в одной конкретной статье Википедии. Вероятно, он мог
бы написать скрипт на каком-то из языков, на которых хорошо
обучен (но он не сообщает, на чём он обучен) и запустить его. Но
он даже не предложил этот вариант, а просто выдумывал
несуществующие изменения.

Эксперименты продолжаются. Пишите о своих достижениях и методах.

                        * * *

Хотя Спектрум существует десятилетиями, многие важные
эксперименты с железом до сих пор не проведены.

Интересует следующая информация:

  • результаты эксперимента по включению тестового режима ULA (64x
    speed) на живой резинке (емнип замыкание MREQ на какую-то
    адресную линию, чтобы было MREQ и IORQ одновременно), можно ли
    получить практически юзабельный скролл?
  • замеры реальной громкости (не напряжений, как раньше) на AY в
    +2
  • на Timex включение hires после формирования color burst, чтобы
    делать фейковые цвета, как на Apple II и CGA
  • отличие уровня цветности на разных цветовых сетках на 48 и
    +2 - сравнение
  • насколько часто на фирменных машинах встречается "яркий
    чёрный" цвет (видел только в одном ролике про Timex)
  • насколько большой разброс уровней яркостей (bright/nobright)
    по цветовым тонам и по моделям
  • осциллограммы типичного отклика клавиши 48K (резиновая) и ZX
    Spectrum + (плёночная), сравнение с российскими клавиатурами
    (плёночная, героновая).
  • поведение AY после сигнала RESET - сбрасываются ли счётчики и
    фазы тональника, огибающей и шума. Т.е. можно ли сделать
    предсказуемые тембры? (на реале это можно сделать только на ATM2
    и Scorpion (если замкнуть RESET проводком на один из битов
    портов AY), т.к. нужен резидент, который после сброса запустит
    плeйер.)

Поделитесь вашим мнением о статье