#!/usr/bin/env python3 """A bot that names a unit which cannot act, on purpose. The turn-order reply may carry a `unit`, and the host acts on that unit instead of its own first-eligible pick. That opens a failure the harness has to refuse rather than obey: a bot naming a unit MegaMek will not accept this turn. If the host substituted its own pick silently, the decision log would say one machine moved while another one did - the same failure class as an illegal path. So this bot answers every question it is asked - it is not a passthrough, and its decisions must not come back `defaulted` - and names a unit id that exists in no game. Every movement and firing decision should come back `answered`, on the host's own pick, with `substituted` equal to the number of those decisions, and a line on stderr for each. Anything else means the refusal is not working. No environment switch, because the harness passes the bot an allowlist of variables and a test switch does not belong in it. This is a whole bot instead, and it is named for the one thing it does. ./sds.sh one --sds-seat North --bot /work/bots/ineligible_bot.py """ import json import sys # Not a unit id. MegaMek numbers entities from zero and never negatively, so # nothing in any game can collide with this. INELIGIBLE = -99999 def main() -> None: for line in sys.stdin: line = line.strip() if not line: continue message = json.loads(line) if message.get("type") != "observation": continue phase = message.get("phase", "") reply = {"type": "action", "seq": message["seq"]} if phase == "MOVEMENT": # Stand still, but claim it is for a unit that cannot act. An empty # step list is a legal path, so the only thing left for the host to # object to is the unit. reply.update({"kind": "move", "steps": [], "unit": INELIGIBLE}) elif phase == "FIRING": reply.update({"kind": "fire", "attacks": [], "unit": INELIGIBLE}) else: reply["kind"] = "pass" sys.stdout.write(json.dumps(reply) + "\n") sys.stdout.flush() if __name__ == "__main__": main()