1 คะแนน โดย GN⁺ 2025-07-02 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • บางช่วงของถังหมุนใน Donkey Kong Country 2 มีบั๊กบนอีมูเลเตอร์ SNES รุ่นเก่า ZSNES ที่ทำให้ถังยังหมุนต่อแม้ปล่อยปุ่มทิศทางแล้ว โดยสาเหตุคือยังไม่ได้ implement พฤติกรรม open bus
  • บน SNES จริง เมื่ออ่านที่อยู่ที่ไม่ได้ map ไว้ จะอ่านค่าล่าสุดบน data bus กลับมาอีกครั้ง และ DKC2 พึ่งพาคุณสมบัตินี้ในการอ่าน $2000/$2001 ของ bank $B3
  • รูทีนที่เป็นปัญหาจะ XOR ทิศทางเดิมของถังกับทิศทางใหม่ แล้วทำ and $2000; บนฮาร์ดแวร์จริง การอ่าน open bus แบบ 16 บิตนี้จะคืนค่า 0x2020 เพื่อใช้ตัดสินว่าผ่านขอบเขตของทิศทางแล้วหรือไม่
  • หากการอ่าน open bus เป็น 0 เหมือนใน ZSNES ผลลัพธ์ของ AND จะเป็น 0 เสมอ ทำให้ branch สำหรับจบการหมุนไม่ถูกเรียก และถังจะหมุนต่อไปจนกว่าจะกดทิศทางตรงข้าม
  • มีความเป็นไปได้สูงว่าคำสั่งดังกล่าวควรเป็น and #$2000; ใน ROM revision หนึ่ง หากเปลี่ยน opcode ที่ $33EDAC จาก 0x2D เป็น 0x29 ก็จะทำงานได้ปกติโดยไม่ต้องพึ่ง open bus

อาการถังหมุนไม่หยุดใน ZSNES

  • Donkey Kong Country 2 มีบั๊กเก่าในบางด่านที่ทำให้ ถังหมุน ทำงานไม่ถูกต้องบน ZSNES
  • ในการทำงานปกติ ผู้เล่นจะควบคุมการหมุนได้เฉพาะระหว่างที่กดปุ่มซ้ายหรือขวาอยู่ขณะอยู่ในถัง
  • บน ZSNES เมื่อกดซ้ายหรือขวาหนึ่งครั้ง ถังจะหมุนไปทางนั้นต่อเนื่อง และเมื่อกดทิศทางตรงข้ามก็จะหมุนต่อเนื่องไปอีกฝั่ง
  • ในช่วงหลัง ๆ ของด่าน ถังถูกวางไว้เหนือหนามหรืออุปสรรคอันตราย ทำให้บั๊กนี้เพิ่มความยากของด่านมากกว่าที่ผู้พัฒนาตั้งใจไว้
  • ดูเหมือนว่า Anomie เคยพบปัญหาเดียวกันเมื่อราว 20 ปีก่อนและแก้ใน Snes9x แล้ว โดยการแก้ในเวลานั้นไม่ใช่การ emulate open bus ทั้งหมด แต่เป็นการ hardcode ค่าของ address เฉพาะที่เกมพึ่งพา
  • ใน ZSNES บั๊กนี้ไม่เคยถูกแก้จนถึงที่สุด และ release สุดท้ายของโปรเจกต์คือปี 2007

Open bus ของ SNES และการกำหนดที่อยู่ของ 65816

  • บน SNES แม้อ่าน memory address ที่ผิด โปรแกรมก็มักจะไม่ crash
  • เมื่ออ่าน address ที่ไม่ได้ map ไว้ จะเกิดพฤติกรรม open bus ซึ่ง CPU จะอ่านค่าล่าสุดที่เคยอยู่บน data bus กลับมา
  • CPU หลักของ SNES คือ 65C816 หรือ 65816 ซึ่งรวมอยู่ในแพ็กเกจ Ricoh 5A22 S-CPU
  • 65816 เป็น CPU ส่วนขยาย 16 บิตของ 6502 และใน SNES ใช้ address bus 24 บิต แต่ address ส่วนใหญ่ประกอบจาก bank 8 บิตกับ offset 16 บิต
  • คำสั่งจำนวนมากใช้ address 16 บิตภายใน โดย instruction fetch เกี่ยวข้องกับ program bank register ส่วนการเข้าถึงข้อมูลเกี่ยวข้องกับ data bank register
  • ในสถานะถังหมุนของ DKC2 จะมีการอ่าน $2000 และ $2001 ของ bank $B3 ซึ่ง address เหล่านี้ไม่ได้ map ไปที่ใดเลยใน bank $B3 จึงกลายเป็นการอ่าน open bus

