Warcraft Rumble

เจาะลึก Warcraft Rumble: การคำนวณค่าประสบการณ์หุ่นจิ๋ว

Blizzard Entertainment

สวัสดีครับ รัมเบลอร์ทุกคน!

ผมแอนดี้ ลิม หัวหน้าวิศวกรด้านคุณลักษณะเซิร์ฟเวอร์ของ Warcraft Rumble ทีมเซิร์ฟเวอร์มีหน้าที่รับผิดชอบทุกอย่างที่คุณคาดหวังจากทีมเซิร์ฟเวอร์ รวมถึงการดูแลเครือข่าย การประมวลผลบนคลาวด์ และการจัดเก็บข้อมูล แต่เราก็ยังมีส่วนสร้างคุณลักษณะของเกมอย่างความก้าวหน้าในแคมเปญและเควสต์ด้วย ผมอยากจะพาทุกคนมารู้ถึงเบื้องหลัง และแบ่งปันวิธีการที่เราจัดเก็บค่าประสบการณ์และใช้เพื่อคำนวณเลเวลของหุ่นจิ๋วแต่ละตัวสักเล็กน้อย


ทำความรู้จักกับ Cassandra

คำเตือน! ต่อไปนี้จะเป็นข้อมูลเทคนิคจ๋า อาจจะเข้าใจยากสักหน่อย แต่เราอยากให้ทุกคนได้อ่าน

มาเริ่มด้วยการทำความรู้จักกับ Cassandra โซลูชันการเก็บข้อมูลที่เราใช้ในการติดตามการเปลี่ยนแปลงข้อมูลผู้เล่นมากมายที่เปลี่ยนแปลงอย่างต่อเนื่องกันก่อนครับ Cassandra เป็นฐานข้อมูลแบบกระจายแบบโอเพ่นซอร์สที่ได้รับความนิยม สามารถปรับขนาดได้อย่างดีเยี่ยม และทำให้เราสามารถหาจุดสมดุลที่เหมาะสมระหว่างความสอดคล้องของข้อมูลและการใช้งานของข้อมูลได้ Cassandra นั้นทำงานได้ดีกับชุดข้อมูลขนาดใหญ่ได้โดยไม่ต้องมีการบังคับใช้เค้าร่างแบบคงตัว (Hard schema) เราได้สร้างเครื่องมือที่จะให้วิศวกรของเราสามารถระบุตารางฐานข้อมูลและตารางเค้าร่างตามที่แต่ละคุณลักษณะต้องการได้ ซึ่งทำให้เรามีเค้าร่างที่ยืดหยุ่นและสามารถจัดระเบียบได้ตามต้องการ เราสามารถเขียนและยืนยันเค้าร่างและคำขอได้อย่างง่ายดาย

เค้าร่าง (Schema) คืออะไร เค้าร่างฐานข้อมูลคือสิ่งที่ระบุรูปแบบการจัดเรียงข้อมูลภายในระบบฐานข้อมูลเชิงสัมพันธ์ เช่นข้อมูลที่คุณสามารถเห็นได้ในตาราง


เก็บค่าประสบการณ์ในรูปแบบบัญชีแยกประเภท

Cassandra นั้นขึ้นชื่อว่าเขียนข้อมูลได้เร็วแต่อ่านข้อมูลได้ช้า การอัพเดตข้อมูลที่อยู่กับที่นั้นมักจะใช้การปฏิบัติการแบบอ่านแล้วเขียน ซึ่งก็ช้าเช่นกัน เราจึงออกแบบให้ข้อมูลผู้เล่นเป็นบัญชีแยกประเภทเพื่อแก้ปัญหานี้ ในบัญชีแยกประเภทแต่ละบัญชี คุณจะเขียนข้อมูลการเปลี่ยนแปลงเป็นบรรทัดแยก แล้วเมื่ออ่านข้อมูล คุณก็จะอ่านข้อมูลทั้งหมดแล้วค่อยคำนวณ

ตัวอย่างที่ดีของบัญชีแยกประเภทคือบัตรเครดิตของคุณ การทำธุรกรรมแต่ละครั้งจะเป็นค่าข้อมูลบวกหรือลบแบบแยก ทุกครั้งที่คุณใช้บัตรเครดิต ก็จะมีการสร้างข้อมูลเป็นธุรกรรมแบบลบ ทุกครั้งที่คุณจ่ายคืน ก็จะมีการสร้างข้อมูลเป็นธุรกรรมแบบบวก แล้วคุณรู้ได้อย่างไรว่าคุณต้องจ่ายเท่าไหร่ คุณต้องดูข้อมูลของทั้งบัญชีแล้วบวก/หักลบข้อมูลแต่ละจุด

