Thử nghiệm dựa trên mô hình cho Dungeons & Dragons
AI/ML·Hacker News·0 lượt xem

Thử nghiệm dựa trên mô hình cho Dungeons & Dragons

Model-Based Testing for Dungeons & Dragons

Lần trước, tôi đã lập mô hình một ký tự D&D. Một sinh vật, đứng một mình trong khoảng không, theo dõi điểm trúng đích, điều kiện và khe cắm phép thuật. Thông số Quint (một ngôn ngữ mô hình hóa chính thức) cho điều đó là khoảng...

Lần trước, tôi đã lập mô hình một ký tự D&D. Một sinh vật, đứng một mình trong khoảng không, theo dõi điểm trúng đích, điều kiện và khe cắm phép thuật. Thông số Quint (ngôn ngữ lập mô hình chính thức) cho thông số đó là khoảng 6.000 dòng.

Bây giờ tôi đã thêm chiến đấu. Không chỉ là kiểu chiến đấu “Chiến binh đánh yêu tinh bằng kiếm”, mà còn là phần khó: Chuỗi phản bùa, Lá chắn làm gián đoạn giữa đòn tấn công, phép thuật sẵn sàng giữ sự tập trung, lật ngược Kháng chiến huyền thoại để cứu.

Vì vậy, tôi đã làm phần khó trước tiên. Không có nó, bạn không thể chứng minh được khả năng tồn tại đầy đủ. Một thông số kỹ thuật xử lý các trường hợp đơn giản không chứng minh được điều gì và gần giống với “dự án Ứng dụng Todo”; những kẻ chặn thực sự sống trong các tương tác. Nếu chuỗi phản thần chú có thể làm tắc nghẽn máy trạng thái hoặc việc tập trung thần chú sẵn sàng có thể làm mất đi các hiệu ứng kích hoạt, thì tôi cần biết ngay bây giờ chứ không phải sau khi xây dựng thêm ba lớp trên cùng.

Thông số trận chiến chưa bằng một nửa mã sinh vật. Bạn có thể nghĩ rằng độ phức tạp của nó chưa bằng một nửa nhưng điều đó không đúng.

Không gian trạng thái của một sinh vật đơn lẻ là một bộ thu gọn phẳng. Một trang ký tự được tôn vinh. Tăng cấp, nhận sát thương, đạt được một điều kiện, mất một điều kiện. Không gian trạng thái rộng nhưng bằng phẳng. Chiến đấu làm cho nó sâu sắc. Hai sinh vật tương tác với nhau có nghĩa là mọi hành động đều có thể kích hoạt phản ứng, mọi phản ứng đều có thể kích hoạt phản ứng ngược và toàn bộ sự việc sẽ diễn ra trước khi bất kỳ ai bị sát thương. Đây là nơi mà những người mới bắt đầu và ngay cả những người chơi thành thạo thường bị nhầm lẫn.

Đây là những gì thông số kỹ thuật hiện bao gồm: tất cả các lớp nhân vật (12 trong số đó), 14 điều kiện sửa đổi những gì bạn có thể và không thể làm. Sinh vật huyền thoại hành động theo lượt của sinh vật khác. Phép phản công làm gián đoạn việc niệm phép. Các đòn tấn công cơ hội kích hoạt giữa chuyển động và kích hoạt chuỗi giải pháp tấn công đầy đủ. Cả nhân vật của người chơi và quái vật, cùng với danh sách nhân vật và khối chỉ số.

Bản demo tái hiện từng bước một trận chiến phức tạp theo kịch bản: Quả cầu lửa, chuỗi phản thần chú, sát thương diện rộng, cứu mạng. Tiến lên, lùi lại hoặc nhấn phát và xem các cửa sổ gián đoạn mở ra và giải quyết.

Lần trước đó là một sinh vật trong khoảng không. Giờ đây, đó là tất cả các lớp, điều kiện và khuôn khổ để thêm bất kỳ phép thuật hoặc kỳ tích nào cho dù từ SRD1 hay sách đã xuất bản. Cuối cùng, bằng chứng về khái niệm đã qua rồi!

Chuỗi ngắt

Một cuộc tấn công đơn lẻ không phải là một sự kiện đơn lẻ.

Khi một sinh vật tấn công sinh vật khác, các quy tắc sẽ mở ra một loạt cửa sổ gián đoạn — những khoảnh khắc mà người tham gia có thể phản ứng và thay đổi kết quả ở giữa độ phân giải. Mỗi người có thể phân nhánh trạng thái trò chơi.

