Chuyển game Amiga 1993 của tôi sang Godot, với LLM đọc assembly 68000
Một lập trình viên đã dùng AI để chuyển toàn bộ game Babylon Twins viết bằng assembly 68000 từ năm 1993 sang nền tảng Godot hiện đại. Quá trình port nhanh đến mức chỉ mất một buổi tối cho bản C++ 34.000 dòng, và vài buổi tối cuối tuần cho bản assembly 72.758 dòng. Bài viết chia sẻ chi tiết kỹ thuật, những lỗi sai thú vị từ AI và cách hồi sinh một tựa game cổ điển.

Chuyển game Amiga 1993 của tôi sang Godot, với LLM đọc assembly 68000
Năm 1993, tại Baghdad, tôi tạo ra tựa game Babylonian Twins trên máy Amiga 500 — một cỗ máy chỉ có 512KB RAM, không ổ cứng, cắm thẳng vào TV. Toàn bộ game được viết tay bằng assembly 68000, từng sprite và từng dòng quét được xử lý thủ công. Ba thập kỷ sau, tôi để AI đọc lại toàn bộ mã nguồn assembly và chuyển nó sang Godot. Kết quả khiến tôi kinh ngạc.
Quá trình port không chỉ thành công mà còn hé lộ những chi tiết kỹ thuật sâu xa mà ngay cả tác giả cũng không nhận ra trong suốt 33 năm.
Vì sao tôi thử lại lần nữa?
Khoảng một năm trước, tôi từng đưa cho một mô hình AI cũ các file binary level của game và yêu cầu giải mã chúng. Phải mất nhiều vòng lặp và rất nhiều gợi ý từ tôi mới hoàn thành.
Sau đó, Claude Fable 5 ra mắt, và tôi đưa cho nó cùng những file đó. Bài kiểm tra được thiết kế có chủ đích: tôi tin rằng có rất ít mã assembly Amiga trong bộ dữ liệu huấn luyện của LLM. Nếu mô hình mới giỏi tự suy luận hơn là ghi nhớ, đây sẽ là nơi thể hiện rõ nhất.
Kế hoạch ba bước được vạch ra, mỗi bước phụ thuộc vào bước trước:
- Bước an toàn: Chuyển engine C++ 34.000 dòng tôi viết năm 2010 sang Godot 4 — đây là phần đối chứng.
- Bước bất công: Chuyển 72.758 dòng assembly 68000 gốc, dành cho một cỗ máy đã ngừng sản xuất, không có chú thích đáng kể, sang Godot — giữ nguyên tốc độ 50Hz gốc.
- Bước tham lam: Nhúng game 1993 vào bên trong bản hiện đại, để ai mua game mới cũng có thể chơi bản gốc.
Cả ba đều hoạt động. Định dạng level mà năm trước phải mất nhiều vòng sửa lỗi với tôi, giờ đây được giải mã trong một lần duy nhất, không cần gợi ý nào.
Cách AI vận hành
Tôi chạy nó trong Claude Code với quyền truy cập terminal và file system. AI có thể chỉnh sửa file, chạy assembler, build game, khởi chạy và đọc kết quả trả về. Khi nói nó "build lại binary 1993 và kiểm tra", điều đó có nghĩa là nó tự chạy vasm và so sánh kết quả bằng diff.
Từ rất sớm, nó thêm một loạt cờ dòng lệnh vào game để có thể tự chơi mà không cần tôi:
--level=để tải trực tiếp một level--pose=để đặt hai nhân vật vào vị trí chính xác--drive=để nhấn nút theo kịch bản từng frame--probeđể đổ trạng thái các công tắc/cửa/cổng/chìa khóa--screenshot=để render một frame rồi thoát
Điều này biến câu hỏi "cảm giác nhảy có đúng không" thành thứ máy móc có thể đọc được:
drive[btw_jump:2.2] pos=(25.44, 24.04) vel=(0.00, -14.51) ground=false apex_y=22.48
Ngoài ra còn có hai bài kiểm tra headless chạy trước khi hiển thị bất cứ thứ gì cho tôi: một bài biên dịch mọi script, một bài build mọi level và báo lỗi. Về phía Amiga, nó điều khiển chuỗi công cụ thật — vasm để assemble và FS-UAE để khởi động kết quả.
Bước một: 34.000 dòng C++ trong một buổi tối
Đêm thứ Tư, nhiệm vụ an toàn. Nhật ký thời gian (không chỉnh sửa):
- 22:23 — Khung Godot 4, đồng bộ asset, pipeline level TMX
- 22:44 — Cả hai nhân vật chính có thể chơi: va chạm, vật lý, camera, chuyển đổi
- 23:19 — Toàn bộ 38 loại entity được port — đầy đủ các đối tượng hoạt động
- 00:35 — Toàn bộ luồng màn hình: menu, map, câu chuyện, lưu game
- 02:15 — Xuất ra macOS, iOS và Android
21 phút từ dự án trống đến nhân vật có thể chơi được. Mỗi dòng nó chuyển trong đêm đó là một dòng tôi đã viết, qua nhiều tháng, vào năm 2010. Tôi đi ngủ trong sự bối rối.
Phải mất khoảng ba ngày sau đó để làm cho cảm giác chơi đúng: quỹ đạo nhảy, thời gian trampoline, và hệ thống phát hiện va chạm thưởng cho việc bấm nút liên tục. Tôi không thử một mình. Con trai 13 tuổi của tôi chơi mọi bản build cùng tôi. Nó luôn biết cha mình tạo ra game này, nhưng chưa từng thấy tôi làm việc trên đó. Việc thử nghiệm biến thành một trải nghiệm cha con mà tôi không lên kế hoạch, và đó là một trong những phần tôi yêu thích nhất của toàn bộ dự án.
Cùng đơn vị, cùng nhịp tick
Toàn bộ trạng thái gameplay nằm trong đơn vị tile (1.0 = một tile 48px), và vòng lặp cập nhật chạy ở tần số cố định 60Hz, vì bản iOS 2010 chạy ở 60Hz. Điều này quan trọng vì game gốc áp dụng lực cản nhân lên mỗi frame:
static const float GROUND_DRAG_FACTOR = 0.85f;
this->velocity.x *= GROUND_DRAG_FACTOR; // mỗi tick!
Nhân với 0.85 sáu mươi lần mỗi giây cho ra một lượng ma sát; nhân năm mươi lần mỗi giây cho ra lượng khác. Port sang tần số tick khác sẽ thay đổi mọi đường cong gia tốc trong game. Không có gì sụp đổ, nhưng cảm giác sẽ sai mãi, và bạn sẽ không tìm ra bằng cách đọc diff. Ở 60Hz, hằng số này được cấy nguyên văn. Đây cũng là lý do bản rebuild 1993 chạy ở 50Hz còn bản hiện đại chạy ở 60Hz: hai bộ số được tinh chỉnh tay, mỗi bộ chỉ đúng ở tần số tick riêng của nó.
Không dùng CharacterBody2D
Godot đi kèm CharacterBody2D và move_and_slide(), mọi tutorial đều bảo bạn dùng chúng. Nhưng bản port không dùng cái nào cho nhân vật chính. Game gốc có hệ thống di chuyển viết tay riêng, và xây dựng lại trên vật lý của người khác sẽ tạo cảm giác sai lệch khó truy tìm. Nhân vật chính là một Node2D đơn giản, và routine va chạm 150 dòng được chuyển nguyên văn, kể cả những con số "chêm" tôi chọn theo cảm giác 15 năm trước và các chú thích gửi cho tương lai:
# Cộng 0.5 vì muốn chân nhân vật nằm giữa tile.
var bottom := pos.y + dim.y / 2 + 0.5 + i + fraction
if int(bottom) == int(pos.y + dim.y / 2 + 0.49):
continue
var right := pos.x
var left := pos.x - dim.x / 4 # mẫu dò không đối xứng!
Không có gì dọn dẹp con số 0.49 lạc lõng kia. Không có test, không có tài liệu — những chú thích đó chính là đặc tả.
Bước hai: Assembly 68000
Đến chiều Chủ nhật, 5 tháng 7, tôi giao thứ thực sự muốn kiểm tra: 72.758 dòng trên 26 file, viết cho máy có 512KB bộ nhớ, bởi tôi, cho tôi, với thói quen chú thích của người không bao giờ nghĩ sẽ có người khác đọc. Không tài liệu. Một lần chuyển sang lưu trữ hiện đại năm 2008 đã làm ngắn mọi tên file dài, khiến mọi include trỏ đến tên không còn tồn tại. Một trong năm file nguồn level bị cắt giữa chừng trong bảng dữ liệu. Không có bản sao nào khác.
Trước khi port bất cứ thứ gì, AI làm cho mã nguồn 1993 assemble lại được, dùng vasm trên Mac Apple Silicon, và tiếp tục cho đến khi đầu ra giống hệt từng byte với binary đã phát hành:
- 14:34 — Nhập mã nguồn Amiga, asset, tài liệu tham khảo
- 14:49 — Toolchain vasm tái tạo binary phát hành giống hệt từng byte
- 15:20 — Đĩa ảo được build lại
- 15:42 — Bản demo đã build khởi động và chạy trong FS-UAE
15 phút từ một thư mục file đến lần rebuild đầu tiên khớp với byte đã phát hành. Có một chi tiết quan trọng: tôi viết mã bằng ASM-One, có phương ngữ khác với vasm ở mức thay đổi byte đầu ra. ASM-One mã hóa cmp #4,d0 thành CMPI, vasm chọn một mã hóa khác nhưng cũng hợp lệ. Vì vậy, chỉ bảo nó đừng tối ưu hóa là chưa đủ. Thay vì sửa trực tiếp nguồn của tôi, AI viết một bước tiền xử lý để bắc cầu năm khác biệt như vậy, và dựng lại ánh xạ tên file bị hỏng từng file một.
Điểm đắt giá nhất là lệnh org. Không có linker, không có relocation, mã level tự bố trí bộ nhớ Amiga bằng tay, từng địa chỉ:
org $6a000 ; phần này nằm ở địa chỉ $6a000
Mapadd:
incbin "btwins:binary/L1/Map1.b" ; bản đồ game
org mapadd+73*1024 ; nhảy đến 73KB sau vị trí bắt đầu map
GLBtable:
dc.w $3333,50,20,100 ; một bản ghi đối tượng bắt đầu
dc.w SahamR-grb,26 ; routine, chiều dài
...
org glbtable+2*1024 ; bảng đối tượng có chính xác 2KB
Bản đồ level một sử dụng 74.400 trong số 74.752 byte đó — dôi ra 352 byte, và không gì kiểm tra điều đó ngoài tôi vào năm 1993. Điều thú vị: SahamR-grb gắn hành vi của đối tượng dưới dạng offset có tên — saham là tiếng Ả Rập nghĩa là mũi tên.
Lệnh org của ASM-One cũng có thể di chuyển con trỏ vị trí lùi lại, điều mà vasm không làm được. Cách xử lý đầu tiên mắc lỗi với một trường hợp: ds.b 800 trong một khối quay lui — ASM-One coi là "bỏ qua 800 byte", nhưng vasm viết ra 800 byte số 0. Mọi thứ sau điểm đó trong file, bao gồm cả copper list, nằm lệch 944 byte so với binary đã phát hành. Game assemble và khởi động được, nhưng vẽ sai thứ.
Ngay cả sau đó, một số khối vẫn không khớp, lệch khoảng 108 byte rải rác trong vùng biến số. Chính những byte này giải thích nguồn gốc của các file đã phát hành. ASM-One assemble vào bộ nhớ, và game lên đĩa bằng cách lưu bộ nhớ đó ra — sau khi game đã chạy. Nói cách khác, các file phát hành là ảnh chụp nhanh của một game đã chạy, không phải đầu ra assembler sạch. Một lần assemble mới có số 0 trong các biến này vì chưa có gì gán giá trị; đĩa phát hành có giá trị chúng giữ trên máy tại thời điểm lưu. Mã nguồn ghi chúng trước khi đọc, nên các số 0 hoàn toàn vô hại.
Lúc đó tôi đọc dòng này, gật gù, và chờ đợi game thực sự chạy. Phải vài tuần sau tôi mới nhận ra đây chính là điều quan trọng nhất trong toàn bộ dự án, và không ai yêu cầu nó làm việc này. Từ thời điểm đó, mọi tuyên bố về game đều có thể xác minh bằng cách so sánh byte. Tôi sẽ không tự làm việc này. Tôi đã có binary, và trong 18 năm, việc build lại chúng từ mã nguồn chưa bao giờ đáng giá một buổi chiều.
Giải mã các định dạng game
Với mọi định dạng, AI đi thẳng vào mã đọc byte và suy ngược lại từ đó. Level loader là 1.652 dòng assembly 68000 không chú thích — đó là lý do tôi luôn dùng hex editor thay vì đọc mã.
Level — bản đồ gạch
Một level là lưới các tile: danh sách dài các số, mỗi số nghĩa là "đặt hình 47 ở đây" — định dạng riêng tư của tôi năm 1993. Input là danh sách số không có header, không có kích thước, nằm trong một khối nén. Lần này tôi không giải thích gì cả.
Toàn bộ 256 tile dùng để xây level một
AI tìm ra routine vẽ, đọc cách lưới được duyệt, suy ra chiều rộng và chiều cao từ các hằng số trong file, và tạo ra bản đồ chính xác cho cả năm level ngay lần đầu tiên.
Một lát cắt level một, ráp từ các tile trên
Sau đó, nó render lại từng level từ dữ liệu tự trích xuất và so sánh pixel-by-pixel với ảnh chụp toàn level tôi chụp năm 2020. Nơi nào không khớp, nó tìm nguyên nhân và phát hiện hai hiệu ứng copper: gradient bầu trời và chu kỳ đổi màu nước. Khi đã tính cả hai: năm ảnh toàn level, không một pixel nào khác biệt. Riêng level một rộng 600 tile, tức 9.600 pixel.
Thuộc tính ô bản đồ
Vẽ level mới chỉ là một nửa công việc. Mỗi ô là một từ 16-bit, và hình ảnh chỉ là phần nhỏ hơn của nó:
một ô bản đồ, 16 bit:
bits 15..10 thuộc tính: ô này LÀM GÌ (6 bit)
bit 8 dùng bank tile nào (1 bit)
bits 7..0 vẽ tile hình nào trong 256 (8 bit)
Thuộc tính chính là "vật lý vô hình" của level. 1 là đất cứng. 2 và 3 có thể trèo. 10 đến 13 đều nghĩa là "gây sát thương", bốn mã vì knockback cần hướng. 14 giết ngay lập tức. 63 là cửa. Không có gì trong số này được ghi lại ở đâu cả. Nó được khôi phục vì hai routine đọc cùng một từ và mỗi routine lộ ra một nửa: vòng lặp vẽ mặt nạ phần byte thấp, còn kiểm tra va chạm làm ngược lại:
move.w (a1),d6 ; cùng một ô
and.w #$fc00,d6 ; giữ 6 bit trên cùng
lsr.w #2,d6
lsr.w #8,d6 ; d6 = thuộc tính, 0..63
bsr cbCheck ; 2 hoặc 3? có thể trèo
bsr Checkrmh ; 10..13? gây đau, từ hướng nào
Checkrmh chuyển các trường hợp đau đớn cho một label tên rmhEnjury — đó là tôi năm 1993 viết sai chính tả từ "injury".
Những bit này được vẽ bằng một trình editor. Trước khi xây game, tôi phải xây công cụ xây nó: MEDITOR.S, 1.254 dòng assembly, tự ghi ngày tháng trong header, bằng tiếng Anh của tôi năm 1993:
; ***********************************************************************
; * This Program was written in four days *
; * 1993-2-8/7/6/5 *
; * I made it to help me to make a map to my first serious *
; * Game *
; ***********************************************************************
Bốn ngày trong tháng 2 năm 1993. Vẽ tile bằng chuột, chọn số thuộc tính trên bộ đếm CURRENT FLAG, đóng dấu lên ô bằng PUT FLAG, và chế độ xem flag đánh dấu mọi ô mang số đã chọn. Khi viết bài này, tôi yêu cầu AI chạy trình editor và chụp màn hình. Nó assemble mã nguồn 1993 bằng assembler hiện đại, đặt dữ liệu Level 2 đã phát hành vào bộ nhớ đúng nơi editor mong đợi, và khởi động trong trình giả lập.
Trình editor bản đồ của tôi năm 1993 đang chạy, tự chính nó sau 33 năm, đang sửa Level 2 thật
Công cụ của tôi ở tuổi 33, đang sửa Level 2 thật, chế độ xem flag bật. CURRENT FLAG hiện 0001 — đất cứng — và mặt đất có thể đứng được được đánh dấu trong khi vật trang trí đi xuyên qua thì không. Bảng điều khiển ghi 1994: artwork bảng là một file bitmap riêng editor tải lên, và bản còn sót lại là bản mới hơn mã tháng 2 năm 1993.
Cái tên còn lại trên bảng, Udai, là cộng sự của tôi ở Mesopotamia Software — cái tên chúng tôi tự đặt. Anh ấy đang xây game riêng của mình lúc đó. Tôi viết editor cho cả hai, nhưng thiết kế được chúng tôi bàn bạc để một công cụ phục vụ cả hai game. Game của anh ấy không bao giờ hoàn thành.
Bảng đối tượng
Kẻ địch không nằm trong bản đồ. Thế giới được lưu từng màn hình một, 25 tile x 20, và mỗi màn hình có một bảng nhỏ các đối tượng trên đó. Chú thích năm 1993 của tôi giải thích các marker:
; $1111 = màn hình này không có gì hoặc (Hết màn hình)
; $2222 = đây là đối tượng nhưng đừng vẽ (đã chết), đi tiếp
; khác = đây là đối tượng, vẽ nó rồi đi tiếp
Scr0: dc.w $3333,50,23,17 ; đối tượng sống: frame, rồi x, y
dc.w hiddenwallR-lrb,20 ; hành vi: một routine, dạng offset
dc.w 0
dc.w 0
dc.w 7
dc.w 10 ; tham số chỉ routine hiểu
dc.w $3333,50,12,14
dc.w GreatTR-LRb,16,GkeyT-GTT,1
dc.w $1111 ; hết màn hình này
Một kẻ địch là một hàng từ: marker, frame, vị trí trong màn hình, rồi hành vi. hiddenwallR-lrb là routine tường vỡ, gắn dưới dạng offset từ label gốc — cùng thủ thuật với kẻ ném tên. Các từ sau nó là tham số có nghĩa bất kỳ điều gì routine đó muốn. Không gì trong file nói từ nào là gì. Vì vậy AI tìm routine duyệt các bảng này mỗi frame, để nó tự đặt tên cho các trường, rồi chuyển đổi mọi đối tượng ở cả năm level sang tọa độ thế giới và kiểm tra chéo với bản đồ đã render.
Cánh cửa không nằm trong bản đồ
Tôi chắc chắn chúng nằm trong đó.
Tải bản đồ tile của một level, sẽ thấy các lỗ hổng ở đúng vị trí mọi cánh cửa, không hề có tile cửa nào, dù mở hay đóng. Một bản ghi đối tượng 18 byte đóng dấu chúng lên bản đồ lúc runtime, một cột tile 1x4, từ một bảng:
closed $528 $53C $550 $564 ; đặc, chặn đường
open $129 $13D $151 $165 ; đi qua được — dịch đúng một cột sheet sang phải
Dữ liệu bản đồ nói không có cửa. Mã level nói có. Suốt 33 năm, tôi sẽ nói với bạn rằng bản đồ là nguồn chân lý và cửa là dữ liệu bản đồ — và tôi sẽ không bao giờ tự kiểm tra. AI nắm cả hai sự thật, tìm routine dung hòa chúng, và quay lại với thiết kế thật: cửa được vẽ bằng mã lúc runtime; chúng chưa bao giờ được tô vào bản đồ trong editor. Đó là lý do bản port level dữ liệu một cách ngây thơ tạo ra một tòa tháp với những ô cửa toàn bầu trời.
Bầu trời copper
Trong mọi level, chỉ số màu 31 là bầu trời, và không gì trong art tile vẽ nó. Tile atlas render nó trong suốt, và đằng sau, copper sơn lại màu nền trên các dòng quét chọn lọc để tạo gradient dọc. Gradient nằm trong mã nguồn level như một danh sách màu đơn giản. Toàn bộ bầu trời của level hai:
backgndcol:
col1: dc.w $09FF,$09FF,$09FF,$09FF,$09EF,$09EF,$0ADF,$0ADF
dc.w $0ACF,$0ACF,$0ABF,$0BBF,$0BBF,$0CBF,$0CBF,$0DCF
dc.w $0DCF,$0ECF,$0DCF,$0DCF,$0CCF,$0CCF,$0CDF,$0CDF
Đọc xuống danh sách, bầu trời chuyển từ xanh nhạt sang ấm gần đường chân trời. Cùng 24 từ đó khi được render: bên trái là đỉnh màn hình.
Hiệu ứng bầu trời gradient của level hai, từ 24 giá trị màu copper
Bản rebuild đầu tiên bỏ lỡ hiệu ứng này, và các level trông vẫn ổn. Phẳng, theo cách tôi không gọi tên được. Phép so pixel từ chối chuyển xanh, và gradient được đưa trở lại.
Sự mơ hồ của sprite sheet
Sprite sheet Amiga là dạng planar — năm bitplane 1-bit riêng biệt dạng dải plane-major, cộng mask trong suốt — và toàn bộ được suy ra từ routine vẽ và số học org. Kích thước sheet dạng frames * width * height * 2 * 5 rất mơ hồ: con số 2 kia có thể là frame double-width, hoặc hai hàng chồng lên nhau, mỗi hàng một hướng đối diện. Cả hai cách đọc đều khớp mọi byte trong file. Hóa ra là hai hàng chồng — đó là lựa chọn của tôi năm 1993. AI phát hiện sự mơ hồ, nêu ra, và hỏi tôi.
Cùng một nhân vật, cùng sáu frame: 1993 ở trên, 2026 ở dưới
Đó là định dạng cuối cùng. Từ đó, game 1993 đi vào Godot theo cùng cách C++ đã đi: hành vi được viết lại bằng GDScript ở tần số 50Hz gốc.
Bước ba: Game cũ bên trong game mới
Yêu cầu tham lam chỉ mất một buổi tối, từ 21:58 đến 23:43. Game retro chạy như một "khách" với namespace và scene host riêng; engine chuyển sang 50Hz khi vào và quay lại 60Hz khi ra. Việc này phức tạp và hoàn thành trong một lần ngồi. Tôi đã nghĩ tính năng này sẽ ngốn một tuần và phải cắt bỏ. Chính nó là lý do phiên bản Steam đi kèm game 1993 bên trong.
Game bạn tải về không chứa mã Amiga. Dữ liệu, các khối nén, đồ họa planar và âm nhạc được giải mã một lần, trên máy của tôi, bằng script Python, thành PNG, WAV và JSON thông thường. Hành vi (cách lính gác tuần tra, khi nào cửa mở) được viết lại bằng ngôn ngữ riêng của engine. Nếu muốn bản thật, đó là file đĩa miễn phí ở cuối bài này cùng một trình giả lập.
Những chỗ AI sai
Lỗi lính gác
Trong level 2, bạn đi dọc hành lang. Thác nước bên trái, cột đá phía trước. Không có kẻ địch trên màn hình, không gì đến gần, và bạn bị trúng đòn. Thứ đánh bạn là một lính cầm giáo đứng mười ba tile phía trên, trên mỏm cỏ cạnh cây cọ, với đá đặc ở giữa.
Hắn là lính gác cửa. Hắn đẩy bất cứ ai đứng dưới chân. Trong bản gốc, kiểm tra đó có hàng rào hai bên:
sub.w d1,d4 ; d4 = khoảng cách dọc tới đứa trẻ
cmp.w #4,d4
bpl Sg.Far ; thấp hơn 4 hàng? không phải việc của tôi
cmp.w #-2,d4
bmi SG.far ; cao quá? cũng không phải việc của tôi
Bản port giữ cận dưới và bỏ cận trên. Một cú đẩy chỉ che ba hàng của lính gác giờ chạy suốt chiều cao cột bản đồ bên dưới hắn, xuyên sàn, vào hành lang mà hắn không hề xuất hiện.
Các lỗi nhỏ hơn
Mọi level có lớp tile thứ hai mà game gốc không bao giờ render: đó là artwork ẩn hiện ra khi cửa mở hoặc tường giả sụp. Render "trung thực" nghĩa là mọi lối đi bí mật mở toang từ đầu. Di chuyển kẻ địch trước người chơi thay vì sau, một cú nhảy trampoline bị đếm hai lần, bay cao hai mươi tile. Một cánh cửa ghi "p1,p2,p3,p4" — nghĩa là bốn cây cọ — bị đọc thành một chìa khóa với tên kỳ lạ, và lối ra hướng dẫn không bao giờ mở. Độ dài vòng lặp âm thanh tính theo stereo trong khi hiệu ứng là mono khiến mọi âm thanh bị cắt giữa chừng và phát lại.
Điều tốn kém nhất là một tính năng tôi yêu cầu trong bản 1993: cho hai anh em sinh đôi hoán đổi vị trí ở khoảng cách bất kỳ. Kiểm tra khoảng cách bị bỏ, và các bức tượng bắt đầu hỏng. AI quay lại routine và ra với câu trả lời thật — không phải điều tôi mong đợi: kiểm tra đó chưa bao giờ là giới hạn khoảng cách. Anh em đứng yên được đóng dấu vào bản đồ như một bức tượng, và hai bức tượng đóng chồng lên nhau sẽ ăn tile của nhau. Lính gác được đưa trở lại, và tôi hủy tính năng đó trong bản 1993.
Tôi cũng từng mắc kiểu lỗi tương tự vào năm 2010, từ từ, qua nhiều tháng.