ลองคิดตามว่าข้อมูลอยู่กับที่เป็นสิ่งของหนึ่งสิ่งที่คุณสามารถจัดเก็บได้ ทุกครั้งที่คุณอยากจะแก้ไขค่า คุณต้องอ่านค่านั้นก่อน จากนั้นเปลี่ยนแปลงค่า แล้วเขียนค่าใหม่ลงไป ถ้าเปรียบเป็นคะแนนของการแข่งฟุตบอล ทุกครั้งที่ทีมใดก็ตามทำคะแนนได้ คุณต้องอ่านคะแนนรวมก่อนหน้าทั้งหมดก่อน เพิ่มการทำคะแนนครั้งใหม่ แล้วเขียนแก้ในคะแนนรวมทั้งหมดอีกที

ตัวอย่างที่ดีสำหรับ Arclight ก็คือบัญชีแยกค่าประสบการณ์ของหุ่นจิ๋วแต่ละตัว หลังจบแต่ละภารกิจ จะมีการเพิ่มแถวข้อมูลค่าประสบการณ์ที่หุ่นจิ๋วได้รับลงไปในบัญชีแยกของหุ่นจิ๋วแต่ละตัว หลังผ่านไปห้าภารกิจ ข้อมูลก็อาจจะมีหน้าตาเป็นแบบนี้:

ตารางที่ 1

ผู้เล่น

หุ่นจิ๋ว

ค่า

เวลา

แอนดี้

Gnoll Brute

3

วันจันทร์ 14:00 น.

แอนดี้

Gryphon Rider

3

วันจันทร์ 14:05 น.

แอนดี้

Gnoll Brute

3

วันจันทร์ 14:10 น.

แอนดี้

S.A.F.E. Pilot

3

วันจันทร์ 14:15 น.

แอนดี้

Gnoll Brute

3

วันจันทร์ 14:20 น.

ท้ายที่สุด เราจะนำแถวข้อมูลทั้งหมดของในบัญชีแยกประเภทของหุ่นจิ๋วแต่ละตัวมารวมกันแล้วบวกรวมค่าประสบการณ์ ผลลัพธ์ก็อาจจจะออกมาเป็นแบบนี้:

ตารางที่ 2

ผู้เล่น

หุ่นจิ๋ว

ยอดรวมทั้งหมด

แอนดี้

Gnoll Brute

9

แอนดี้

Gryphon Rider

3

แอนดี้

S.A.F.E. Pilot

3


เริ่มกระบวนการรวมข้อมูล

หลังจากที่เราอธิบายวิธีการเก็บค่าประสบการณ์แล้ว ก็มาดูวิธีการปรับปรุงการคำนวณของหุ่นจิ๋วแต่ละตัวกัน การเก็บข้อมูลค่าประสบการณ์ทั้งหมดแบบแยกบรรทัดเช่นนี้กับหุ่นจิ๋วทุกตัวจะทำให้การอ่านข้อมูลนั้นเป็นไปได้ยาก เนื่องจากมีแถวเยอะมาก และข้อมูลในตารางเหล่านี้จะมากขึ้นเรื่อยๆ แบบไม่มีสิ้นสุด สมมติว่าการเล่นปกติของเกม Rumble ในหนึ่งวันของคุณคือการทำเควสต์หรือ PVP 15 ครั้ง รวมถึงการลง Surge 20 รอบต่อสัปดาห์เพื่อรับ Gold และการเล่นดันเจี้ยนรายสัปดาห์เพื่ออัพเกรดกองทัพ นั่นหมายถึงแต่ละสัปดาห์จะมีข้อมูลเพิ่มถึง 100 แถว หลังจากการเล่นเป็นเวลา 3 เดือน ข้อมูลของคุณจะมีประมาณ 1260 แถว แล้วการจะคำนวณยอดรวมของคุณได้นั้น เราต้องดึงข้อมูลมาจากการอ่านข้อมูลที่เชื่องช้าของ Cassandra รอกันแบบจุกๆ

