Pull to refresh

Почему Pinky и Inky ведут себя по-другому, когда Pac-Man двигается вверх?

Reading time4 min
Views4.2K
Original author: Don Hodges
В игре Pac-Man (и во многих клонах и продолжениях), было установлено, что привидения Pinky и Inky преследуют Pac-Man´а, определяя точку в которую следовать с учётом направления, в котором он движется. Например, Pinky обычно следует к точке, которая расположена в четырёх единицах от Pac-Man´а по направлению его движения. Однако, если Pac-Man двигается вверх, этой точкой назначения становится точка, расположенная на четыре единицы вверх и четыре единицы влево относительно Pac-Man´а. Привидение Inky имеет похожее поведение, когда Pac-Man двигается вверх. Почему же Pinky и Inky имеют различное поведение, когда Pac-Man двигается вверх?

Вкратце, по моему мнению, из-за программной ошибки. Вот доказательства.



Pac-Man кодирует направления как минимум двумя способами. Один способ, которым Pac-Man записывает направления используя один байт со следующими значениями: Вправо = 0, Вниз = 1, Влево = 2 и Вверх = 3.

Другой способ, это когда Pac-Man записывает направления, используя двухбайтовую пару, чтобы записать вектор направления следующим способом. Все значения шестнадцетиричные значения записываются с префиксом #, и могут быть просмотрены в ячейках памяти с адреса #32FF по #3306.

