Ghi chú phát triển của xkcd về "Machine"
(chromakode.com)- Machine của xkcd là một trò chơi cỗ máy bi lắp ghép kiểu Rube Goldberg khổng lồ được tạo bằng cách nối các thiết bị dạng ô do độc giả làm ra, và ý tưởng này đã được hiện thực hóa thành một truyện tranh tương tác chỉ trong 3 tuần
- Kinh nghiệm từ các dự án có sự tham gia của người dùng trước đó đã dẫn tới tiêu chí thiết kế rằng để một canvas chia sẻ hoạt động tốt thì cần có ngữ cảnh và mục tiêu chung
- Để giữ được khả năng biểu đạt của người chơi nhưng vẫn đảm bảo tính tương thích giữa các ô, nhóm đặt ra các ràng buộc mạnh về đầu vào/đầu ra, đồng thời giới hạn mỗi thiết bị phải đạt trạng thái ổn định trong vòng 30 giây
- Thay vì mô phỏng toàn bộ cỗ máy theo thời gian thực, họ chỉ chạy Rapier cho vùng đang nhìn thấy và dùng snapshot tại thời điểm phê duyệt để khiến các thiết bị trông như đã hoạt động sẵn
- Họ kết hợp React và render bằng DOM, backend Haskell, Redis, OpenAPI, TanStack Query và UI kiểm duyệt để vận hành quy trình phê duyệt và phát hành bài gửi
Điểm khởi đầu của Machine
- xkcd đã ra mắt Machine vào ngày 5 tháng 4
- Machine là một trình xây dựng Rube Goldberg machine khổng lồ theo phong cách game kinh điển The Incredible Machine
- Toàn bộ cỗ máy được tạo thành bằng cách nối các ô thiết bị nhỏ do từng độc giả xkcd tạo ra
- Nhóm đã làm xong Machine trong 3 tuần, và ý tưởng bắt nguồn từ GIF cộng tác năm 2005 Blue Ball Machine
- Những câu hỏi trọng tâm trong giai đoạn brainstorming ban đầu là: bi đến từ đâu, liệu mọi người có đang nhìn cùng một cỗ máy hay không, mục đích của cỗ máy là gì, người chơi sẽ tương tác ra sao, và vì sao họ muốn tham gia
Bài học rút ra từ các xkcd có sự tham gia của người dùng
- Trong số các truyện tranh tương tác xkcd trước đây lấy nội dung do người dùng tạo làm trung tâm, Lorenz cho phép độc giả viết lời trong các panel để phát triển trò đùa và câu chuyện, và đây là một trải nghiệm tốt
- Collector’s Edition năm 2020 cho người chơi tìm sticker trong kho lưu trữ xkcd rồi dán một lần lên canvas chia sẻ toàn cục, nhưng nó không vận hành tốt như kỳ vọng
- Tất cả người chơi đều bắt đầu từ giữa một bản đồ trống, và chẳng mấy chốc ấn tượng đầu tiên đã trở thành một màn hình hỗn loạn
- Không có nhiều động lực để chọn vị trí sticker thật cẩn thận, và rất khó để chỉ bằng hành động cá nhân mà thúc đẩy cốt truyện tiến lên
- Vì không có câu chuyện hay mục tiêu chung, nên không rõ mỗi sticker liên kết với các yếu tố khác trên trang như thế nào
- Để một canvas tập thể hoạt động tốt, người dùng cần có thể học bằng ví dụ về việc nên tạo ra thứ gì thì sẽ hay
- Để kết quả sáng tạo hội tụ theo một hướng, cần có ngữ cảnh và mục tiêu chung giúp đồng bộ hóa việc nên tạo ra điều gì
Thiết kế ràng buộc: khả năng biểu đạt, tính tương thích, và trạng thái ổn định trong 30 giây
- Ngay cả sau khi quyết định làm một thiết bị thả bi cộng tác quy mô lớn, các vấn đề như kích thước toàn bộ cỗ máy, cách mô phỏng và cách tích hợp các ô vẫn còn đó
- Nếu giả định cỗ máy có kích thước 100x100, thì mục tiêu chạy 10.000 ô theo thời gian thực trên client và xử lý hàng chục viên bi trong mỗi ô bị xem là quá rủi ro
-
Ưu tiên khả năng biểu đạt hơn độ chính xác
- Nhóm đã cân nhắc phương án chạy toàn bộ cỗ máy trên server hoặc mô phỏng từng ô riêng lẻ để xác thực
- Khi editor nguyên mẫu dễ dàng tạo ra các mẫu va chạm bi đầy hỗn loạn, họ đi đến kết luận rằng nếu bắt buộc cỗ máy phải có thể dự đoán được thì sẽ làm giảm tự do của người chơi
- Thiết kế cuối cùng ưu tiên tính linh hoạt của người chơi, kể cả việc họ có thể tạo ra những thiết bị rất phi định tính hoặc thậm chí bị lỗi
- Chính lựa chọn này làm nảy sinh nhu cầu kiểm duyệt chủ động để kiểm tra xem các ô có đáp ứng ràng buộc hay không và để loại bỏ nội dung mang tính công kích
-
Ràng buộc đầu vào/đầu ra để đảm bảo tương thích giữa các ô
- Ban đầu, họ từng cân nhắc cách để người chơi sau tự do mở rộng dựa trên vị trí đầu ra của ô trước đó
- Nhưng nếu một ô được đặt sớm về sau cần bị thay thế, một vùng lớn phụ thuộc vào nó có thể bị phá vỡ
- Vì vậy, họ đặt ràng buộc đầu vào/đầu ra rất chặt để nhiều người chơi có thể tạo ra các thiết kế tương thích trong cùng một không gian ô
- Cách tiếp cận này gợi đến nguyên tắc Robustness principle: “bảo thủ trong những gì bạn gửi đi, khoan dung trong những gì bạn nhận vào”
- map generator của Kevin bắt đầu từ các puzzle đơn giản 1 đầu vào 1 đầu ra, trở nên phức tạp hơn ở phần giữa với các cụm hợp nhất 4 đầu vào 4 đầu ra, rồi quay lại 2 đầu ra mỗi ô ở phần cuối
- Editor cung cấp phản hồi theo thời gian thực khi người chơi tạo ô
- Trung bình, ô phải đẩy bi ra với tốc độ tương đương tốc độ bi nhận vào
- Họ muốn giảm bớt các thiết bị nuốt mất bi hoặc gây độ trễ lớn
- Để phản ánh biến động của đầu vào từ thượng nguồn, họ áp dụng chaos testing bằng cách ngẫu nhiên hóa tốc độ bi đi vào editor
-
Phải đạt trạng thái ổn định trong vòng 30 giây
- Để giảm thời gian mà moderator phải theo dõi, họ đặt ra tiêu chí tùy ý rằng thiết bị phải đi vào trạng thái ổn định trong vòng 30 giây
- Cơ sở của tiêu chí này là phép tính rằng nếu xem 10.000 ô, mỗi ô 30 giây, thì tổng thời gian kiểm duyệt sẽ vào khoảng 83,3 giờ
- Các viên bi cũng được cho hết hạn sau 30 giây
- Nếu không có cơ chế hết hạn, trải nghiệm đầu tiên của người chơi mới sẽ là nhìn bi chất đống trên màn hình
- Số lượng rigid body đang hoạt động tăng cao cũng sẽ làm chậm mô phỏng vật lý
- Việc cho bi hết hạn giúp lỗi không tích lũy theo thời gian, đồng thời khiến chỉ với 30 giây quan sát cũng có thể thấy phần lớn vị trí bi đến, qua đó đơn giản hóa khâu kiểm duyệt
Cách không chạy toàn bộ cỗ máy theo thời gian thực
- Giả định lớn đầu tiên trong kiến trúc của Machine là nếu tuân thủ các ràng buộc trên, họ có thể nối các ô khác nhau lại để trông như một cỗ máy hoàn chỉnh
- Họ đã kiểm chứng giả định này bằng cách tạo và giải thử một vài bản đồ nhỏ
- Vì không thể chạy toàn bộ cỗ máy theo thời gian thực trên server hay client, họ cần một cách chỉ mô phỏng khu vực xung quanh nơi người dùng đang nhìn
- Mục tiêu là cho phép người dùng theo dõi một viên bi đi từ trên xuống dưới cỗ máy
-
Một thế giới vật lý chỉ tồn tại trong vùng nhìn thấy
- Trình xem bản đồ ban đầu chỉ mô phỏng vùng đang thấy, nhưng khi cuộn thì các ô mới xuất hiện lại bắt đầu từ trạng thái trống, làm lộ ra khoảng hẫng của dòng chảy
- Để chúng trông như đã hoạt động sẵn thay vì trống rỗng, họ chọn cách lưu snapshot của các ô đã đạt trạng thái ổn định và nạp chúng ngay trước khi đi vào màn hình
- Trong truyện tranh hoàn chỉnh, chỉ những ô đang được render mới thực sự tồn tại trong mô phỏng vật lý
- Để tạo cảm giác phía trên màn hình còn nhiều máy móc nữa, các ô ở hàng trên cùng của vùng mô phỏng được cấp bi sinh ra theo tốc độ mong đợi của ràng buộc đầu vào
-
Snapshot tại thời điểm phê duyệt
- Việc tạo snapshot được gắn với UI kiểm duyệt
- Moderator phải chờ ít nhất 30 giây trước khi phê duyệt ô, và trạng thái tại thời điểm bấm nút phê duyệt sẽ được lưu làm snapshot
- Moderator cũng có quyền chờ thêm một chút nếu muốn đợi thiết bị vào trạng thái đẹp hơn
- Cách làm bằng snapshot có tác dụng reset lỗi tích lũy, nên khi người dùng cuộn đến và lần đầu nhìn thấy một ô mới, họ sẽ thấy trạng thái sạch mà moderator đã đánh giá là tốt
- Nếu theo dõi quá lâu, nhiều thiết bị có thể dừng lại hoặc rơi vào trạng thái hỏng, nhưng tiếp tục khám phá thì sẽ lại gặp các snapshot mới
- Toàn bộ cỗ máy không bao giờ được mô phỏng hoàn chỉnh, và vì vậy kết quả có cấu trúc gần với hyperreality
Cấu trúc render với React, DOM và Rapier
- Machine được xây dựng trên engine vật lý Rapier
- Rapier được chọn vì có tài liệu, API, các khối cơ bản hữu ích và hiệu năng trình duyệt WASM tốt nhờ triển khai bằng Rust
- Lúc đầu họ cũng quan tâm đến đảm bảo tính quyết định của Rapier, nhưng rốt cuộc không chạy mô phỏng phía server
- Trên Rapier, họ viết một React context tùy biến là
<PhysicsContext>- Nó tạo và quản lý các đối tượng vật lý Rapier trong vòng đời component React
- Điều này giúp dễ phát triển từng vật thể có thể đặt và từng bề mặt va chạm dưới dạng component “widget”
- React hoạt động như một scene graph nhanh và thô
- Khi một ô bị unmount, các đối tượng vật lý liên quan và DOM cũng được dọn dẹp
- Hot reloading qua refresh nhanh giúp dễ tinh chỉnh các hình va chạm
- Các hook vật lý được thiết kế để không hoạt động nếu ở ngoài
<PhysicsContext>, nhờ đó có thể dùng cho preview tĩnh trong UI kiểm duyệt - Về sau họ cho rằng sẽ tốt hơn nếu tạo các đối tượng Rapier bằng component thay vì hook
- react-three-rapier dùng cách này và phù hợp hơn với React diffing
- Cách dựa trên
useEffectsẽ xóa instance cũ và tạo instance mới khi dependency thay đổi
-
Render chỉ bằng DOM
- Machine được render hoàn toàn bằng DOM
- Ban đầu họ nghĩ nếu chạm trần hiệu năng thì có thể chuyển sang PixiJS hoặc canvas, nhưng trước mắt đẩy cách làm DOM đi xa nhất có thể vì ít thứ phải xây hơn
- Để tối ưu hiệu năng render, vòng lặp frame áp style trực tiếp lên các widget đang được mô phỏng vật lý
- React diff chỉ chạy khi cấu trúc của scene graph thay đổi
- Lúc đầu các viên bi cũng được render bằng React, nhưng việc tạo/xóa liên tục làm tăng chi phí diff nên họ làm một optimized renderer riêng
- Họ áp dụng draw culling cho các viên bi và widget nằm ngoài màn hình
- Cách này hoạt động tốt với 4.000 viên bi đang được mô phỏng và hàng trăm viên bi trên màn hình, nên họ chốt phương án render chỉ bằng DOM
API, kiểm duyệt và vận hành bài gửi
- Backend do davean và Kevin viết bằng Haskell, dùng Redis làm kho lưu trữ
- Họ dùng OpenAPI và OpenAPI fetch để chia sẻ type giữa các codebase
- Ban đầu có một số bất tiện trong việc khớp với type Haskell
- Nhưng điều này hữu ích trong việc điều phối các thay đổi API vào phút chót
- TanStack Query tỏ ra hữu ích để xử lý cache và tự động làm mới mà không cần server push
-
UI kiểm duyệt và mức độ ưu tiên
- UI kiểm duyệt do Ed White thiết kế là nút thắt mà mọi bài gửi đều phải đi qua trước khi được công khai
- Moderator có thể phải chọn giữa hàng trăm thiết kế ứng viên cho một ô cụ thể
- Mức độ ưu tiên của queue được xác định bằng cách đặt interestingness score theo từng loại widget rồi đếm mỗi instance để xếp hạng các ô ứng viên
- Cách này thiên về các lời giải nhồi nhiều thành phần, nhưng moderator có thể xem giữa danh sách để bù lại bằng các lời giải tối giản hơn
- Sự chênh lệch lớn giữa số lượng thiết kế được gửi và số lượng thật sự được đưa lên cỗ máy vẫn là điều đáng tiếc
- Họ đã tìm cách công khai thêm nhiều backlog hơn trước khi ra mắt, nhưng không tìm được điểm cân bằng tốt trong giới hạn thời gian kiểm duyệt
- Sau khi giai đoạn gửi trực tiếp kết thúc, họ muốn tìm cách chia sẻ thêm nhiều dữ liệu bài gửi hơn
-
Cooldown phê duyệt và điều chỉnh tốc độ
- Vì chất lượng snapshot của ô rất quan trọng, nút phê duyệt của moderator bị vô hiệu hóa cho tới khi mô phỏng đã chạy ít nhất 30 giây
- Cooldown này giúp tạo snapshot ở trạng thái ổn định và xác nhận đầu ra có nhận bi theo tốc độ dự kiến hay không
- Ban đầu họ nghĩ moderator sẽ thấy phiền, nhưng thực tế nó lại được đón nhận tích cực vì giúp ngăn các quyết định vội vàng
- Sau khi ra mắt, họ bổ sung một thanh trượt để moderator có thể chạy mô phỏng nhanh hơn rất nhiều so với thời gian thực
- Nhờ đó, họ có thể xem 30 giây đầu của một bài gửi trong dưới 5 giây, và cũng dễ xem xét hành vi trong thời gian dài hơn
Tương tác ngoài dự kiến giữa các ô
- “Jamslunt Interfoggle” là một thiết bị được đăng trong vài giờ đầu sau khi công khai, với cơ chế tận dụng một dải hoạt động hẹp của quạt
- Thiết bị này gom các viên bi xanh dương vào một lối đi, rồi khi đủ nặng chúng sẽ đổ tràn ra hai phía
- “Bouncy” đặt phía trên là một cỗ máy hỗn loạn bắn bi qua các đường giao cắt ba nhánh
- Bouncy đôi khi gửi bi xanh lá sang sai đầu ra, và những viên bi này làm vỡ đống bi xanh dương đang bị kẹt, tạo nên dòng chảy dây chuyền cho Interfoggle
- Trong editor, để đầu vào dễ hiểu hơn, họ chỉ cấp đúng màu bi cần thiết, nên Interfoggle không thể được thiết kế với hành vi của viên bi xanh lá này trong đầu
- Những kết hợp ngoài dự kiến như vậy đã trở thành một phần rất thú vị của dự án, khi mọi người dùng công cụ theo những cách sáng tạo trên canvas chia sẻ
Mã nguồn và các thử nghiệm còn lại
- Mã nguồn của Machine có thể xem tại kho GitHub
- Một bản triển khai mô phỏng hoàn chỉnh toàn bộ cỗ máy trên phạm vi toàn cục vẫn là một bài hack thú vị còn bỏ ngỏ
- Liên kết để trực tiếp thêm thiết kế vào Machine nằm tại xkcd 2916
1 bình luận
Ý kiến trên Hacker News
Điều buồn cười khi đọc bài này là lúc đó tôi hoàn toàn không biết chuyện như vậy đang diễn ra
Dường như cũng không có lời giải thích nào về tình huống, tôi cũng không biết đây là một trải nghiệm mọi người cùng tham gia, mà chỉ cảm thấy có nhiều việc ngẫu nhiên xảy ra một cách hỗn loạn
Tôi hoàn thành vài ô rồi gửi lên, nghĩ đó là cách để sang “bước tiếp theo”, nên đặt những cái tên ngớ ngẩn như “test 1b”. Vì tôi tưởng đây là chơi đơn, nên nghĩ chỉ mình tôi thấy tên đó
Làm vài cái xong thì chán, đi loanh quanh thấy những thứ phức tạp, nhưng không biết đó là bài gửi của người khác mà chỉ nghĩ chúng là điểm bắt đầu để giải màn. Rốt cuộc có thể nói là tôi đã bị dính trò đùa Cá tháng Tư
Có lẽ vì tôi chưa từng chơi trò máy móc gốc mà nó dựa trên :-)
Có vẻ tôi đã thêm quá nhiều phần tử “bonk” đến mức làm chết rapier
Uncaught Error: recursive use of an object detected which would lead to unsafe aliasing in rustat jt (rapier_wasm2d_bg.js:4836:11)at 4ea5626ea4b1e4145572.module.wasm:0xf061cat 4ea5626ea4b1e4145572.module.wasm:0xf0638at 4ea5626ea4b1e4145572.module.wasm:0xb5e7bat H.remove (rapier_wasm2d_bg.js:1051:14)at l.remove (collider_set.js:87:18)at y.removeCollider (world.js:343:28)at PhysicsContext.tsx:258:15Dù vậy vẫn thật sự rất vui, tiếc là tôi không biết lúc nó đang mở theo thời gian thực. Sẽ rất tuyệt nếu có thể tạo liên kết vĩnh viễn cho từng cỗ máy do mọi người làm
Tôi hiểu là có thể có vấn đề về dung lượng lưu trữ, nhưng liệu có thể mã hóa JSON bằng base64 rồi đưa vào tham số URL không? Tôi muốn tạo các bản đồ kỳ quặc để chia sẻ với mọi người
Những cỗ máy đã vào phiên bản công khai đầy đủ thì có liên kết vĩnh viễn, nhưng các tác phẩm riêng lẻ không được chọn từ hàng đợi điều phối thì không có liên kết vĩnh viễn
Đây là quyết định có chủ ý nhằm tránh rủi ro lưu trữ nội dung do người dùng tạo chưa được kiểm duyệt trên domain truyện tranh
Nhân tiện, chủ đề này cũng đã được đăng trên HN vào ngày 6 tháng 4 và có 14 bình luận
https://news.ycombinator.com/item?id=39953514
“Không có động lực để cân nhắc kỹ xem nên dán sticker ở đâu. Người chơi không có đủ quyền chủ động để thúc đẩy cốt truyện bằng từng hành động riêng lẻ. Vì vậy, sự sáng tạo bị giới hạn ở các mẫu đơn giản như lặp lại các sticker tương tự thành ô lát hoặc tạo thành đường thẳng.”
À, vậy là trò chơi đã trở thành đời sống công sở ở tập đoàn lớn rồi
Tôi đã tham gia khi nó được công bố. Hình như tôi đã dành khoảng một tiếng để cố làm cho quả bóng đúng đi tới đúng đầu ra một cách đáng tin cậy nhất có thể
Sau khi gửi, tôi tải lại trang thì ở chỗ đó đã là thiết bị của người khác. Phải thừa nhận là nó đẹp hơn, nhưng độ tin cậy thì kém hơn
Giá như họ nói rõ hơn ngay từ đầu rằng cơ chế là như vậy. Và có vẻ tôi không phải người duy nhất không biết danh sách khối xây dựng có thể cuộn được
Giờ thì không còn sức quay lại kiểm tra nữa :(
Wow, tôi và bạn tôi cũng từng nghĩ ra cùng ý tưởng này hồi năm 2014 và triển khai cho Ludum Dare. https://nickfa.ro/wiki/CoinSlot
Thật tuyệt khi thấy ý tưởng đó được trau chuốt hơn và xuất hiện dưới dạng hoạt động tốt
Nó làm tôi nhớ đến thứ này hồi còn trẻ. Tôi đã lãng phí rất nhiều thời gian một cách vô cùng vui vẻ
https://www.myabandonware.com/game/the-incredible-machine-1m...
Có vẻ tôi đã bỏ lỡ điều gì đó, nhưng vì sao một số phần tử trong cỗ máy dường như chỉ ảnh hưởng đến bóng có màu nhất định?
Tôi đoán đó là cơ chế để ngăn các màu bị trộn lẫn hoàn toàn, nhưng hình như bài viết không giải thích chuyện này
Bóng vàng nhẹ và có lực cản không khí lớn, bóng xanh lá nặng, còn bóng đỏ nảy rất mạnh
Nhờ vậy có thể thiết kế các bộ phân loại bằng cơ chế vật lý
Ước gì có cách dễ dàng để kiểm tra xem có cỗ máy nào tôi làm đã vào phiên bản cuối cùng không
Ở thiết kế tiếp theo, có lẽ sẽ ổn nếu lưu tiêu đề các bài gửi trước vào nơi như local storage rồi hiển thị thông báo