เราจึงได้พัฒนาวิธีการแก้ไขปัญหานี้: การรวมข้อมูล (rollup) การรวมข้อมูลคือการคำนวณค่าทั้งหมดจนถึงเวลาหนึ่งๆ ในกรณีนี้ เราจะบวกข้อมูลทั้งหมดเข้าด้วยกันและแสดงเป็นบรรทัดเดียว แล้วเราจะเก็บข้อมูลนี้ไว้ในตารางที่สอง คุณก็จะได้เห็นว่าประทับเวลานั้นมีประโยชน์อย่างไร สำหรับค่าประสบการณ์ ข้อมูลสำคัญคือผู้เล่นและหุ่นจิ๋ว และการคำนวณคือการรวมยอด  เรามารวมข้อมูลทั้งหมดของบัญชีแยกประเภทจนถึงวันอังคารเวลา 0:00 น. แล้วเก็บผลไว้ในตาราง Cassandra ที่สองกัน ข้อมูลอาจจะออกมาเป็นแบบนี้:

ตารางที่ 3

ผู้เล่น

หุ่นจิ๋ว

ยอดรวมทั้งหมด

วันสิ้นสุด

แอนดี้

Gnoll Brute

9

วันอังคาร 0:00 น.

แอนดี้

Gryphon Rider

3

วันอังคาร 0:00 น.

แอนดี้

S.A.F.E. Pilot

3

วันอังคาร 0:00 น.

ในวันพุธ คุณเล่นเกมมากกว่าปกติ และสร้างข้อมูลค่าประสบการณ์มากขึ้น ตารางค่าประสบการณ์เดิมของคุณก็จะมีข้อมูลมากขึ้นเช่นกัน

ตารางที่ 4

ผู้เล่น

หุ่นจิ๋ว

ค่า

เวลา

แอนดี้

Gnoll Brute

3

วันจันทร์ 14:00 น.

แอนดี้

Gryphon Rider

3

วันจันทร์ 14:05 น.

แอนดี้

Gnoll Brute

3

วันจันทร์ 14:10 น.

แอนดี้

S.A.F.E. Pilot

3

วันจันทร์ 14:15 น.

แอนดี้

Gnoll Brute

3

วันจันทร์ 14:20 น.

แอนดี้

S.A.F.E. Pilot

3

วันพุธ 12:00 น.

แอนดี้

Chain Lightning

3

วันพุธ 12:05 น.

แอนดี้

Gryphon Rider

3

วันพุธ 12:10 น.

ตอนนี้เราอยากจะดูข้อมูลทั้งหมด แล้วคำนวณอีกครั้งว่าเลเวลของหุ่นจิ๋วจะเป็นเท่าไหร่หลังจากที่คุณเล่นเกมทั้งหมดเสร็จแล้ว เราส่งคำขอไปให้ทั้งสองตาราง โดยขอข้อมูลแถวเดียวจากตารางรวมข้อมูล และขอข้อมูลทั้งหมดที่เกิดขึ้นหลังข้อมูลเดี่ยวของตารางรวมข้อมูลจากบัญชีแยกประเภท แล้วนำค่ามารวมกันเพื่อสร้างผลลัพธ์ในตารางค่าประสบการณ์รวม ดังนั้นเราจะอ่านตารางที่ 3 แล้วส่งคำขอไปยังตารางที่ 4 เพื่อขอเฉพาะข้อมูลที่เกิดขึ้นหลังแต่ละแถวในตารางที่ 3 รายการขั้นตอนที่เราจะทำจะคล้ายๆ แบบนี้:

  1. อ่านแถวข้อมูลจากตารางที่ 3
     

Gryphon Rider

3

วันอังคาร 0:00 น.

  1. อ่านแถวข้อมูลของ Gryphon Rider จากตารางที่ 3 ที่สร้างหลังวันอังคาร 0:00 น.
     

Gryphon Rider

3

วันพุธ 12:10 น.

  1. ต่อไปเราก็จะรวมค่าของชุดข้อมูลทั้งสองเพื่อสร้างผลลัพธ์:

Gryphon Rider

6

คุณจะเห็นได้ว่าวิธีนี้สามารถลัดการอ่านข้อมูลจากตารางที่ 4 ได้บางส่วน