การเข้าถึงหน่วยความจำของรูทีนที่เป็นปัญหา

  • เมื่อปล่อยปุ่มซ้ายขวาขณะอยู่ในถังหมุน รูทีนที่ทำงานทุกเฟรมจะทำการอ่าน open bus
  • สถานะเริ่มต้นคือ data bank register เป็น $B3, direct page เป็น $0000, และแฟล็ก M/X ทั้งคู่ clear อยู่ ทำให้ register และการเข้าถึง memory เป็นแบบ 16 บิต
  • รูทีนนี้ใช้ address หลายตำแหน่งในช่วง $0000-$2001
    • $0EE6: ทิศทางปัจจุบันของถัง
    • $0E0A: ปริมาณการหมุนต่อเฟรม
    • $0032: ตำแหน่งที่ดูเหมือนเป็นตัวแปรชั่วคราว
  • ใน bank $00-$3F และ $80-$BF ช่วง $0000-$1FFF จะถูก map ไปยัง 8KB แรกของ WRAM 128KB ของคอนโซล
  • $2000-$20FF เป็นพื้นที่ที่ไม่ได้ map ดังนั้นคำสั่ง and $2000 จึงกลายเป็น การอ่าน open bus

วิธีที่ 0x2020 ถูกใช้ตัดสินการจบการหมุน

  • รูทีนจะนำปริมาณการหมุนไปบวกกับทิศทางปัจจุบันเพื่อสร้างทิศทางใหม่ แล้วเก็บไว้ในตัวแปรชั่วคราว
  • จากนั้นจะ XOR ทิศทางเดิมกับทิศทางใหม่ แล้วนำผลลัพธ์ไปใช้กับ and $2000 ก่อนตรวจว่าผลลัพธ์เป็น 0 หรือไม่
  • บนฮาร์ดแวร์ SNES จริง and $2000 ซึ่งเป็นการอ่าน open bus แบบ 16 บิตจะคืนค่า 0x2020 เสมอ
    • machine code ของ and $2000 คือ 2D 00 20
    • 65816 ใช้ data bus 8 บิต จึงทำการอ่าน 16 บิตเป็นการอ่าน 8 บิตสองครั้ง
    • ในกรณีนี้ byte ล่าสุดที่อ่านคือ high byte ของ address คือ 0x20 ทำให้ทั้งสองครั้งคืนค่า 0x20
  • ดังนั้นพฤติกรรมจริงจึงเทียบเท่ากับ and #$2020 ในเชิงฟังก์ชัน
  • หากผล AND เป็น 0 จะเก็บทิศทางใหม่ไว้ตามเดิมและหมุนต่อในเฟรมถัดไป
  • หากไม่เป็น 0 จะตั้งค่าปริมาณการหมุนเป็น 0 แล้วบวก 0x1000 ให้ทิศทางใหม่ จากนั้น mask ด้วย 0xE000 เพื่อจัดแนวไปยังทิศทางที่เป็นพหุคูณของ 0x2000 ที่ใกล้ที่สุด

ทำไมใน ZSNES จึงหมุนต่อไปเรื่อย ๆ

  • ค่าทิศทางของถังดูเหมือนใช้สเกลที่ 0x0000 ชี้ลง และ 0x4000 ชี้ซ้าย
  • ถังที่วิเคราะห์มีปริมาณการหมุนตามเข็มนาฬิกาเป็น 0x0300 และทวนเข็มนาฬิกาเป็น 0xFD00 โดยใช้เวลาหมุนครบ 360 องศามากกว่า 85 เฟรมเล็กน้อย
  • ค่านี้สั้นกว่า 1.5 วินาทีเล็กน้อยเมื่อคิดที่ 60fps
  • ในการ AND กับ 0x2020 การเปลี่ยนแปลงที่มีความหมายจริง ๆ คือ bit 13 หรือ 0x2000
  • การเปลี่ยนแปลง 0x2000 ในค่าทิศทางเทียบได้กับการเปลี่ยนหนึ่งขั้นของทิศทางหลักหรือทิศทางทแยง
  • บนฮาร์ดแวร์ปกติ เมื่อปล่อยปุ่มทิศทาง ถังจะหมุนต่อจนถึงทิศทางหลัก/ทแยงถัดไป แล้วหยุดโดยหันไปทางนั้นพอดี
  • หากการอ่าน open bus คืนค่า 0 เสมอ ผล AND ก็จะเป็น 0 เสมอ ทำให้โค้ดจบการหมุนไม่ถูกเรียก