Вправо = (#FF, 00)

Вниз = (00, 01)

Влево = (01, 00)

Вверх = (00, #FF)

#FF (десятичное 255) используется как -1, для 8-битной математики Z80. Рассмотрим пример:



Координаты точек (XX, YY) даны в шестнадцетиричной нотации. Пусть Pac-Man в точке (#26, #2F).

Игра вычисляет положения четырёх точек окружающих данную, прибавляя двухбайтный вектор направления к координатам данной точки.

Чтобы вычислить положение точки справа от Pac-Man´а, надо добавить двухбайтное число «Вправо» (#FF,00) к (#26,#2F). Так как FF используется как -1, получаем координаты точки (#25,#2F), которая расположена справа от Pac-Man´а. Чтобы вычислить точку снизу от Pac-Man´а, надо добавить двухбайтное число «Вниз» (00,01) к (#26,#2F). Получим (#26,#30), точку, которая расположена непосредственно под Pac-Man´ом. Чтобы вычислить положение точки слева от Pac-Man´а, надо добавить двухбайтное число «Влево» (01,00) к (#26,#2F). Получим (#27,#2F), точку, которая расположена слева от Pac-Man´а. Чтобы вычислить положение точки над Pac-Man´ом, надо добавить двухбайтное число «Вверх» (00,#FF) к (#26,#2F). Так как FF используется как -1, получаем координаты точки (#26,#2E), которая расположена непосредственно над Pac-Man´ом.

Всё это работает правильно только когда мы работаем с единичными координатами по-отдельности, что является единственным корректным способом выполнять эти арифметические действия, с учётом наличия возможных отрицательных чисел. Например, в коде по адресу #2000, расположена процедура, которая выполняет эти действия правильно:

2000 FD7E00     LD  A,(IY+#00)  ; загрузить Y-координату в регистр A
2003 DD8600     ADD A,(IX+#00)  ; добавить  Y-координату вектора смещения
2006 6F         LD  L,A         ; сохранить результат в L
2007 FD7E01     LD  A,(IY+#01)  ; загрузить X-координату в регистр A
200A DD8601     ADD A,(IX+#01)  ; добавить  X-координату вектора смещения
200D 67         LD  H,A         ; сохранить результат в H
200E C9         RET             ; возврат


Эта процедура используется для того, чтобы загрузить в двухбайтовую регистровую пару HL новое положение по данному исходному положению, указанному в регистре IY, и вектору направления, указанному в регистре IX. Она работает правильно, потому что сложение координат X и Y выполняется раздельно.

Проблема возникает, когда игра пытается вычислить, например, целевую точку для Pinky. Код игры определяет цель Pinky, как точку в четыре единицы от Pac-Man´а, относительно направления его движения.

Чтобы вычислить эту точку, код берёт вектор направления Pac-Man´а и добавляет его к самому себе, тем самым удваивая его. Затем он удваивает получившийся результат, получая значение в четыре раза больше, чем первоначальное. Проблема в том, что координаты X и Y не складываются по-отдельности, они складываются одновременно. Баг проявляется когда вычисляется вектор направления «Вверх». Вот выдержка из ассемблерного кода Pac-Man´а для Z80, код, который содержит код, выделен:

; Pac-Man Pinky targeting subroutine

278E ED5B394D   LD  DE,(#4D39)  ; загрузить в DE положение Pac-Man´а
2792 2A1C4D     LD  HL,(#4D1C)  ; загрузить в HL вектор направления Pac-Man´а
2795 29         ADD HL, HL      ; [баг!!!]удвоить вектор направления Pac-Man´а
2796 29         ADD HL, HL      ; [баг!!!]учетверить вектор направления Pac-Man´а
2797 19         ADD HL, DE      ; [баг!!!]добавить результат к положению, чтобы получить целевую точку


Следующая таблица покажет результаты выполнения этой процедуры (относительно нулевого положения Pac-Man´а):
Имя вектора Первоначальное значение HL и результат 2xHL и результат 4xHL и результат
Вправо #FF00 (-1,0) #FE00 (-2,0) #FC00 (-4,0)
Вниз #0001 (0,1) #0002 (0,2) #0004 (0,4)
Влево #0100 (1,0) #0200 (2,0) #0400 (4,0)
Вверх #00FF (1,-1) #01FE (2,-2) #03FC (4,-4)


HL это двухбайтовая регистровая пара Z80. В строке #2792, в H загружается координата X вектора, в L загружается координата Y вектора.

Когда обрабатывается вектор для направления «Вправо», HL содержит #FF00. Инструкция в строке #2792 складывает это значение само с собой, после чего регистровая пара HL содержит #FE00, при этом устанавливается флаг переноса, в связи с переполнением регистра H. Но программа игнорирует флаг переноса и использует только числовое значение, которое интерпретируется как -2. Когда происходит удвоение в строке #2796, в HL оказывается значение #FC00, что является правильным значением -4. Флаг переноса игнорируется. Когда происходит сложение с положением Pac-Man´а, флаг переноса снова устанавливается в результате переноса, что в прочем, снова игнорируется программой. Желаемый результат достигнут: новая точка расположена в 4 единицы справа от Pac-Man´а.

Баг в действии



Однако, когда используется вектор для направления «Вверх», в строке #2792 в регистровую пару HL загружается #00FF. Когда на следующей строке происходит удвоение, HL содержит #01FE. Вместо того, чтобы отбросить флаг переполнения, как это происходило при обработке вектора для направления «Вправо», удвоение координаты Y приводит к переполнению с переносом флага переноса в координату X. Это приводит к порче вектора, так как вектор смещает не только вверх, но и влево. Следующее удвоение даёт значение #03FC, которое будучи добавленным к координатам Pac-Man´а приводит к получению новой точки 4 единицы вверх и влево.



Я полагаю, что это баг, и он не намеренный, потому что одна и та же команда используется для всех четырёх направлений. Другими словами, код не запускает отдельную процедуру для обработки направления «Вверх», тем не менее для направления «Вверх» процедура даёт неожиданный, отличный от других направлений результат. Учитывая низкоуровневую природу проблемы, я сомневаюсь, что программисты были в курсе этого. Если они это знали, то, видимо, проигнорировали эту проблему. Тем не менее, я сомневаюсь, что они оставили бы этот баг, если бы знали о нём.
Tags:
Hubs:
Total votes 56: ↑49 and ↓7+42
Comments7

Articles