คุณอาจจะคิดว่าเราน่าจะเก็บข้อมูลที่เพิ่งคำนวณเสร็จไว้หลังข้อมูลที่สร้างใหม่ไปเลย ซึ่งก็ฟังแล้วดูดี แต่ในความเป็นจริง การทำเช่นนี้จะทำให้เกิดข้อมูลที่ไม่สอดคล้องกัน และทำให้ประสิทธิภาพลดลงได้ หนึ่งตัวอย่างของประสิทธิภาพที่ลดลงคือการที่ระบบของเราอาจจะยุ่งกับการประมวลผลซ้ำและเก็บข้อมูลบ่อยเนื่องจากการต้องอ่านแล้วเขียนข้อมูล เราต้องทำให้การคำนวณนั้นสมดุลเพื่อให้ผู้เล่นสามารถเล่นเกมได้ในประสิทธิภาพที่เหมาะสม

หนึ่งตัวอย่างของข้อมูลที่ไม่สอดคล้องกันคือการคำนวณเมื่อจบเกม โครงสร้างและแพลตฟอร์มเซิร์ฟเวอร์ของเรารองรับข้อความที่มาช้า ตัวอย่างคือ ถ้าสองศูนย์ข้อมูล (A และ B) มีปัญหาชั่วคราว และศูนย์ A ส่งข้อความที่จะมอบค่าประสบการณ์ให้กับหุ่นจิ๋วของผู้เล่น แต่ B ยังไม่ได้รับข้อความนั้น ลองคิดสถานการณ์ตามนี้นะครับ: คุณกำลังเล่นเกมก่อนถึงช่วงเที่ยงคืน คุณชนะเกม และตอนนั้นเป็นวันพุธเวลา 23:59 น. ตัวประมวลผลการรวมข้อมูลนั้นถูกตั้งค่าให้ทำงานทุกวันเพื่อรวมข้อมูลทั้งหมดในวันนั้น โดยตัวประมวลผลจะเริ่มทำงานตอนเที่ยงคืนและรวมค่าประสบการณ์ทั้งหมดของคุณจนถึงวันพฤหัสบดีเวลา 0:00 น. ข้อมูลนั้นจะถูกเก็บในตารางที่สอง และการอ่านข้อมูลค่าประสบการณ์ใหม่จะมองหาค่าสุดท้ายที่มีการเขียนลงบัญชี และสรุปค่าทั้งหมดจนถึงวันพฤหัสบดีเวลา 0:00 น. สิ่งที่เกิดขึ้นกับข้อความที่มาช้าคือระบบจะได้รับข้อความ แต่เห็นว่าเกมถูกเล่นจบไปในวันพุธเวลา 23:59 น. และเขียนค่าประสบการณ์ตามเวลาที่เห็น แต่ตารางรวมข้อมูลจะไม่มีข้อมูลนี้ และการร้องขอข้อมูลต่อจากนี้ก็จะไม่มีข้อมูลนี้เช่นกัน ซึ่งทำให้เกิดปัญหาข้อมูลไม่ครบถ้วน แล้วถ้าข้อความมาช้าหลายวันล่ะ เรามีระบบตรวจสอบและแจ้งเตือนที่สามารถเกือบการันตีได้ว่าข้อมูลใหม่นี้จะถูกประมวลผลก่อนการรวมข้อมูลครั้งต่อไป


การคำนวณเลเวลของหุ่นจิ๋ว

ถ้าย้อนกลับมาดูที่ตารางค่าประสบการณ์รวม คุณจะเห็นว่าเราไม่ได้คำนวณเลเวลที่ตารางนี้ เราจะมีแค่ข้อมูลค่าประสบการณ์ที่ได้รับเท่านั้น เรามีตารางคงที่ที่นักออกแบบเกมสร้างไว้แยกต่างหาก โดยมีหน้าตาประมาณนี้ หุ่นจิ๋วจะเริ่มที่เลเวล 1 และเมื่อได้ค่าประสบการณ์ 1 แต้ม เลเวลจะเพิ่มเป็น 2 จากนั้นต้องใช้ค่าประสบการณ์อีก 3 แต้มเพื่อเพิ่มเป็นเลเวล 3

 เลเวล

จำนวนที่ต้องมีเพื่อเพิ่มเลเวล

1

1

2

3

3

6

4

10

5

20

10

250

เราสามารถใช้ทั้งสองตารางนี้ช่วยคำนวณเลเวลของหุ่นจิ๋วในเวลารันไทม์ได้ด้วยการคำนวณผลลัพธ์ ที่จะให้ชุดข้อมูลสุดท้ายเช่นนี้:

หุ่นจิ๋ว

เลเวล

ค่าประสบการณ์