ความเป็นไปได้ว่าเป็นการพิมพ์ผิดและผลของ ROM patch

  • and $2000 เป็นคำสั่ง absolute addressing และในเชิงตรรกะมีความเป็นไปได้สูงว่าควรเป็น and #$2000 แบบ immediate addressing
  • บนฮาร์ดแวร์จริง open bus คืนค่า 0x2020 ทำให้ and $2000 บังเอิญทำงานตรงตามเจตนา
  • พฤติกรรมที่บังเอิญนี้ขึ้นกับเงื่อนไขที่ว่า 6 บิตล่างของปริมาณการหมุนต่อเฟรมเป็น 0 เสมอ ซึ่งทำให้ 0x2020 เทียบเท่ากับ 0x2000 ในเชิงฟังก์ชัน
  • opcode ที่ผิดถูกเรียกใช้ที่ bank $B3 offset $EDAC และใน revision ที่วิเคราะห์ จะถูก map ไปยัง $33EDAC ภายใน ROM ขนาด 4MB ของเกม
  • หากเปลี่ยน byte นี้จาก 0x2D เป็น 0x29 ถังหมุนจะทำงานปกติแม้การอ่าน open bus จะคืนค่า 0 เสมอ
  • ตำแหน่ง ROM ที่แน่นอนอาจแตกต่างกันใน revision อื่นของเกม
  • เนื่องจากเกมทำงานได้ปกติบน SNES emulator แทบทั้งหมด ยกเว้น ZSNES รุ่นเก่า กรณีนี้จึงใกล้เคียงกับการวิเคราะห์เพื่อไล่ตามพฤติกรรมที่พึ่งพาฮาร์ดแวร์ มากกว่าการ patch ที่มีผลในทางปฏิบัติ

