Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ Allowed phases are `NS_GREEN` and `EW_GREEN`. The engine handles minimum green t
## Submit

```bash
python submit.py login MLG-XXXX-XXXX-XXXX-XXXX --url https://YOUR-EVENT-URL
python submit.py login MLG-XXXX --url https://YOUR-EVENT-URL
python submit.py
```

You may make up to **20 unique submissions**, with a five-minute cooldown after each accepted submission. Submitting identical controller code returns the existing result without consuming an attempt or restarting the cooldown.
You may make up to **20 unique submissions**, with a one-minute cooldown after each accepted submission. Submitting identical controller code returns the existing result without consuming an attempt or restarting the cooldown.

During the challenge, the leaderboard shows a provisional score made from 20% public scenarios and 80% private validation scenarios. When submissions close, each team's best provisional submission is selected automatically and evaluated once on a separate sealed final suite. The twelve final maps form six traffic families. Each family contributes the lower of its two map scores, and those six results are combined geometrically. The final leaderboard uses 20% public score and 80% sealed-final score.
If you downloaded the starter before the event began, replace it with the current download before submitting. Older `submit.py` versions expect score fields that the event API no longer returns.

During the challenge, the public leaderboard shows order only. Ranking uses a provisional total made from 20% public scenarios and 80% private validation scenarios, and the event screen reveals only that combined total every 20 minutes. When submissions close, each team's best provisional submission is selected automatically and evaluated once on a separate sealed final suite. The twelve final maps form six traffic families. Each family contributes the lower of its two map scores, and those six results are combined geometrically. The final event-screen reveal uses 20% public score and 80% sealed-final score.

The fixed-time baseline earns 10,000 points per scenario. Each public scenario includes a calibrated gold target worth 25,000 points. Improvement toward gold uses a squared curve: making half of the cost improvement from baseline to gold earns 13,750 points, while reaching gold earns 25,000. Costs better than gold remain capped at 25,000.

Expand All @@ -50,7 +52,9 @@ Este es el reto de Cursor Build Night Málaga. Tu equipo dispone de **105 minuto
4. Edita `controller.py`; el navegador se actualizará cada vez que guardes.
5. Inicia sesión con el código de tu mesa y ejecuta `python submit.py`.

Puedes realizar hasta **20 envíos únicos**, con una espera de cinco minutos después de cada envío aceptado. Durante el reto, la clasificación provisional combina un 20% de los mapas públicos y un 80% de mapas privados de validación. Al cerrar los envíos, se selecciona automáticamente el mejor envío provisional de cada equipo y se evalúa una sola vez en doce mapas finales secretos, agrupados en seis familias de tráfico. Cada familia aporta el menor de sus dos scores y esos seis resultados se combinan con media geométrica. La clasificación final combina un 20% de la puntuación pública y un 80% de la puntuación final secreta.
Puedes realizar hasta **20 envíos únicos**, con una espera de un minuto después de cada envío aceptado. Durante el reto, la clasificación pública muestra solo el orden. La clasificación provisional combina un 20% de los mapas públicos y un 80% de mapas privados de validación, y la proyección del evento revela únicamente ese total combinado cada 20 minutos. Al cerrar los envíos, se selecciona automáticamente el mejor envío provisional de cada equipo y se evalúa una sola vez en doce mapas finales secretos, agrupados en seis familias de tráfico. Cada familia aporta el menor de sus dos scores y esos seis resultados se combinan con media geométrica. La revelación final en la proyección combina un 20% de la puntuación pública y un 80% de la puntuación final secreta.

Si descargaste el starter antes de que empezara el evento, sustitúyelo por la descarga actual antes de enviar. Las versiones anteriores de `submit.py` esperan campos de puntuación que la API del evento ya no devuelve.

El baseline obtiene 10.000 puntos por escenario y el objetivo gold obtiene 25.000. La mejora usa una curva cuadrática: conseguir la mitad de la reducción de coste entre baseline y gold otorga 13.750 puntos; alcanzar gold otorga 25.000, que también es el máximo.

Expand Down
14 changes: 6 additions & 8 deletions submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
CONTROLLER = ROOT / "controller.py"
DEFAULT_API = os.getenv("TRAFFIC_ARENA_URL", "http://localhost:3000")
POLL_TIMEOUT_SECONDS = 15 * 60
TOKEN_PATTERN = re.compile(r"^MLG-(?:DEMO-DEMO-DEMO-DEMO|[A-Z2-9]{4}(?:-[A-Z2-9]{4}){3})$")
TOKEN_PATTERN = re.compile(r"^MLG-(?:DEMO-DEMO-DEMO-DEMO|[A-Z2-9]{4})$")


def save_token(token: str, base_url: str) -> None:
Expand Down Expand Up @@ -109,12 +109,10 @@ def submit() -> None:
)
current_status = status.get("status")
if current_status == "completed":
if not all(isinstance(status.get(key), int) for key in ("publicScore", "hiddenScore", "totalScore")):
raise SystemExit("Server returned an incomplete score result.")
print(f"PUBLIC {status['publicScore']:>8,}")
print(f"HIDDEN {status['hiddenScore']:>8,}")
print(f"TOTAL {status['totalScore']:>8,}")
webbrowser.open(f"{config['baseUrl']}/es/replay?submission={submission_id}")
print("Evaluation completed.")
replay_url = f"{config['baseUrl']}/es/replay?submission={submission_id}"
print(f"Replay: {replay_url}")
webbrowser.open(replay_url)
return
if current_status == "failed":
raise SystemExit(status.get("errorMessage", "Evaluation failed."))
Expand All @@ -137,7 +135,7 @@ def main() -> None:
token = args.token.strip().upper()
base_url = args.url.rstrip("/")
if not TOKEN_PATTERN.fullmatch(token):
raise SystemExit("Team code must look like MLG-XXXX-XXXX-XXXX-XXXX.")
raise SystemExit("Team code must look like MLG-XXXX.")
parsed_url = urlsplit(base_url)
if parsed_url.scheme not in {"http", "https"} or not parsed_url.hostname or parsed_url.query or parsed_url.fragment:
raise SystemExit("Event URL must be a valid http:// or https:// address without a query or fragment.")
Expand Down
17 changes: 17 additions & 0 deletions tests/test_submit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from submit import TOKEN_PATTERN


def test_team_code_pattern_accepts_production_card_code() -> None:
assert TOKEN_PATTERN.fullmatch("MLG-BHJ3")


def test_team_code_pattern_accepts_demo_code() -> None:
assert TOKEN_PATTERN.fullmatch("MLG-DEMO-DEMO-DEMO-DEMO")


def test_team_code_pattern_rejects_obsolete_long_code() -> None:
assert not TOKEN_PATTERN.fullmatch("MLG-BHJ3-ABCD-2345-WXYZ")


def test_team_code_pattern_rejects_ambiguous_characters() -> None:
assert not TOKEN_PATTERN.fullmatch("MLG-B0I1")
Loading