จำนวนที่ต้องมีเพื่อเพิ่มเลเวล

Gnoll Brute

3

5

1

Gryphon Rider

3

2

4

S.A.F.E. Pilot

3

2

4

Chain Lightning

2

2

1

นักออกแบบเกมมีความยืดหยุ่นในการเปลี่ยนจำนวนค่าประสบการณ์ที่ต้องมีเพื่อเพิ่มเลเวล พวกเขาอาจจะตัดสินใจว่าช่วงเลเวลต่ำนั้นเพิ่มเลเวลยากเกินไป และลดจำนวนค่าประสบการณ์ที่ต้องใช้เพื่อเพิ่มเลเวลก็ได้ ซึ่งนั่นหมายความว่าหุ่นจิ๋วทุกตัวจะได้ความก้าวหน้าด้านเลเวลเพิ่มขึ้นเล็กน้อยและต้องเก็บค่าประสบการณ์น้อยลง โดยที่ไม่ต้องแก้ไขข้อมูลผู้เล่นที่บันทึกไว้ในฐานข้อมูลเลยสักนิด ซึ่งก็ส่งผลกลับด้านได้เช่นกัน ถ้านักออกแบบเกมตัดสินใจว่าการเพิ่มเลเวลนั้นง่ายเกินไปและเพิ่มเกณฑ์ค่าประสบการณ์ พวกเราก็ไม่จำเป็นต้องเพิ่มค่าประสบการณ์เพื่อชดเชยเลเวลของหุ่นจิ๋วก่อนที่จะมีการแก้ไขเช่นกัน แต่ก็ไม่ได้หมายความว่าเราจะไม่ออกแบบตัวเลือกที่จะมอบค่าประสบการณ์ให้กับหุ่นจิ๋วเพื่อลดปัญหาในการเล่น


ทางเลือกอื่นที่เราเคยคิดไว้

ก่อนที่เราจะใช้โซลูชันที่ใช้อยู่ตอนนี้ เราได้พิจารณาวิธีต่างๆ ในการเก็บข้อมูลค่าประสบการณ์ที่ได้รับและการคำนวณเลเวลของหุ่นจิ๋วเช่นกัน บางวิธีก็ให้เวลาเราในการปรับประสบการณ์ของผู้เล่น ส่วนบางวิธีก็เหมาะสมในการมอบประสบการณ์ที่ดี และยังสามารถติดตามความคืบหน้าของผู้เล่นได้อยู่

'เก็บข้อมูลเลเวลและค่าประสบการณ์ที่มีกับค่าประสบการณ์ที่ต้องใช้เพื่อเพิ่มเลเวลของหุ่นจิ๋วอยู่เสมอ'

แล้วทำไมเราไม่ใช้รูปแบบ SQL มาตรฐานแล้วอัพเดตการแลกเปลี่ยนข้อมูลเอาล่ะ การอัพเดตการแลกเปลี่ยนข้อมูลจะเป็นการอัพเดตชุดข้อมูลทั้งหมดทีเดียว ไม่สามารถอัพเดตแค่บางส่วนได้ ถ้ามีหนึ่งส่วนที่ไม่สามารถเขียนได้ ข้อมูลทั้งหมดจะถูกย้อนกลับไปเป็นค่าตั้งต้น วิธีนี้จะมอบคุณสมบัติ A.C.I.D. กับข้อมูล ซึ่งก็คือ Atomicity (ดำเนินการให้เสร็จหรือไม่ดำเนินการเลย) Consistency (ข้อมูลสอดคล้องกัน) Isolation (การแบ่งแยกการดำเนินการ) และ Durability (ข้อมูลคงอยู่ถาวร)

วิธีนี้จะเก็บข้อมูลค่าประสบการณ์และเลเวลรวมของหุ่นจิ๋วแต่ละตัวไว้ในบรรทัดของตัวเอง ซึ่งทำให้อ่านได้ง่ายมาก

