--- id: setup-tree title: Match setup is one tree, rendered as a lobby and as a scenario file status: open repos: [headquarters, arena] dependsOn: [lobby] exitCriterion: > A new kind of match is a new tree, and neither the lobby nor the `.mms` writer needs a branch for it. --- # setup-tree A direction jmm sketched, not a decision. Locking is the right idea, but it wants to be AST-like rather than a flag per setting: `Locked(Choice(x, y, z))`, so that lobby logic is something to iterate on quickly instead of something to rewrite per feature. What is behind it: a daily challenge is not a match type. It is a scenario plus a set of locked lobby settings — the scenario is fixed, both seats are fixed, chat is gone. Everything distinctive about it is a control a player would otherwise have, taken away. Duel is the same trick weaker: a `Choice` over two-slot scenarios where Scenario has a `Choice` over the whole catalog. ## The shape A value that is free, `Locked(x)`, `Choice(a, b, c)`, and `Locked(Choice(…))` as the composition — a tutorial that offers force A, B or C and nothing else. One tree, two renderings. The lobby greys out what is locked and offers what is a choice; the `.mms` writer emits whatever the tree resolved to. A scenario kind — tutorial, co-op, weekly, ladder — is then a tree rather than a code path, and the fourth one costs a tree rather than a fourth branch in `lobbyScreen`. ## What a V1 file can already lock `crates/mms` reads and writes `MMSVersion=1`, so this part needs no new format work. Checked against `ScenarioV1.java` and `MegaMekGUI.java` in MegaMek 0.51.0: - `SinglePlayer=true` skips both setup dialogs — player assignment and host — and makes every faction after the first a bot. - `FixedGameOptions=true` skips the Game Options dialog. `GameOptionsFile=` names the ruleset it would otherwise have asked for. - `FixedPlanetaryConditions=true` skips the Planetary Conditions dialog. - `team_`, `Location_` and `camo_` fix a faction's team, deployment edge and paint. The case of those three is a trap, and whatever writes them has to know it. `ScenarioV1.getFactionParam` is `param + "_" + faction`, and the constants are `team` and `camo` lower case but `Location` capitalised — so `Team_X` and `Camo_X` are read as nothing and dropped in silence. `crates/mms` already reports that as a `Quirk`. Omitting `team_` is not neutral either: MegaMek then gives every faction its own team. Deployment edges are `IStartingPositions.START_LOCATION_NAMES`, which carries `CTR` alongside the compass points, so a fixed centre deployment is already expressible. ## What V1 cannot express and V2 can - A per-player `bot:` node — Princess's behaviour. MegaMek ships 14 named presets in `mmconf/princessBehaviors.xml` (Striker, Scout, Brawler, …), and which bot you are fighting is exactly the sort of thing a tree would want to lock or to offer. - Board files resolved relative to the scenario file, multi-board and `embed`, `postprocess` terrain edits, and hex-area deployment rather than an edge. So the V2 migration belongs to this epic rather than being a separate format chore: the tree wants nodes V1 has no line for. arena has to catch up either way. `MatchHost.java` builds `new Princess(p.getName(), LOCALHOST, a.port)` and never consults the scenario, so a `bot:` node today is ignored rather than broken. That is a safe order to do it in, but it means "arena reads the scenario and applies what it says" is this epic's work too, not a one-off. ## What neither can express Equipment modes. Nothing in either loader sets one, so stealth armour cannot be switched on at scenario start — `Mek.isStealthActive` wants `curMode().equals("On")` *and* `hasActiveECM()`. A tree offering "stealth on" would be promising something the file cannot carry, so either arena applies it after load or the node does not exist. ## Still open - [ ] **Distinguish `VisiblyLocked` from `InvisiblyLocked`.** A locked setting has two meanings and the tree does not yet tell them apart. Some locks are the point and belong on screen — a daily's rules and its AI, which is what the card's override pips already show. Others are plumbing nobody wants read out: an arena flag, a timeout, a slot ordering that happens to be fixed. `content/overrides.ts` already splits its own list this way, with a `Disclosure` of `"visible" | "silent"` and a `VISIBLE_RULES` set deciding which is which. That is the same distinction arrived at from the other end, and the tree should carry it as a property of a node rather than leaving each renderer to keep its own list of what is worth mentioning. ## Open questions - [ ] **Whether a tree earns its keep before the third kind.** Two kinds are a branch. The cheap test is writing the tree for what already exists and seeing whether the daily and Duel fall out of it unchanged. - [ ] **Where it lives.** The lobby is TypeScript and the writer is Rust, so one of them reads a serialisation rather than the tree itself. Which one owns it decides what that wire format is. - [ ] **What a leaf is.** `SinglePlayer` and "no chat" are not the same kind of thing — one goes in the file, the other only ever exists here. A tree that mixes them needs to say which is which. - [ ] **What happens to a lock the format cannot carry.** Enforced by us at deploy, or not offered at all. - [ ] **Whether a downloaded scenario has to be the same fight.** [daily-challenge](daily-challenge.md) makes the day's `.mms` downloadable and playable in desktop MegaMek. A lock that exists only in our lobby makes the download a different game. ## Related [lobby](lobby.md) is what would be rendered from the tree, and its Duel-is-only-a-filter note is the same observation from the other side. [scenarios](scenarios.md) is the file the writer emits; [daily-challenge](daily-challenge.md) is the first tree, written by hand. ## Done - [x] **A team control in the lobby.** One `