Giai đoạn 1: Cuộc tấn công thành công. d20 tiếp đất, DM so sánh nó với Lớp Giáp — trúng hoặc trượt. Nhưng mục tiêu có thể sử dụng Khiên (+5 AC, có khả năng khiến đòn đánh trượt). Lời cắt của Bard sẽ trừ một con súc sắc khỏi cuộn. Tất cả điều này xảy ra sau lần tung nhưng trước lượt truy cập được xác nhận.

Giai đoạn 2: Thiệt hại trên đất liền. Kẻ tấn công tiếp đất. Xúc xắc được tung ra. Nhưng Rogue có Uncanny Dodge - giảm một nửa sát thương đó để phản ứng. Các đòn tấn công làm chệch hướng của một nhà sư trừ đi 1d10 cộng với mức độ và mức sửa đổi Khéo léo của họ. Chúng bắn sau đòn đánh nhưng trước khi gây sát thương.

Giai đoạn 3: Một phép thuật đang được thi triển. Phép thuật mở ra cửa sổ riêng của nó: bất kỳ sinh vật nào có Phản phép đã chuẩn bị sẵn và có phản ứng đều có thể buộc lưu Hiến pháp. Nếu người thực hiện thất bại, câu thần chú sẽ biến mất. Sử dụng một khe đủ cao và nó sẽ tự động thành công. Bản thân Phản thần chú là một câu thần chú, vì vậy một sinh vật khác có thể Phản thần chú. Trong thông số kỹ thuật, đây là cấu trúc dữ liệu ngăn xếp theo nghĩa đen — đẩy, giải quyết, pop:

var bSpellStack: List[SpellStackEntry] // đẩy vào diễn viên, bật vào quyết tâm

Giai đoạn 4: Một pha cứu thua không thành công. Mục tiêu thực hiện cú ném cứu thua của họ không thành công. Nhưng Rồng cổ có Khả năng kháng cự huyền thoại - nó tiêu tốn một khoản phí và việc cứu sẽ tự động thành công. Điều này kích hoạt sau kết quả lưu nhưng trước khi áp dụng hiệu ứng lỗi.

Bây giờ xếp các lớp này lên nhau.

Rogue di chuyển khỏi sinh vật kẻ thù. Kẻ thù có phản ứng không được sử dụng nên sẽ nhận được một đòn tấn công cơ hội - một đòn tấn công cận chiến (ngay cả khi kẻ tấn công có thuộc tính tấn công nhiều lần, hãy nhớ tôi!). Đòn tấn công đó bước vào Giai đoạn 1 (Rogue có thể sử dụng Khiên không?), Sau đó là Giai đoạn 2 (Uncanny Dodge?), Sau đó là các hiệu ứng sau sát thương (kiểm tra nồng độ xem Rogue có đang tập trung vào một câu thần chú hay không). Tất cả điều này xảy ra giữa chuyển động, trước khi Rogue đến đích.

Một Pháp sư đồng minh sử dụng Giữ Quái vật lên Rồng. Giai đoạn 3: Cửa sổ phản bùa. Tay sai của Rồng cố gắng chống lại nó. Đồng minh thứ hai phản đòn. Ngăn xếp bật lên: Counterspell bên trong được giải quyết trước tiên, sau đó là phản ứng bên ngoài. Phép thuật đi qua. Con rồng không cứu được nó. Giai đoạn 4: Kháng chiến huyền thoại. Con Rồng đốt cháy một khoản phí. Đã lưu thành công.

Sau khi tất cả những điều này được giải quyết, máy trạng thái sẽ quay trở lại vị trí nào? Nó phụ thuộc vào những gì đã bắt đầu nó. Một cuộc tấn công trong lượt của bạn sẽ tiếp tục lượt. Một đòn tấn công cơ hội đang di chuyển sẽ tiếp tục chuyển động. Một phép thuật AoE sẽ di chuyển đến mục tiêu tiếp theo. Độ phân giải thiệt hại là giống nhau trong mọi trường hợp - chỉ có điểm tiếp tục là khác nhau. Trong thông số kỹ thuật, mỗi nguồn mang địa chỉ trả về riêng dưới dạng liên kết được gắn thẻ:

loại AfterDamageReturn =
 | ADRActiveTurn
 | ADRGiải quyếtAoE(AoESpellCtx)
 | ADRResolutionMovement(MovementCtx)
 | ADRAwaitingLegendaryAction(LAWindowCtx)
 | ADRawaingReadiedAction(ReadyWindowCtx)

Mỗi khi máy trạng thái đi vào một chuỗi ngắt, nó sẽ mang theo địa chỉ trả về. Khi chuỗi giải quyết — tất cả các phản ứng được đưa ra, tất cả thiệt hại được áp dụng, tất cả các lần kiểm tra nồng độ được thực hiện — nó khớp với mẫu trên thẻ đó và tiếp tục đúng giai đoạn.

Máy XState phản chiếu cùng loại và cùng mẫu khớp:

// battle-machine-types.ts
loại AfterDamageReturn =
 | { tag: "ADRActiveTurn" 
 | { tag: "ADRResolutionAoE"; aoe: AoESpellCtx 
 | { tag: "ADRResolvingMovement"; mv: MovementCtx 
 | { tag: "ADRAwaitingLegendaryAction"; la: LAWindowCtx 
 | { tag: "ADRAwaitingReadiedAction"; sẵn sàng: ReadyWindowCtx 

// battle-machine-helpers.ts
hàm returnToState(r: AfterDamageReturn): PhaseFields {
 return Match.value(r).pipe( // hiệu ứng/Khớp: khớp mẫu đầy đủ
 byTag("ADRActiveTurn", () => PHASE_ACTIVE),
 byTag("ADRResolutionAoE", (v) => giai đoạnResolutionAoE(v.aoe)),
 byTag("ADRResolutionMovement", (v) => giai đoạnResolutionMovement(v.mv)),
 // ...
 Match.exhaustive // không có nhánh mặc định — trình biên dịch từ chối các biến thể bị thiếu
 )

Khớp mẫu 5 phần trên thẻ để quyết định vị trí tiếp theo. XState cũng thực hiện tương tự thông qua Match.exhaustive — điều này cũng có nghĩa là việc thêm một biến thể trả về mới là một lỗi biên dịch cho đến khi mọi trang gọi đều xử lý được nó. MBT so sánh bối cảnh sau mỗi bước: nếu máy XState định tuyến sai giai đoạn, dấu vết sẽ phân kỳ và hạt giống sẽ được ghi lại.

Đây là lý do tại sao thông số kỹ thuật chính thức lại quan trọng. Các tương tác ngắt tạo ra một không gian trạng thái tổ hợp. Bộ fuzzer Quint khám phá nó; bài kiểm tra đơn vị bao gồm các đường dẫn bạn nghĩ đến.

Thông số kỹ thuật mô hình hóa tất cả những điều này. Nhưng nó không lập mô hình mọi thứ.

Thông số kỹ thuật và DM là gì

Thông số kỹ thuật không lập mô hình mọi thứ. Nó mô hình hóa mọi thứ theo cơ chế xác định — với cùng một thông tin đầu vào, sẽ có chính xác một câu trả lời đúng. Mọi thứ khác đều nhập thông số dưới dạng thông tin đầu vào do người gọi cung cấp.

Xúc xắc là trường hợp đơn giản nhất. Thông số kỹ thuật không bao giờ tạo ra số ngẫu nhiên. Người gọi chuyển các cuộn dưới dạng đối số, đã được giải quyết. Thông số kỹ thuật chứng minh rằng với bất kỳ cuộn nào, cơ chế xuôi dòng đều đúng. Tính ngẫu nhiên là vấn đề của người gọi.

Bìa hoạt động theo cách tương tự. Thông số kỹ thuật nhận được nó dưới dạng enum được gõ: NoCover | HalfCover | Ba Phần TưCover | TotalCover. Nó chứng minh tiền thưởng tiết kiệm AC và DEX cho mỗi cấp độ. Việc cây cột đó cấu thành một nửa mái che hay 3/4 mái che là cách DM sử dụng hình học.

Sự thật thú vị: theo truyền thống, hình học chiến đấu của Dungeon và Dragon là phi Euclide.

Các cuộc tấn công cơ hội đẩy điều này đi xa hơn. Khi một sinh vật di chuyển qua khu vực bị đe dọa, thông số kỹ thuật sẽ nhận được một bộ sinh vật đe dọa làm thông tin đầu vào, sau đó kiểm tra: đưa ra bất kỳ bộ nào, quy trình OA có giải quyết chính xác không? Nó không bao giờ hỏi "ai thực sự ở trong vòng 5 feet?" - đó là không gian, một vấn đề hoàn toàn khác. Thông số kỹ thuật chứng minh tính kinh tế (tái) hành động và khả năng giải quyết cuộc tấn công hoạt động đối với mọi cấu hình mối đe dọa mà người gọi cung cấp.

Đây là sự lựa chọn kiến trúc có chủ ý chứ không phải sự trì hoãn. D&D hỗ trợ cả kịch tính (không có bản đồ, khoảng cách là tường thuật) và bản đồ chiến đấu dựa trên lưới. DM chọn một hoặc ứng biến một kết hợp. Thông số kỹ thuật cam kết với mô hình không gian là sai đối với một nửa số bảng. Phương pháp của chúng tôi lấy thông tin không gian làm đầu vào và chứng minh cơ chế chính xác bất kể DM thể hiện không gian như thế nào.

Mẫu này khái quát hơn D&D. Bất kỳ miền nào có quy tắc chính thức cũng có điểm quyết định của con người. Thông số kỹ thuật chứng minh các bộ phận cơ khí chính xác bất kể con người quyết định thế nào.

MBT thực sự đã bắt được những gì

Vì vậy: thông số kỹ thuật mã hóa những gì quy tắc quy định. Máy XState mã hóa hành vi thời gian chạy. Khi hai người đó không đồng ý, cầu thử nghiệm dựa trên mô hình sẽ nắm bắt được.

Đêm làm mờ đầu tiên đã gây ra hàng nghìn rưỡi thất bại. Tất cả đều xuất phát từ một nguyên nhân cốt lõi: máy XState không lấy được các ô thần chú từ các cấp độ lớp. Mọi người làm bùa chú đều bắt đầu với số không. Các bài kiểm tra viết tay không bao giờ phát hiện ra điều đó - chúng cung cấp các giá trị vị trí rõ ràng trong đồ đạc. Bộ làm mờ đã sử dụng dữ liệu thực tế của lớp và ngay lập tức nhận thấy rằng không ai có thể truyền bất cứ thứ gì.

Đã triển khai bản sửa lỗi. Lỗi mới xuất hiện. Sau đó nhiều hơn nữa. Đây là đêm hôm đó với các bản sửa lỗi tự động do Claude Code thực hiện:

20:06 Bắt đầu làm mờ. 1.447 thất bại. Một nguyên nhân cốt lõi: khe cắm chính tả init.
Sửa lỗi 21:16. Tất cả ba triệu chứng phụ (vị trí, vị trí hiệp ước, xúc xắc) đều đã được giải quyết.
21:32 Lỗi mới: phân kỳ cứu mạng, cờ Phục hồi phức tạp.
23:07 Thiếu đòn tấn công bổ sung của Paladin — cây cầu đã quên Paladin trong danh sách.
00:36 Phục hồi phức tạp: cờ không được đặt khi ô mục tiêu đã đầy.
01:08 Defy Death: thông số kỹ thuật đã vượt qua cấp độ tổng, không phải cấp độ Máy bay chiến đấu.
01:38 Chu kỳ làm sạch đầu tiên.
02:08 Chín chu kỳ sạch liên tiếp. Im lặng cho đến sáng.

Mỗi bản sửa lỗi sẽ làm lộ ra lớp tiếp theo. Dưới đây là các loại lỗi liên tục xuất hiện:

Hoán đổi đối số cùng loại. computeTakeDamage nhận miễn dịch, kháng cựlỗ hổng dưới dạng tham số riêng biệt. Cả ba đều là ReadonlySet<DageType>. Hoán đổi hai và TypeScript không nhấp nháy2. Sinh vật của bạn bây giờ có khả năng “chống lại” những gì nó dễ bị tổn thương. MBT đã phát hiện ra điều này ở 19 hạt giống — mọi tính toán sát thương trong trò chơi đều sai.

Thông số kỹ thuật sai chứ không phải cách triển khai. Defy Death là một tính năng của Champion Fighter: lượt cứu mạng từ 18-19 được tính là 20 giây tự nhiên. Thông số kỹ thuật đã vượt qua config.level (tổng cấp độ ký tự) thay vì config.classLevels.get(Fighter). Một Barbarian cấp 18 có được tính năng của Nhà vô địch. Máy XState thực sự đúng - nó sử dụng cấp độ máy bay chiến đấu. MBT đã tìm thấy sự khác biệt và chỉ vào thông số kỹ thuật. Điều này phá vỡ câu chuyện thông thường: “nguồn sự thật” có lỗi.

Quint thông số kỹ thuật của sự thậtXState machineruntime performancespec bắt được lỗi implsimpl bắt lỗi thông sốMBT

Bộ làm mờ bất biến phát hiện các vi phạm quy tắc. Chuỗi Counterspell có lỗi về thời gian chi tiêu trong ô. Việc chi tiêu vị trí được hoãn lại cho đến khi giải quyết được, do đó, khi sinh vật A sử dụng phép thuật phản thần chú Fireball và D, slotExpendsThisTurn của A vẫn sai — khiến A đủ điều kiện để phản thần chú với vị trí giây trong cùng một lượt. spellStackDistinctCasters bất biến đã bắt được sáu hạt giống trong đêm đầu tiên. Đây không phải là lỗi mã. Đó là một lỗi thiết kế ở cấp độ thông số kỹ thuật đã vi phạm quy tắc "một khe phép thuật mỗi lượt" của SRD.

Nuốt im lặng qua các nhánh mặc định. Một câu lệnh switch trong resolveSpellEntry có một nhánh default đã âm thầm ăn PCECountersspell mục trong chuỗi Counterspell sâu. Phép thuật biến mất. Không có lỗi, không có sự cố, chỉ là hành vi sai. Việc chuyển sang effect/Match bằng Match.exhaustive (cấm các nhánh mặc định) đã giết chết toàn bộ lớp lỗi này tại thời gian biên dịch3.

Đồng bộ hóa trạng thái giữa các nhánh. XState mô hình các vùng trạng thái song song: theo dõi thiệt hại, chuyển pha, niệm phép chạy đồng thời. Một sinh vật ổn định thông qua việc chữa lành đầu lượt ở một nhánh trong khi một nhánh khác vẫn chấp nhận cứu sống. Sinh vật chết đã ngừng xử lý các hiệu ứng do nghỉ trong đó continue thuộc về — bỏ qua tất cả các hiệu ứng còn lại sau hiệu ứng đầu tiên.

Các tầng hàm ý tình trạng. Bị tê liệt ngụ ý Không có năng lực. Loại bỏ Bị tê liệt và Mất khả năng hoạt động sẽ nhấc lên — trừ khi sinh vật này cũng Bị choáng, điều này ngụ ý một cách độc lập là Mất khả năng hoạt động. Thông số kỹ thuật này theo dõi các nguồn ngụ ý dưới dạng một tập hợp: incapacitatedSources: Set[IncapSource]. Máy XState đã sử dụng mẫu trải rộng Set ([...set].filter(...)) âm thầm không thể xóa nguồn trên một số đường dẫn nhất định. Sinh vật này vẫn không còn khả năng hoạt động sau khi tất cả các nguồn đã biến mất. Một bất biến xuôi dòng (incapNotConcentrated, khẳng định sinh vật mất khả năng tập trung không thể tập trung) đã phát hiện ra sự mâu thuẫn: một sinh vật đang tập trung vào một câu thần chú trong khi máy cho biết nó không còn khả năng tập trung.

Đây không phải là các trường hợp ngoại lệ mà là hệ quả thông thường của không gian trạng thái quá lớn để có thể liệt kê bằng tay.


12.700 đối số, được kiểm tra bằng máy

Các lỗi trên là những lỗi mà thông số kỹ thuật tìm thấy trong bản triển khai của tôi. Quy trình QA tìm thấy các lỗi mà người chơi tìm thấy trong quy tắc.

Tôi đã thu thập được khoảng 12.700 mục Q&A của cộng đồng: chủ đề r/onednd của Reddit, câu hỏi RPG Stack Exchange, Bản tóm tắt lời khuyên của Sage. Mỗi cái đi qua một băng tải: LLM phân loại nó (“đây có phải là câu hỏi về Quy tắc dưới dạng văn bản không?”). LLM thứ hai chuyển câu trả lời được chấp nhận thành khẳng định Quint. Xác nhận được kiểm tra đánh máy và quint test chạy xác nhận đó. Đạt hoặc trượt.

Gần một nghìn người sống sót để trở thành xác nhận có thể chạy được4. The rest either aren’t RAW questions or the LLM’s generated code fails to typecheck. Tỷ lệ thất bại ~ 40% đó là ổn - thông số kỹ thuật là bộ lọc. Nếu Claude phát minh ra một hàm không tồn tại, máy đánh máy sẽ phát hiện ra hàm đó. Nếu nó giải thích sai một phán quyết, thông số kỹ thuật sẽ không đồng ý. Dù bằng cách nào: tín hiệu.

Prone Lock là đơn giản nhất. Nắm lấy một sinh vật, đẩy nó nằm sấp. Tốc độ của nó giảm xuống 0 và việc đứng thẳng sẽ khiến bạn mất một nửa tốc độ - tức là một nửa bằng 0. Ba lệnh gọi hàm xác nhận khóa: vật lộn thành công, đẩy thành công, tốc độ hiệu quả == 0. Sinh vật không thể di chuyển cho đến khi vật lộn kết thúc.

Grapp Leapfrog. Một thử nghiệm ban đầu từ khi dự án vẫn sử dụng các quy tắc năm 2014. Hai người chơi cách nhau 60 feet với tốc độ mỗi người chỉ 30 feet. Bob vật lộn với Alice (cô ấy sẵn sàng thất bại). Theo quy định năm 2014, tốc độ của anh ấy giảm đi một nửa khi kéo. Anh ta lao tới để nhân đôi nó lên 30, bế cô ấy đi 30 feet, thả ra. Đến lượt Alice: điều tương tự, hướng ngược lại. Độ dịch chuyển ròng: 60 feet. Quy tắc năm 2024 đã thay đổi cách vật lộn thành chi phí di chuyển thêm 1 foot mỗi foot, điều này sẽ chấm dứt hoạt động khai thác kéo và thả.

chạy qa_grapple_leapfrog = {
 val bob = Sinh vật tươi(30)
 val alice = Sinh vật tươi(30)
 // Quy tắc năm 2014: tốc độ giảm một nửa khi vật lộn cùng kích thước sinh vật
 val bobTurn = pStartTurn(TEST_CONFIG, bob, Unarmored, 0, true, false)
 val bobDashed = pUseAction(bobTurn, bob, ADash) // 15 + 15 = 30
 val aliceTurn = pStartTurn(TEST_CONFIG, alice, Unarmored, 0, true, false)
 val aliceDashed = pUseAction(aliceTurn, alice, ADash)
 khẳng định(bobDashed.movementRemaining + aliceDashed.movementRemaining == 60)

Ogre vs. Giáp hoạt hình. Giáp hoạt hình là một quái vật kiến trúc — Sức mạnh thấp, AC cao. Các quy tắc năm 2024 đã thay đổi việc vật lộn từ một bài kiểm tra Điền kinh đối lập thành một cú ném cứu thua trước một DC phẳng: 8 + công cụ sửa đổi STR + trình độ thành thạo. Theo luật lệ cũ, môn Điền kinh của Yêu tinh khiến việc giành lấy trở nên khó khăn. Theo quy tắc mới, điều quan trọng là phần thưởng tiết kiệm của mục tiêu. Một con yêu tinh với sự khéo léo khiêm tốn rất dễ bị tóm gọn. Áo giáp hoạt hình, mặc dù nhìn chung yếu hơn, nhưng có khả năng cứu thua tốt hơn. Cộng đồng đã nhận thấy sự đảo ngược này.

// DC = 8 + STR mod + mức độ thành thạo = 8 + 3 + 2 = 13
// Ogre: DEX tiết kiệm +0 → cần 13+ trên d20, không thành công ở hầu hết các lần quay
// Giáp hoạt hình: Tiết kiệm STR +4 → cần 9+, thành công thường xuyên
pGrappleShoveDC(strMod, profBonus) = 8 + strMod + profBonus

Cùng một người vật lộn, cùng một DC, kết quả rất khác nhau tùy thuộc vào phần thưởng tiết kiệm của mục tiêu thay vì chỉ số chiến đấu của mục tiêu.

Quy trình không yêu cầu riêng các chuỗi Reddit. Tôi đã thử nghiệm cung cấp cho nó các bản ghi phiên trò chơi - bản ghi phiên chơi thực tế trong đó DM đưa ra phán quyết giữa trận chiến. Lộn xộn hơn Stack Exchange, nhưng đó là những quy tắc mà mọi người thực sự tuân theo.

Bề mặt tính năng

Thông số kỹ thuật có 62 bất biến về an toàn. Họ xứng đáng được xem xét kỹ hơn. Bạn đã biết bản năng đằng sau chúng — bạn chỉ cần gọi nó bằng những tên khác nhau tùy thuộc vào vị trí của bạn trong ngăn xếp.

Bạn viết assert() ở đầu hàm. Điều kiện tiên quyết, hậu điều kiện, “điều này không bao giờ có giá trị rỗng ở đây.” TigerStyle của TigerBeetle là phiên bản có quy tắc nhất về điều này: các xác nhận được ghép nối tại địa điểm cuộc gọi và định nghĩa, kiểm tra cả điều nên và điều không nên là đúng. Nguyên tắc của họ: các xác nhận hạ cấp các lỗi về tính đúng đắn thảm khốc thành các lỗi về sự sống động. Sụp đổ sớm, tham nhũng không có gì. Bản năng tương tự: nêu điều gì đó phải giữ, sẽ hủy nếu không.

Bạn đặt lược đồ Zod5 tại ranh giới API của mình. Bản năng giống nhau, lớp khác nhau. Bạn không kiểm tra logic. Bạn đang kiểm tra hình dạng. Đúng cấu trúc, đúng loại, giá trị trong phạm vi. Trình phân tích cú pháp chạy một lần, ở rìa và mọi thứ ở phía dưới đều tin cậy đầu ra. Đó là cổng chứ không phải màn hình.

Bạn viết bài kiểm tra dựa trên thuộc tính. Bản năng tương tự, được nhân rộng. Thay vì một ví dụ, bạn tạo ra hàng nghìn đầu vào ngẫu nhiên và kiểm tra xem một thuộc tính có phù hợp với tất cả chúng hay không. Tôi đã sử dụng phương pháp này để thử nghiệm lưu trữ biểu đồ thời gian dựa trên thuộc tính. Hạt giống xác định, hàng triệu kịch bản, thuộc tính như lời tiên tri. Bộ làm mờ Quint MBT hoạt động theo cách tương tự: dấu vết ngẫu nhiên, có thể tái tạo hạt giống, các thuộc tính được kiểm tra ở mỗi bước.

Bất biến Quint là bản năng tương tự được đưa đến giới hạn của nó. Trình kiểm tra mô hình không lấy mẫu. Nó khám phá mọi trạng thái có thể tiếp cận mà thông số kỹ thuật có thể tạo ra. “HP không bao giờ vượt quá mức tối đa” không được thử nghiệm với hàng nghìn sinh vật ngẫu nhiên. Nó đã được chứng minh cho mọi sinh vật mà thông số kỹ thuật có thể tạo ra. Đó là bước nhảy vọt: từ “điều này được giữ trên tất cả các đầu vào mà tôi đã thử” đến “điều này được giữ ở mọi trạng thái mà hệ thống có thể đạt tới.”

Nếu bạn đã xác thực ở ranh giới và xác nhận trong thời gian chạy thì bạn sẽ biết bất biến là gì. Thông số kỹ thuật chỉ kiểm tra chúng trên mọi trạng thái mà hệ thống có thể đạt tới thay vì các trạng thái mà thử nghiệm của bạn sẽ truy cập.

Mười hai lớp, cấp độ 1 đến 20, mỗi lớp có lớp con SRD. Mười bốn điều kiện với chuỗi hàm ý - áp dụng theo sau Người bị liệt và Người mất năng lực; loại bỏ Người bị liệt và hệ thống sẽ biết Người mất năng lực nên ở lại hay đi. Sinh vật huyền thoại với Hành động huyền thoại, Kháng chiến huyền thoại và khả năng Nạp tiền. Thông thạo vũ khí bao gồm tất cả tám thuộc tính của 38 loại vũ khí. Quy tắc môi trường: rơi, nghẹt thở, tầm nhìn và ánh sáng, chiến đấu dưới nước. Sáu mươi hai bất biến an toàn bảo vệ toàn bộ sự việc.

bảng tính năng đầy đủ có thông tin chi tiết. Đã qua giai đoạn chứng minh khái niệm.

Tại sao điều này tồn tại và nó sẽ đi đến đâu

SRD 5.2.1 được phát hành theo CC-BY-4.0 — cho phép nội dung trò chơi dễ dàng một cách bất thường. Đó là lý do tại sao dự án này có thể thực hiện được. Nhưng SRD là tập hợp con của Sổ tay người chơi đầy đủ: một lớp con cho mỗi lớp, hầu như không có chiến công, một phần nhỏ phép thuật. Wizards of the Coast xuất bản đủ để xây dựng, không đủ để thay thế cuốn sách. Thông số kỹ thuật mô hình những gì SRD cung cấp cho chúng tôi. Kiến trúc được thiết kế sao cho nội dung không phải SRD — các lớp con bổ sung, chiến công, phép thuật từ PHB hoặc homebrew — có thể cắm vào lớp cơ học mà không cần chạm vào thông số kỹ thuật cốt lõi. Cơ học tồn tại ở Quint, nội dung tồn tại ở TS.

Thông số kỹ thuật không gắn liền với một lần triển khai. Mô hình Quint là nguồn gốc của sự thật; XState tình cờ là thời gian chạy đầu tiên phản chiếu nó. Nhưng các dấu vết tương tự xác thực XState có thể xác thực bất kỳ triển khai nào - Rust, Go, một plugin của Godot. Cầu MBT không quan tâm mục tiêu được viết bằng ngôn ngữ nào. Nó tạo ra dấu vết từ thông số kỹ thuật và so sánh trạng thái. Nếu các trạng thái khớp nhau thì việc triển khai đã tuân thủ.

Điều này trở nên thú vị với tác nhân mã hóa. Một tác nhân có thể đọc thông số Quint, tạo bản triển khai bằng bất kỳ ngôn ngữ nào và quy trình MBT ngay lập tức cho nó biết liệu đầu ra có đúng hay không — không phải “biên dịch” chính xác, không “vượt qua các bài kiểm tra mà tôi đã viết” đúng, nhưng “khớp với thông số chính thức trên hàng nghìn dấu vết ngẫu nhiên” chính xác. Vòng phản hồi rất dễ sử dụng: tạo, chạy dấu vết, sửa lỗi phân kỳ, lặp lại. Thông số kỹ thuật là lời tiên tri mà tác nhân tự kiểm tra. Bộ khai thác tác nhân hoàn hảo.

Không phải dự án nào cũng cần thông số kỹ thuật chính thức. Điều này đã làm được vì các quy tắc đã được viết ra nhưng các tương tác là vấn đề kết hợp, tính chính xác và một cộng đồng lớn đã tìm ra các trường hợp đặc biệt. Quá trình triển khai là một máy trạng thái, ánh xạ tự nhiên tới các thông số kỹ thuật của dòng TLA.

Đây không phải là lần thử đầu tiên. MongoDB published their TLA+ conformance checking work in June 2025. Spec as oracle, trace replay against implementation. Chúng tôi làm điều tương tự với Quint và XState, trên một miền có cộng đồng Q&Một kho dữ liệu cung cấp xác thực thông số kỹ thuật.

Các miền khác phù hợp với mẫu: quy định tài chính, triển khai giao thức, cây quyết định y tế. Bất cứ nơi nào có quy tắc bằng văn bản, tương tác tổ hợp và yêu cầu về tính chính xác.

Dùng thử



D&D là thương hiệu của Wizards of the Coast. Đây là nội dung không chính thức của người hâm mộ được cho phép theo Chính sách nội dung của người hâm mộ.

Chú thích cuối trang

  1. Tài liệu tham khảo hệ thống 5.2.1 — Wizards of the Coast xuất bản các quy tắc D&D cốt lõi trong CC BY 4.0 nên bất kỳ ai cũng có thể xây dựng dựa trên chúng.

  2. Trên thực tế, các loại có thương hiệu có thể nắm bắt được điều này tại thời điểm biên dịch — tạo ra Miễn nhiễm, Các mức kháng cựCác lỗ hổng bảo mật có cấu trúc giống hệt nhau nhưng khác biệt về mặt danh nghĩa và việc hoán đổi trở thành một lỗi loại. Thay vào đó, chúng tôi đã phát hiện ra nó ở lớp MBT, nhưng vẫn tồn tại bản sửa lỗi cấp loại.

  3. Tôi đã viết về lý do tại sao các nhánh mặc định lại nguy hiểm và cách kết hợp toàn diện ngăn chặn loại lỗi này trong Never Have I Ever.

  4. Lần chạy này trái ngược với phiên bản đầu tiên của thông số kỹ thuật (sinh vật đơn lẻ, không có chiến đấu). Thông số kỹ thuật chiến đấu hiện bao phủ nhiều bề mặt hơn, do đó, một lần chạy mới có thể sẽ tạo ra nhiều khẳng định hơn. Tôi đã đốt tất cả mã thông báo claude của mình trong tuần này, vì vậy đó là một bài tập trong tương lai.

  5. Zod là thư viện xác thực lược đồ dựa trên TypeScript. Bạn khai báo hình dạng dữ liệu của mình, đồng thời Zod phân tích và xác thực dữ liệu đó trong thời gian chạy — biến đầu vào không xác định thành đầu ra được nhập và đáng tin cậy. Zod là phổ biến nhất nhưng không phải là duy nhất.

Tác giả: Firfi

#discussion