แต่ละครั้งที่หุ่นจิ๋วแต่ละตัวได้รับค่าประสบการณ์ จะต้องมีการอัพเดตว่าหุ่นจิ๋วตัวนี้ได้ค่าประสบการณ์ 2 แต้มและขาดอีก 8 แต้มถึงจะเพิ่มเลเวล และตอนนี้หุ่นจิ๋วอยู่ที่เลเวล 3 การเปลี่ยนข้อมูลในรูปแบบนี้จะบังคับให้ต้องมีการอ่านข้อมูล ประมวลผลใหม่และเขียนซ้ำอีกครั้ง ซึ่งรูปแบบนี้ไม่เข้ากับจุดแข็งของ Cassandra อีกปัญหาของวิธีนี้คือถ้าเราอยากจะเปลี่ยนจำนวนค่าประสบการณ์ในการเพิ่มเลเวล เราจะต้องปิดเซิร์ฟเวอร์เกมเพื่อแก้ไขข้อมูลของหุ่นจิ๋วทั้งหมดของผู้เล่นทุกคน

'เก็บเป็นหน่วยเปอร์เซ็นต์'

เรายังเคยพิจารณาที่จะเก็บค่าประสบการณ์ในการเพิ่มเลเวลเป็นหน่วยเปอร์เซ็นต์เช่นกัน ตัวอย่างเช่น:

หุ่นจิ๋ว

เลเวล

ค่าประสบการณ์เพื่อเพิ่มเลเวล

Gnoll Brute

3

20%

Gryphon Rider

2

20%

S.A.F.E. Pilot

2

20%

หลังจบเกม เราจะคำนวณอย่างรวดเร็ว เช่น Gnoll Brute ได้รับค่าประสบการณ์ +3 เปอร์เซ็นต์คือ +3 / 10 ก็คือต้องให้ค่าเป็น 30% ของเลเวลนั้น ผลที่ออกมาก็คือ:

หุ่นจิ๋ว

เลเวล

ค่าประสบการณ์เพื่อเพิ่มเลเวล

Gnoll Brute

3

50%

Gryphon Rider

2

20%

S.A.F.E. Pilot

2

20%

วิธีนี้จะให้ความยืดหยุ่นเมื่อเราอยากจะเปลี่ยนแนวโน้มการเพิ่ม/ลดค่าประสบการณ์ที่ต้องใช้ในการเพิ่มเลเวลของหุ่นจิ๋วโดยไม่ก่อให้เกิดการเปลี่ยนแปลงเลเวล วิธีนี้จะทำให้เราสามารถแปลงข้อมูลเป็นภาพในอินเทอร์เฟซผู้ใช้ในเกมได้ง่าย แค่เติมหลอดประสบการณ์ 30% ของหลอดทั้งหมด ไม่ต้องคำนวณอะไรเพิ่มเลย อย่างไรก็ตาม วิธีนี้จะทำให้เปอร์เซ็นต์ขึ้นน้อยมากเมื่อหุ่นจิ๋วมีเลเวลสูง ถ้าหุ่นจิ๋วได้รับค่าประสบการณ์ 10 แต้มต่อเกม และค่าประสบการณ์ที่ต้องใช้ในการเพิ่มเลเวลคือ 100,000 ค่าที่ได้ก็จะเป็นแค่ 0.01% หน่วยประมวลผลกลาง (CPU) สามารถคำนวณเลขทศนิยมได้ แต่ความแม่นยำและความถูกต้องอาจทำให้เกิดบั๊กที่เราไม่คาดคิดได้จากการทำงานของคณิตทศนิยม


ไว้พบกันใหม่คราวหน้า...

ในเทคโนโลยีฐานข้อมูล มีตัวเลือกให้เราพิจารณามากมาย รวมถึงเครื่องมือที่เราใช้ได้และวิธีการเก็บและคำนวณข้อมูล และเช่นเดียวกับทุกสิ่งที่กำลังอยู่ในการพัฒนา เราอาจจะเปลี่ยนแปลงสิ่งเหล่านี้ในภายหลังถ้าเราเจอกับสิ่งที่ทำงานได้ดีกว่า แต่รูปแบบฐานข้อมูลนี้เป็นจุดเริ่มต้นที่ดีสำหรับเกมนี้

ขอขอบคุณที่อ่านการอัพเดตทางวิศวกรรมของเรา!

~ แอนดี้ ลิม

แล้วก็อีกเรื่อง ผมขอประกาศไว้ตรงนี้เลยว่าผมไม่ใช่จอมโจรติดตา เพราะวันแรกที่ผมทำงานในทีมนี้ จอมโจรคนนั้นก็เอาสติกเกอร์ลูกตากลิ้งมาติดที่จอมอนิเตอร์ใหม่เอี่ยมของผมเหมือนกัน ลูกตากลิ้งได้พวกนั้นจ้อง... มองผม... ทั้งวันเลย