1 ความคิดเห็น

 
GN⁺ 2025-07-02
ความคิดเห็นจาก Hacker News
  • ตอนเขียน 6502 assembly ผมเสียเวลาในชีวิตไปมากกับความผิดพลาดแบบลืมใส่ # หน้าค่า immediate แล้วกลายเป็นไปเข้าถึงหน่วยความจำแทน
    แบบในกรณีนี้ก็มักจะมีหลายครั้งที่มันดันทำงานได้ในบางสถานการณ์โดยบังเอิญ ที่แย่กว่าคือกรณีที่ไปพึ่งพา RAM ที่ยังไม่ได้ initialize เพราะด้วยคุณสมบัติของ DRAM มันอาจทำงานได้ดีเสมอบนเครื่องของผมหรือบนอีมูเลเตอร์ แต่พอไปอยู่บนเครื่องคนอื่นที่ใช้ชิป DRAM คนละตัวกลับพัง ปกติก็มักจะพบเรื่องนี้ตอนเหลือเวลาอีก 15 นาทีก่อนขึ้นนำเสนอใน demoparty แล้วโปรแกรมดันไม่รันบนเครื่องของงาน

    • สงสัยว่ามีสถาปัตยกรรมไหนที่ใช้ 6502 CPU คู่กับ dynamic memory หรือเปล่า จากประสบการณ์ที่มีจำกัดของผม เหมือนแพลตฟอร์มเหล่านั้นจะใช้แต่ static RAM เสมอ
    • 6502 เป็นภาษาแอสเซมบลีตัวแรกของผม และผมก็แยกคิดมาตลอดว่า LDA #2 คือ “โหลดเลข 2 เข้า A” ส่วน LDA 2 คือ “โหลดค่าที่อยู่ในตำแหน่งหน่วยความจำ 2 เข้า A”
    • ในสถานการณ์แบบนี้ การเอาโค้ดให้ LLM ช่วยดูอาจมีประโยชน์จริง ๆ ความผิดพลาดหรือการพิมพ์ตกแบบนี้มีผลกระทบสูง แต่สายตาคนกลับมองข้ามได้ง่ายมาก และ LLM ค่อนข้างเก่งในการจับอะไรแบบนั้น
  • พอเห็นคำว่า Open Bus ในชื่อเรื่องเขียนตัวใหญ่ ผมก็เริ่มอ่านโดยนึกว่าเป็นชื่อเฉพาะของโปรโตคอลหรือมาตรฐานบัสเก่า ๆ ที่ไม่เคยได้ยินมาก่อน
    พออ่านแล้วถึงเข้าใจว่ามันหมายถึงสถานะ “เปิด” ที่บัสไม่ได้ต่อกับอะไรเลย เพราะตัวถอดรหัสเส้นแอดเดรสไม่ได้เปิดใช้งานอุปกรณ์หน่วยความจำใด ๆ ที่แอดเดรส $2000 มันค่อนข้างขำที่ปัญหาจากการลืม # ใน addressing แบบ immediate เพิ่งโผล่มาเมื่ออีมูเลเตอร์เก่าไม่ได้จัดการการอ่านหน่วยความจำเหมือนฮาร์ดแวร์จริง ถ้าแก้โดยใช้ addressing แบบ immediate แทน absolute addressing ก็จะไม่ต้องอ่านหน่วยความจำด้วย ทำให้เวลารันเร็วขึ้นด้วย ในบล็อกโค้ดนั้นน่าจะเร็วขึ้นราว 2µs แต่ก็คงมีความหมายเฉพาะบน bare metal และอีมูเลเตอร์เองก็น่าจะไม่ได้แม่นเรื่อง timing แบบสมบูรณ์อยู่แล้ว

    • Rare มีหลายกรณีของเกมที่ตอนทดสอบเหมือนทำงานได้ดี แต่พอหลายปีผ่านไปถึงค่อยเจอบั๊กแอบซ่อนเมื่อไปรันบนสถาปัตยกรรมใหม่ ไม่ได้หมายความว่าบริษัทอื่นไม่มีแบบนี้ เพียงแต่ในประเด็นนี้ Rare เป็นชื่อที่ยกมาอ้างอิงได้ง่าย
      เท่าที่ทราบ Donkey Kong 64 มี memory leak ที่ทำให้เกมพังหลังเล่นต่อเนื่องประมาณ 8–9 ชั่วโมง ซึ่งสำหรับยุคนั้นถือว่านานเกินจริงจนไม่น่าจะเกิดขึ้นระหว่างพัฒนา แต่ถ้าใช้อีมูเลเตอร์เซฟสเตตแล้วเล่นต่อเรื่อย ๆ แทนการใช้ระบบเซฟในเกม เวลานั้นจะสะสมเร็วมาก อย่างไรก็ตาม ประวัติยังไม่ชัดเจน บางแหล่งอ้างว่าการแถม Memory Pak มาด้วยเป็นมาตรการนาทีสุดท้ายเพื่อซ่อนบั๊ก โดยยืดเวลาที่เกมแครชจาก 8–9 ชั่วโมงไปเป็น 13–20 ชั่วโมง แต่จากงานค้นคว้าล่าสุดดูเหมือนจะเป็นเรื่องบังเอิญ และไม่น่าใช่ว่า Rare หรือ Nintendo รู้ถึงบั๊กนั้นแล้วค่อยวางขาย
    • SNES emulator บางตัวทุกวันนี้แทบจะสมบูรณ์แม้กระทั่งด้าน timing แล้ว [0] ถึงอย่างนั้น 2µs ก็คงไม่สร้างความแตกต่างที่รู้สึกได้ เว้นแต่ในกรณีพิเศษจริง ๆ
      [0] https://arstechnica.com/gaming/2021/06/how-snes-emulators-go...
  • ตอนทำฟีเจอร์ RunAhead ของ RetroArch และกำลังตรวจจุดที่เซฟสเตตไม่ตรงกัน ผมเคยเห็น SNES Puyo Puyo ใช้ PPU open bus
    หลังจากโหลดสถานะแล้ว ค่าที่อ่านจาก PPU Open Bus เปลี่ยนไป ทำให้ log trace การทำงานของ CPU ไม่ตรงกัน

  • ผมไม่ได้พลาดเรื่องตระกูล 6502 บ่อยตลอดเวลา แต่พอพลาดก็มักเป็นการใส่ memory address แทนค่า immediate
    มันเป็นความผิดพลาดที่เจอบ่อยและเกิดขึ้นได้ง่ายมาก และผมเชื่อว่า Chuck Peddle เองก็เสียใจกับไวยากรณ์ที่ต้องใส่ # แบบ #$1234 สำหรับค่า immediate มากพอสมควร การตั้งให้ # แสดงเป็นสีแดงสดใน IDE ช่วยได้บ้าง แม้แต่มหาเทพแอสเซมบลีของ Rare ก็ยังโดนปัญหานี้

    • นานมาแล้วผมเคยเจอปัญหาคล้ายกันในโหมด intel_syntax noprefix ของ GNU assembler
      ในคำสั่งที่รับได้ทั้งค่า immediate และแอดเดรสหน่วยความจำ มันมี ความกำกวมทางไวยากรณ์ ที่ทำให้ค่าคงที่แบบมีชื่อซึ่งอ้างถึงล่วงหน้าถูกตีความเป็นการอ้างถึงสัญลักษณ์ที่ยังไม่ทราบ ผลคือมันถูกแอสเซมบลีออกมาเป็นคำสั่งที่มีแอดเดรสหน่วยความจำแบบตัวแทนสำหรับให้ลิงเกอร์มาเติม relocation address ของสัญลักษณ์ แทนที่จะเป็นค่า immediate ตามที่คาดไว้ ดีบักกันทรมานมาก
    • instruction set อย่าง ARM ทำให้ความผิดพลาดแบบนั้นแทบเป็นไปไม่ได้ เพราะถ้าจะจัดการหน่วยความจำก็ต้องใช้คำสั่งคนละชุด
  • ผมชอบบทความแบบนี้ assembly มักเป็นอะไรที่ผมตามได้แค่ราว 60% ดังนั้นการมี คำอธิบายแบบร้อยแก้ว ประกบไว้ช่วยได้มากจริง ๆ
    และการได้ฟังเรื่องบั๊กในซอฟต์แวร์คลาสสิกที่ไม่มีใครเข้าใจ หรืออาจจะไม่เคยมีใครสังเกตเห็นมาจนถึงตอนนี้ ก็สนุกดี

    • เหตุผลหนึ่งที่ระบบยุคนั้นมีเสน่ห์ คือมันไม่มีมาตรการตรวจสอบสมัยใหม่ที่ตอนนี้เราแทบถือเป็นของพื้นฐานในทุกที่
      อุปกรณ์แบบที่จำเป็นสำหรับระบบที่ต่อเครือข่ายได้ และทุกวันนี้ก็ถูกพอที่จะใส่ในสถาปัตยกรรม embedded ที่แยกขาดจากโลกภายนอกโดยสิ้นเชิงได้แล้ว บน NES เดิมนั้น การอ่านและเขียนหลายอย่างเป็นเพียงการสลับแรงดันไฟบนเส้นลวดบางเส้น แล้วจากนั้นอะไรก็เกิดขึ้นตามที่จะเกิด ผลลัพธ์ที่ต้องการได้มาจากการสลับแรงดันไฟอย่างควบคุมอย่างแม่นยำให้ตรงกับสัญญาณที่บอกช่วง blanking ของ CRT แอนิเมชันบางส่วนใน Super Mario Bros 3 ใช้การสลับ RAM multiplexer เพื่อเลือกหนึ่งในหลาย bank ของข้อมูลสไปรต์ แล้วทำให้ฮาร์ดแวร์กราฟิกไปอ่านจากชิปอีกตัวที่ต่างออกไปเล็กน้อยตอนดึงสไปรต์มาใช้ เพราะ timing ของทีวีสำคัญ ซอฟต์แวร์สำหรับเขต NTSC และ PAL จึงต้องแยกกันออกด้วย และทั้งสองแบบมีอัตรารีเฟรชต่างกัน โดยอัตรารีเฟรชนั้นก็คือ clock ที่ขับตรรกะการเรนเดอร์อยู่ มันเป็นยุคที่ดิบมากจริง ๆ
  • เท่าที่ผมรู้ open bus จะพบได้ในระบบยุคแรก ๆ ที่ใช้บัส synchronous แบบเรียบง่ายเท่านั้น
    ระบบส่วนใหญ่อื่น ๆ น่าจะให้ค่าคงที่เป็นศูนย์ล้วนหรือหนึ่งล้วนเมื่อเข้าถึงแอดเดรสที่ไม่มีอยู่ เพราะมี handshake ที่ทำให้ master รู้ได้ว่าไม่มีการตอบสนองบนบัส ซึ่งในศัพท์ของ PCI จะเรียกว่า “master abort”

  • โอเพนบัสหมายถึง สายบัสข้อมูลเป็นวงจรเปิด ตามตัวอักษร
    CPU วางแอดเดรสที่ไม่ได้แมปหรือเป็นแบบเขียนอย่างเดียวลงบนแอดเดรสบัส และเนื่องจากไม่มีฮาร์ดแวร์ใดบนบัสตอบสนอง สายบัสจึงไม่ได้ถูกขับสัญญาณและลอยค้างอยู่ ในทางทฤษฎีแล้วนี่คือพฤติกรรมที่ไม่กำหนดไว้ในระดับฮาร์ดแวร์
    ถ้าจะเข้าใจว่าในความเป็นจริงเกิดอะไรขึ้น ต้องดูโครงสร้างทางกายภาพของดาต้าบัสเพิ่มอีกนิด มีตัวนำยาว ๆ ที่พาสัญญาณไปรอบเมนบอร์ดและคาร์ทริดจ์ โดยแยกจากกราวด์เพลนด้วยชั้นแผ่นฉนวนบาง ๆ สิ่งนี้มีลักษณะเหมือนตัวเก็บประจุ และในทางปฏิบัติวิศวกรก็อธิบายและจำลองมันด้วย ความจุไฟฟ้าแฝง ผลกระทบนี้เป็นตัวจำกัดอัตราการส่งข้อมูลสูงสุดของบัส จึงมักพยายามลดให้ต่ำที่สุด แต่เพราะผลกระทบนี้เอง เมื่อบัสไม่ได้ถูกขับสัญญาณ มันจึงมีแนวโน้มจะค้างอยู่ที่แรงดันล่าสุดที่ถูกขับไว้ มันทำงานคล้ายเซลล์ DRAM ขนาดเล็ก จึงเกิดผลตามที่บทความพูดว่า “การอ่านโอเพนบัสจะคืนค่าที่เพิ่งผ่านบัสไปล่าสุด”
    การที่เกมเผลอพึ่งพาผลของโอเพนบัสแบบ DKC2 นั้นไม่ใช่เรื่องหายาก บน NES รีจิสเตอร์พอร์ตอนุกรมสำหรับต่อคอนโทรลเลอร์จะขับเฉพาะบิตล่าง ส่วนบิตบนเป็นโอเพนบัส มีเกมบางเกมที่อ่านอินพุตคอนโทรลเลอร์ด้วยคำสั่ง LDA $4016 แล้วคาดว่าจะได้ค่า $40 หรือ $41 โดยเลข 4 ตรงนี้คือค่าที่ค้างอยู่เพราะโอเพนบัส
    ยังมี กลยุทธ์สปีดรัน ที่อาศัยพฤติกรรมโอเพนบัสเป็นส่วนหนึ่งของการทำ memory corruption หรือเอ็กซ์พลอยต์รันโค้ดตามอำเภอใจด้วย ตัวอย่างเช่นเครดิตวาร์ปของ Super Mario World จะส่งโปรแกรมเคาน์เตอร์ไปยังหน่วยความจำที่ไม่ได้แมป ปล่อยให้มันวิ่งต่อไปพักใหญ่ก่อนจะไปถึง RAM ในที่สุด แล้วจึงรันเพย์โหลดที่สร้างขึ้นจากการจัดตำแหน่งศัตรูอย่างประณีต [1]
    อย่างไรก็ตาม แม้โดยทั่วไปพฤติกรรมโอเพนบัสจะคาดเดาได้ ก็ยังมีข้อยกเว้น คาร์ทริดจ์ที่ไม่เป็นมาตรฐานอาจคืนค่าเริ่มต้นสำหรับหน่วยความจำที่ไม่ได้แมป หรือมีตัวต้านทาน pull-up/pull-down ซึ่งส่งผลต่อพฤติกรรมของโอเพนบัสได้ นอกจากนี้ยังมีปฏิสัมพันธ์ที่น่าสนใจกับ DMA ด้วย โดย SNES รองรับฟีเจอร์ชื่อ HDMA ซึ่งให้แอปพลิเคชันตั้งเวลาให้ส่งข้อมูลจาก CPU ไปยังฮาร์ดแวร์กราฟิกอย่างแม่นยำ เพื่ออัปโหลดข้อมูลหรือเปลี่ยนการตั้งค่ากลางเฟรม [2] การส่งข้อมูลแบบ DMA นี้จะหยุด CPU ชั่วคราวเพื่อใช้บัสในการส่ง และถ้าการส่ง DMA แทรกเข้ามาระหว่างการรันคำสั่ง หลังจากอ่านแอดเดรสเป้าหมายแล้วแต่ก่อนการอ่านโอเพนบัสจริง ก็อาจเปลี่ยนพฤติกรรมการอ่านโอเพนบัสได้
    กรณีขอบเขตที่เฉพาะทางมากนี้ส่งผลอย่างมากต่อเอ็กซ์พลอยต์สปีดรันของ Super Metroid [3] เอ็กซ์พลอยต์นี้พยายามทำให้เกิด memcpy ที่ออกนอกขอบเขต เพื่อย้ายบล็อกข้อมูลขนาดใหญ่จากโอเพนบัสไปยัง RAM การอ่านโอเพนบัสแทบจะคืนค่า 0 เสมอ เพราะไบต์สุดท้ายของคำสั่งโหลดที่เกี่ยวข้องเป็น 0 แต่ในห้องบางห้องที่มีเอฟเฟ็กต์กราฟิก HDMA จำนวนมาก มีโอกาสพอสมควรที่การส่ง DMA จะไปกระทบกับการอ่านครั้งใดครั้งหนึ่ง ทำให้มีไบต์ที่ไม่ใช่ 0 โผล่มาในตำแหน่งสำคัญ ส่งผลให้เอ็กซ์พลอยต์ทำงานไม่ปกติและแครช เรื่องนี้จึงกลายเป็นข้อถกเถียงเล็ก ๆ ในคอมมูนิตี้ บางเส้นทางและกลยุทธ์จึงเสถียรเฉพาะบนอีมูเลเตอร์และเฟิร์มแวร์ที่ไม่เป็นมาตรฐาน ผู้เล่นที่ใช้ฮาร์ดแวร์จริงหรืออีมูเลเตอร์ที่แม่นยำมากมีโอกาสเจอแครชสูงกว่า แต่ในทางกลับกัน อีมูเลเตอร์ส่วนใหญ่ รวมถึงเวอร์ชันวางจำหน่ายซ้ำอย่างเป็นทางการทั้งหมดของ Nintendo ไม่ได้จำลองกรณีขอบเขตพิเศษนี้ที่การส่ง HDMA กลางคำสั่งไปเปลี่ยนค่าการอ่านโอเพนบัส
    นอกจากนี้ TAS เคลียร์ Super Metroid ที่เร็วที่สุดในปัจจุบัน [4] ก็อาศัยปฏิสัมพันธ์กับ HDMA นี้เช่นกัน มีการพบสถานการณ์ที่การพยายามรันโอเพนบัสจะทำให้แครช แต่ปกติไม่สามารถควบคุมให้เกิดประโยชน์ได้ อย่างไรก็ตาม ด้วยการจัดการศัตรูในห้องเพื่อส่งผลต่อจังหวะเวลา CPU และใช้ HDMA ให้วางคำสั่งที่เป็นประโยชน์ลงบนบัสในจังหวะที่เหมาะสม ก็สามารถทำให้คอนโซลเอาอินพุตคอนโทรลเลอร์ไปรันเป็นโค้ดได้สำเร็จ จนบรรลุการรันโค้ดตามอำเภอใจอย่างสมบูรณ์
    [1]: https://youtu.be/vAHXK2wut_I
    [2]: https://youtu.be/K7gWmdgXPgk
    [3]: https://youtu.be/CnThmKhtfOs
    [4]: https://tasvideos.org/8214S

    • ตรงที่บอกว่าต้องดูโครงสร้างทางกายภาพของดาต้าบัส ทำให้อยากชม Ben Eater อีกครั้ง
      ซีรีส์วิดีโอของเขาเกี่ยวกับการสร้างคอมพิวเตอร์บนเบรดบอร์ดด้วย 6502 ช่วยให้เข้าใจทั้งเนื้อหาในบทความและคำอธิบายปัญหาฮาร์ดแวร์ได้จริง แน่นอนว่าต้องเอาตัวอย่างบัสพื้นฐานของเขาไปขยายความต่อเป็นเครื่องเชิงพาณิชย์ด้วย ถ้าไม่ได้ดู ก็คงแทบไม่เข้าใจเลย
      https://eater.net
  • ตอนเขียนโปรแกรมชิป Parallax Propeller ก็มีปัญหาคล้าย ๆ กันอยู่บ้าง
    ต้องใช้ JMP #address เพื่อกระโดดไปยังตำแหน่งหน่วยความจำที่ระบุ แต่กลับเผลอใช้ JMP address อยู่เรื่อย ๆ ซึ่งเป็นการกระโดดไปยังแอดเดรสที่อ่านได้จากตำแหน่งหน่วยความจำที่ระบุ น่าจะเพราะยังมีแอสเซมบลี 6502 ติดเป็น muscle memory อยู่
    Propeller: JMP #address
    6502: JMP address
    Propeller: JMP address
    6502: JMP (address)
    ที่แย่ที่สุดคือเหมือนในบทความนี้ โค้ด Propeller ที่มีบั๊กแบบนี้บางครั้งก็ดันทำงานได้ แล้วจู่ ๆ ก็หยุด จนต้องเสียเวลาหลายชั่วโมงเพื่อหาว่าทำไม

  • กราฟิก 3D แบบพรีเรนเดอร์ด้วย SGI ของ DKC 1 ล้ำสมัยมากในยุคนั้น Vector Man บน Genesis ก็ทำอะไรคล้ายกัน แต่ไม่ค่อยได้รับความสนใจเท่าไร

    • ตอนนั้นอายุราว 11 ปี ซึ่งเป็นกลุ่มเป้าหมายตรง ๆ ของ DKC ในปี 1995 และเกมนั้นช็อกมากจริง ๆ
      แทบไม่อยากเชื่อสายตาตัวเอง จำได้ว่าช่วงใกล้วางขายมีม้วนวิดีโอที่ทั้งโปรโมตเกมและเล่าเบื้องหลังการพัฒนา น่าจะเป็นของประชาสัมพันธ์ที่ขอรับมาจากอะไรสักอย่างอย่างกล่องซีเรียล ดูเทปนั้นซ้ำเยอะมาก แม้จะไม่ได้เป็นเจ้าของ DKC เอง แต่ก็ได้เล่นที่บ้านเพื่อน
    • ตอนเด็ก ๆ กราฟิกนั้นให้ความรู้สึก ปลอม ๆ อยู่บ้าง เหมือนในระดับหนึ่งเรารับรู้ได้ว่ามันไม่ใช่ของ “จริง”
      นิตยสารในยุคนั้นมักพูดเรื่องนี้แบบคลุมเครือ และชวนให้เข้าใจว่า SNES มีพลังพอจะเรนเดอร์ตัวละครอะไรทำนองนั้นแบบเรียลไทม์ ทั้งที่จริงแล้วมันใกล้เคียงกับแอนิเมชันแบบฟลิปบุ๊กโดยเนื้อแท้
  • เวลาเล่นเกมผ่านอีมูเลชันแล้วติดขัด ก็มักจะสงสัยในที่สุดว่าเป็น บั๊กของอีมูเลเตอร์ หรือเปล่า
    ถ้าเป็นปัญหาเฉพาะจุดนี้ ก็คงจะคิดว่าเกมถูกออกแบบมาแบบนี้ตั้งแต่แรกและแค่มันยากเฉยๆ ไม่ได้เกี่ยวกันโดยตรงนัก แต่เวลาเกมรู้สึกยากมากจริงๆ ก็จะคิดคล้ายๆ กันว่า “นี่เป็นเพราะความหน่วงของอีมูเลชันหรือเปล่า?” สุดท้ายพอขุดเรื่องนี้ลึกเข้าไป ก็เลยประกอบ MiSTer FPGA ขึ้นมาเอง

    • Chrono Trigger ก็มีอะไรแบบนี้เหมือนกัน
      จำได้ว่ามีช่วงหนึ่งที่หลังจากจับหนูแล้ว ต้องกดปุ่มสี่ปุ่มพร้อมกัน แต่การรับอินพุตผ่าน USB ส่งได้พร้อมกันแค่ 3 ปุ่ม เลยต้องกดมั่วๆ ทั้งสี่ปุ่มเพื่อให้ในช่วงเวลาสั้นมากๆ มันถูกลงทะเบียนครบทั้งหมด ต้องลองหลายครั้งและหงุดหงิดมาก
    • DKC เคยเล่นผ่าน ZSNES อย่างเดียว และก่อนอ่านบทความนี้ก็ไม่เคยรู้เลยว่านี่เป็น บั๊กของอีมูเลเตอร์
      อย่างที่บอกไป ผมแค่คิดว่าการกะจังหวะยิงออกจากถังเพื่อให้ได้มุมที่ถูกต้องเป็นการออกแบบเกมตามปกติ เลยตกใจมากที่รู้ว่ามันเป็นบั๊ก
    • ตอนเด็กเล่น Bionic Commando เยอะมาก
      พอลองรันด้วยอีมูเลเตอร์ช่วงต้นยุค 2000 มันยากกว่าที่จำได้มาก พอมาทีหลังถึงรู้ว่ามีบั๊กอีมูเลชันที่ทำให้แม้จะระเบิดฐานแล้ว ศัตรูก็ยังไม่หายไป และ Ladd ก็ยังคงถูกแช่แข็งอยู่ ดังนั้นถ้าจะเคลียร์ด่านให้ได้ ก็ต้องมีพลังชีวิตเพิ่มอีกราว 2 ขีด ครั้งหนึ่งเคยผ่านได้แบบนั้นเพื่อดูว่าทำได้ไหม แต่ก็ไม่เคยทำอีกเลย