The Outbound Message Encoding system is responsible for converting internal domain action.Action objects into JSON messages that conform to the Mjai protocol. This process involves a dispatch mechanism that maps domain-specific action types to their corresponding Mjai message structures, including the optional attachment of debug logs for the AI's decision-making process.
The serialization flow begins with a domain action.Action and ends with a UTF-8 encoded JSON byte slice. The pipeline is split into two distinct phases: conversion to an intermediate message struct and final JSON marshaling.
The ToMessage function serves as the central dispatcher internal/adapter/mjai/outbound/marshal_message.go14-39 It takes a domain action and a log string, performing a type switch to instantiate the correct outbound message struct.
| Domain Action Type | Mjai Message Type | Outbound Constructor |
|---|---|---|
*action.Discard | "dahai" | NewDahai |
*action.Chii | "chi" | NewChi |
*action.Pon | "pon" | NewPon |
*action.CalledKan | "daiminkan" | NewDaiminkan |
*action.ConcealedKan | "ankan" | NewAnkan |
*action.PromotedKan | "kakan" | NewKakan |
*action.Riichi | "reach" | NewReach |
*action.Win | "hora" | NewHora |
*action.Kyushukyuhai | "ryukyoku" | NewKyushukyuhai |
*action.Pass | "none" | NewPass |
Once the domain action is converted into an outbound.Message interface, it is passed to MarshalMessage internal/adapter/mjai/outbound/marshal_message.go10-12 This function utilizes the encoding/json/v2 library internal/adapter/mjai/outbound/marshal_message.go4 to produce the final JSON output sent over the wire (TCP or Stdio).
The following diagram illustrates how domain entities are transformed into the wire format.
"Outbound_Encoding_Flow"
Sources:
Each outbound message struct is designed to match the Mjai protocol specification exactly. All outbound messages include an optional log field, which the AI uses to transmit its evaluation metrics (such as shanten, win probability, or average rank) for debugging purposes.
Discard actions (dahai) and meld calls (chi, pon, daiminkan, ankan, kakan) include fields for the actor, the tile being played (pai), and the tiles being removed from the hand (consumed).
target field indicating which player's discard is being claimed internal/adapter/mjai/outbound/pon_test.go31-33 internal/adapter/mjai/outbound/chi_test.go31-33 internal/adapter/mjai/outbound/daiminkan_test.go35-37type: "ankan" and lists all four tiles in the consumed array internal/adapter/mjai/outbound/ankan_test.go34-36type: "kakan" and includes the tile being added to the existing pon in the pai field internal/adapter/mjai/outbound/kakan_test.go34-36actor (winner), target (loser or self), and the winning pai internal/adapter/mjai/outbound/hora_test.go26-27ryukyoku message with the reason set to "kyushukyuhai" internal/adapter/mjai/outbound/kyushukyuhai_test.go21-22type: "none" is sent internal/adapter/mjai/outbound/pass_test.go21-22The following diagram maps internal Go struct names to their corresponding Mjai JSON "type" fields.
"Action_to_JSON_Mapping"
Sources:
A key feature of the outbound package is the consistent handling of the log field. Every message constructor (e.g., NewPass, NewDahai) accepts a log string internal/adapter/mjai/outbound/marshal_message.go14-35 If this string is non-empty, it is included in the serialized JSON internal/adapter/mjai/outbound/pon_test.go55 This allows the engine to output its internal "thought process" alongside its actions, which is essential for analyzing the ManueAgent's decisions in game viewers or logs.
Example of a Pon action with logs:
Sources: