Seeking Guidance on RankDir (Layout)

Below I am trying to model a state diagram as FSA/FSM I have just started using GraphViz in the hope of keeping the state diagrams as source. The layout I want is eluding me. I would like the subgraph clusterPreGame and clusterGame to have a rankDir of “TB” but these are inherited from the parent graph as “LR”. Any guidance on better layout algorithms or techniques is appreciated.

Setup: I am rendering this using vscode plugin “Graphviz Preview” [EFanZh] which renders a preview on MacOS via a brew installed implementation of dot.

any help appreciated.

[dot]

digraph cardtable {
    size=5.5
    node [ shape=record]
    compound=true;
    rankdir="LR";

    
    subgraph clusterPreGame {
        label="Pregame";
        rank=same;
        InRoom [ label="\<\<State\>\> \n InRoom | bool:public"];
        InLobby[ label="\<\<State\>\> \n InLobby"];
        InSelector[ label="\<\<State\>\> \n InGameSelector | enum: local \| remote"];   
    }

    subgraph clusterGame {
        label="Game";
        subgraph clusterDealing {
            label="Cut For Deal";
            CutForDeal;
            AwaitDeal;
            CutForDeal -> AwaitDeal;
     }

      subgraph clusterNthState {
            nthStateNode;
            label="nthState";
            rankdir="TB";
            rank=same;
        }
    }

    InLobby -> InSelector -> InRoom;
    InRoom -> CutForDeal [ lhead=clusterDealing label="startGame"];
    AwaitDeal -> nthStateNode[ label="nthTransition" lhead=clusterNthState];
    
}

[/dot]

digraph cardtable {
    size=5.5
    node [ shape=record]
    compound=true;
    rankdir="LR";

    
    subgraph clusterPreGame {
        label="Pregame";
        rank=same;
        InRoom [ label="\<\<State\>\> \n InRoom | bool:public"];
        InLobby[ label="\<\<State\>\> \n InLobby"];
        InSelector[ label="\<\<State\>\> \n InGameSelector | enum: local \| remote"];   
    }
    subgraph clusterGame {
        label="Game";
        subgraph clusterDealing {
            label="Cut For Deal";
            CutForDeal;
            AwaitDeal;
            CutForDeal -> AwaitDeal;

      subgraph clusterNthState {
            nthStateNode;
            label="Numerals";
            rankdir="TB";
            rank=same;
        }
     }
        
        
    }

    InLobby -> InSelector -> InRoom
    InRoom -> CutForDeal [ lhead=clusterDealing label="startGame"];
    WaitForDeal -> nthStateNode[ lhead=clusterNthState label="nthTransition"];


    
    
    
}

Unfortunately, rankdir is only applicable in the root graph. Second, the implementation of rankdir=LR lays out the graph using rankdir=TB and then rotates the layout 90 degrees counterclockwise. This means that flat edges end up pointing up rather than down. Also, using shape=record should only be used in the simplest cases. In particular, record shapes don’t work well with rankdir. The preferred construct is HTML-like labels. I have supplied a solution below. As a final “unfortunately,” it appears to trigger a bug where an edge is drawn using two splines.

digraph cardtable {
    compound=true;
    rankdir="LR";
    newrank="true";

    subgraph clusterPreGame {
        label="Pregame";
        rank=same;
        InRoom  [margin=0 shape=none label=< <table cellborder="0"><tr><td>&lt;&lt;State&gt;&gt; <BR/> InRoom </td></tr> <hr/> <tr><td>bool:public</td></tr></table> >];
        InLobby  [margin=0 shape=box label=< &lt;&lt;State&gt;&gt; <BR/> InLobby >];
        InSelector  [margin=0 shape=none label=< <table cellborder="0"><tr><td>&lt;&lt;State&gt;&gt; <BR/> InGameSelector </td></tr> <hr/> <tr><td>enum: local | remote</td></tr></table> >];
        InRoom -> InSelector -> InLobby [dir=back]
    }
    subgraph clusterGame {
        label="Game";
        node[shape=box]
        rank=same;
        subgraph clusterDealing {
            label="Cut For Deal";
            CutForDeal;
            AwaitDeal;
            AwaitDeal -> CutForDeal [dir=back]
        }
      subgraph clusterNthState {
            nthStateNode;
            label="Numerals";
      }

      AwaitDeal -> nthStateNode [ lhead=clusterNthState xlabel="nthTransition"];
    }

    InRoom -> CutForDeal [ lhead=clusterDealing label="startGame"];
}