fcode 2.2.0__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fcode/__init__.py +3 -0
- fcode/_types.py +655 -0
- fcode/api.py +146 -0
- fcode/auth.py +49 -0
- fcode/cli.py +127 -0
- fcode/commands/__init__.py +0 -0
- fcode/commands/init.py +29 -0
- fcode/commands/ladder.py +74 -0
- fcode/commands/login.py +135 -0
- fcode/commands/logout.py +21 -0
- fcode/commands/map_editor.py +91 -0
- fcode/commands/match_detail.py +173 -0
- fcode/commands/match_group.py +163 -0
- fcode/commands/matches.py +127 -0
- fcode/commands/run.py +186 -0
- fcode/commands/starter.py +119 -0
- fcode/commands/status.py +112 -0
- fcode/commands/submission.py +260 -0
- fcode/commands/team_group.py +73 -0
- fcode/commands/teams.py +73 -0
- fcode/commands/test.py +51 -0
- fcode/commands/test_matches.py +59 -0
- fcode/commands/test_run.py +125 -0
- fcode/commands/watch.py +123 -0
- fcode/compat.py +36 -0
- fcode/config.py +47 -0
- fcode/data/__init__.py +0 -0
- fcode/data/docs/spec.md +290 -0
- fcode/data/maps/__init__.py +0 -0
- fcode/data/maps/atoll.map26 +0 -0
- fcode/data/maps/aurora.map26 +0 -0
- fcode/data/maps/crossfire.map26 +0 -0
- fcode/data/maps/duel.map26 +0 -0
- fcode/data/maps/fjord.map26 +0 -0
- fcode/data/maps/hive.map26 +0 -0
- fcode/data/maps/longship.map26 +0 -0
- fcode/data/maps/pinch.map26 +0 -0
- fcode/data/maps/quarry.map26 +0 -0
- fcode/data/maps/runestone.map26 +0 -0
- fcode/data/maps/skerry.map26 +0 -0
- fcode/data/maps/sprint.map26 +0 -0
- fcode/data/maps/strait.map26 +0 -0
- fcode/data/maps/twins.map26 +0 -0
- fcode/data/maps/vault.map26 +0 -0
- fcode/data/starter_bot.py +452 -0
- fcode/data/visualiser/.directory +9 -0
- fcode/data/visualiser/Builderbot_med.png +0 -0
- fcode/data/visualiser/armoured_conveyor_gold.png +0 -0
- fcode/data/visualiser/armoured_conveyor_silver.png +0 -0
- fcode/data/visualiser/assets/app-BiRa_5u6.css +1 -0
- fcode/data/visualiser/assets/app-CK52NxpR.js +86 -0
- fcode/data/visualiser/assets/main-COIx42iq.js +1 -0
- fcode/data/visualiser/assets/map-editor-BDLYjgel.js +1 -0
- fcode/data/visualiser/axionite_ore.png +0 -0
- fcode/data/visualiser/axionite_processed.png +0 -0
- fcode/data/visualiser/axionite_raw.png +0 -0
- fcode/data/visualiser/barrier_gold.png +0 -0
- fcode/data/visualiser/barrier_silver.png +0 -0
- fcode/data/visualiser/base_gold.png +0 -0
- fcode/data/visualiser/base_silver.png +0 -0
- fcode/data/visualiser/battlecodetitle-orange.webp +0 -0
- fcode/data/visualiser/bg-light.png +0 -0
- fcode/data/visualiser/bg.png +0 -0
- fcode/data/visualiser/breach_e_gold.png +0 -0
- fcode/data/visualiser/breach_e_silver.png +0 -0
- fcode/data/visualiser/breach_n_gold.png +0 -0
- fcode/data/visualiser/breach_n_silver.png +0 -0
- fcode/data/visualiser/breach_ne_gold.png +0 -0
- fcode/data/visualiser/breach_ne_silver.png +0 -0
- fcode/data/visualiser/breach_nw_gold.png +0 -0
- fcode/data/visualiser/breach_nw_silver.png +0 -0
- fcode/data/visualiser/breach_s_gold.png +0 -0
- fcode/data/visualiser/breach_s_silver.png +0 -0
- fcode/data/visualiser/breach_se_gold.png +0 -0
- fcode/data/visualiser/breach_se_silver.png +0 -0
- fcode/data/visualiser/breach_sw_gold.png +0 -0
- fcode/data/visualiser/breach_sw_silver.png +0 -0
- fcode/data/visualiser/bridge_gold.png +0 -0
- fcode/data/visualiser/bridge_silver.png +0 -0
- fcode/data/visualiser/bridge_stand_gold.png +0 -0
- fcode/data/visualiser/bridge_stand_silver.png +0 -0
- fcode/data/visualiser/builderbot_back_gold.png +0 -0
- fcode/data/visualiser/builderbot_back_silver.png +0 -0
- fcode/data/visualiser/builderbot_front_gold.png +0 -0
- fcode/data/visualiser/builderbot_front_silver.png +0 -0
- fcode/data/visualiser/builderbot_side_gold.png +0 -0
- fcode/data/visualiser/builderbot_side_silver.png +0 -0
- fcode/data/visualiser/conveyor_gold.png +0 -0
- fcode/data/visualiser/conveyor_silver.png +0 -0
- fcode/data/visualiser/dimetric/Dimetric_Tile_Cube.png +0 -0
- fcode/data/visualiser/dimetric/Dimetric_Tile_Floor_Footprint.png +0 -0
- fcode/data/visualiser/dimetric/barrier_team1.png +0 -0
- fcode/data/visualiser/dimetric/barrier_team2.png +0 -0
- fcode/data/visualiser/dimetric/base_team1.png +0 -0
- fcode/data/visualiser/dimetric/base_team2.png +0 -0
- fcode/data/visualiser/dimetric/bg.png +0 -0
- fcode/data/visualiser/dimetric/bridge_stand_team1.png +0 -0
- fcode/data/visualiser/dimetric/bridge_stand_team2.png +0 -0
- fcode/data/visualiser/dimetric/bridge_team1.png +0 -0
- fcode/data/visualiser/dimetric/bridge_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_e_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_e_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_n_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_n_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_ne_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_ne_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_nw_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_nw_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_s_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_s_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_se_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_se_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_sw_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_sw_team2.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_w_team1.png +0 -0
- fcode/data/visualiser/dimetric/builderbot_w_team2.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_ne_team1.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_ne_team2.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_nw_team1.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_nw_team2.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_se_team1.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_se_team2.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_sw_team1.png +0 -0
- fcode/data/visualiser/dimetric/conveyor_sw_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_e_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_e_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_n_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_n_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_ne_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_ne_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_nw_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_nw_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_s_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_s_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_se_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_se_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_sw_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_sw_team2.png +0 -0
- fcode/data/visualiser/dimetric/gunner_w_team1.png +0 -0
- fcode/data/visualiser/dimetric/gunner_w_team2.png +0 -0
- fcode/data/visualiser/dimetric/harvester_team1.png +0 -0
- fcode/data/visualiser/dimetric/harvester_team2.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team1_ne.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team1_nw.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team1_se.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team1_sw.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team2_ne.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team2_nw.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team2_se.png +0 -0
- fcode/data/visualiser/dimetric/launcher_team2_sw.png +0 -0
- fcode/data/visualiser/dimetric/natural_wall.png +0 -0
- fcode/data/visualiser/dimetric/natural_wall_ne.png +0 -0
- fcode/data/visualiser/dimetric/natural_wall_nw.png +0 -0
- fcode/data/visualiser/dimetric/natural_wall_se.png +0 -0
- fcode/data/visualiser/dimetric/natural_wall_sw.png +0 -0
- fcode/data/visualiser/dimetric/road_team1.png +0 -0
- fcode/data/visualiser/dimetric/road_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_e_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_e_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_n_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_n_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_ne_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_ne_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_nw_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_nw_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_s_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_s_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_se_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_se_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_sw_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_sw_team2.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_w_team1.png +0 -0
- fcode/data/visualiser/dimetric/sentinel_w_team2.png +0 -0
- fcode/data/visualiser/dimetric/splitter_ne_team1.png +0 -0
- fcode/data/visualiser/dimetric/splitter_ne_team2.png +0 -0
- fcode/data/visualiser/dimetric/splitter_nw_team1.png +0 -0
- fcode/data/visualiser/dimetric/splitter_nw_team2.png +0 -0
- fcode/data/visualiser/dimetric/splitter_se_team1.png +0 -0
- fcode/data/visualiser/dimetric/splitter_se_team2.png +0 -0
- fcode/data/visualiser/dimetric/splitter_sw_team1.png +0 -0
- fcode/data/visualiser/dimetric/splitter_sw_team2.png +0 -0
- fcode/data/visualiser/dimetric/titanium.png +0 -0
- fcode/data/visualiser/dimetric/titanium_ore.png +0 -0
- fcode/data/visualiser/fonts/HKGrotesk-Regular.otf +0 -0
- fcode/data/visualiser/foundry_gold.png +0 -0
- fcode/data/visualiser/foundry_silver.png +0 -0
- fcode/data/visualiser/gunner_n_gold.png +0 -0
- fcode/data/visualiser/gunner_n_silver.png +0 -0
- fcode/data/visualiser/gunner_ne_gold.png +0 -0
- fcode/data/visualiser/gunner_ne_silver.png +0 -0
- fcode/data/visualiser/gunner_nw_gold.png +0 -0
- fcode/data/visualiser/gunner_nw_silver.png +0 -0
- fcode/data/visualiser/gunner_s_gold.png +0 -0
- fcode/data/visualiser/gunner_s_silver.png +0 -0
- fcode/data/visualiser/gunner_se_gold.png +0 -0
- fcode/data/visualiser/gunner_se_silver.png +0 -0
- fcode/data/visualiser/gunner_sw_gold.png +0 -0
- fcode/data/visualiser/gunner_sw_silver.png +0 -0
- fcode/data/visualiser/gunner_w_gold.png +0 -0
- fcode/data/visualiser/gunner_w_silver.png +0 -0
- fcode/data/visualiser/harvester_gold.png +0 -0
- fcode/data/visualiser/harvester_silver.png +0 -0
- fcode/data/visualiser/index.html +20 -0
- fcode/data/visualiser/launcher_gold.png +0 -0
- fcode/data/visualiser/launcher_silver.png +0 -0
- fcode/data/visualiser/map-editor.html +20 -0
- fcode/data/visualiser/marker_gold.png +0 -0
- fcode/data/visualiser/marker_silver.png +0 -0
- fcode/data/visualiser/natural_wall.jpg +0 -0
- fcode/data/visualiser/road_gold.png +0 -0
- fcode/data/visualiser/road_silver.png +0 -0
- fcode/data/visualiser/sentinel_e_gold.png +0 -0
- fcode/data/visualiser/sentinel_e_silver.png +0 -0
- fcode/data/visualiser/sentinel_n_gold.png +0 -0
- fcode/data/visualiser/sentinel_n_silver.png +0 -0
- fcode/data/visualiser/sentinel_ne_gold.png +0 -0
- fcode/data/visualiser/sentinel_ne_silver.png +0 -0
- fcode/data/visualiser/sentinel_nw_gold.png +0 -0
- fcode/data/visualiser/sentinel_nw_silver.png +0 -0
- fcode/data/visualiser/sentinel_s_gold.png +0 -0
- fcode/data/visualiser/sentinel_s_silver.png +0 -0
- fcode/data/visualiser/sentinel_se_gold.png +0 -0
- fcode/data/visualiser/sentinel_se_silver.png +0 -0
- fcode/data/visualiser/sentinel_sw_gold.png +0 -0
- fcode/data/visualiser/sentinel_sw_silver.png +0 -0
- fcode/data/visualiser/splitter.png +0 -0
- fcode/data/visualiser/splitter_e_gold.png +0 -0
- fcode/data/visualiser/splitter_e_silver.png +0 -0
- fcode/data/visualiser/splitter_n_gold.png +0 -0
- fcode/data/visualiser/splitter_n_silver.png +0 -0
- fcode/data/visualiser/splitter_s_gold.png +0 -0
- fcode/data/visualiser/splitter_s_silver.png +0 -0
- fcode/data/visualiser/splitter_w_gold.png +0 -0
- fcode/data/visualiser/splitter_w_silver.png +0 -0
- fcode/data/visualiser/template02.png +0 -0
- fcode/data/visualiser/titanium.png +0 -0
- fcode/data/visualiser/titanium_ore.png +0 -0
- fcode/fcode_engine.cp312-win_amd64.pyd +0 -0
- fcode/py.typed +0 -0
- fcode/version_check.py +72 -0
- fcode-2.2.0.dist-info/METADATA +7 -0
- fcode-2.2.0.dist-info/RECORD +245 -0
- fcode-2.2.0.dist-info/WHEEL +4 -0
- fcode-2.2.0.dist-info/entry_points.txt +2 -0
- fcode-2.2.0.dist-info/sboms/fcode_engine.cyclonedx.json +1963 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""Starter bot — economy-first strategy with light defense.
|
|
2
|
+
|
|
3
|
+
Each unit gets its own Player instance; the engine calls run() once per round.
|
|
4
|
+
Use ct.get_entity_type() to branch on what kind of unit you are.
|
|
5
|
+
|
|
6
|
+
Strategy:
|
|
7
|
+
1. Core spawns builder bots and publishes its position via the store
|
|
8
|
+
2. Builder bots explore, build harvesters on ore, and lay conveyors toward
|
|
9
|
+
the core so titanium flows back automatically
|
|
10
|
+
3. Once the economy is running (3+ harvesters), builder bots place gunners
|
|
11
|
+
near the core for defense
|
|
12
|
+
4. Gunners auto-fire at the closest visible enemy
|
|
13
|
+
|
|
14
|
+
Entity types used: Core, Builder Bot, Harvester, Conveyor, Gunner
|
|
15
|
+
|
|
16
|
+
Communication store slots:
|
|
17
|
+
0 SLOT_CORE_X Core X position (written by core on round 1)
|
|
18
|
+
1 SLOT_CORE_Y Core Y position
|
|
19
|
+
2 SLOT_HARVESTER_COUNT Total harvesters built by the team
|
|
20
|
+
3 SLOT_ORE_LOCATION Packed (x, y) of an uncovered ore tile
|
|
21
|
+
Slots 4-15 are free — use them for your own coordination logic.
|
|
22
|
+
|
|
23
|
+
Ideas for improvement:
|
|
24
|
+
- Build full conveyor chains from distant harvesters back to the core
|
|
25
|
+
- Feed ammo to gunners via conveyors so they can actually fire
|
|
26
|
+
- Add sentinels or launchers for stronger defense
|
|
27
|
+
- Explore the map systematically instead of picking random targets
|
|
28
|
+
- Use more store slots to coordinate roles between builder bots
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import random
|
|
32
|
+
|
|
33
|
+
from fcode import Controller, Direction, EntityType, Environment, GameConstants, Position
|
|
34
|
+
|
|
35
|
+
# All directions except CENTRE — useful for movement and spawning
|
|
36
|
+
DIRECTIONS = [d for d in Direction if d != Direction.CENTRE]
|
|
37
|
+
|
|
38
|
+
# Cardinal directions only — conveyors and splitters can only face these
|
|
39
|
+
CARDINALS = [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]
|
|
40
|
+
|
|
41
|
+
# --- Communication store slot assignments ---
|
|
42
|
+
# The store has 16 slots (indices 0-15), each holding a u32 value.
|
|
43
|
+
# Writes are buffered: a write_store() call becomes visible to all units
|
|
44
|
+
# at the start of the *next* round.
|
|
45
|
+
SLOT_CORE_X = 0
|
|
46
|
+
SLOT_CORE_Y = 1
|
|
47
|
+
SLOT_HARVESTER_COUNT = 2
|
|
48
|
+
SLOT_ORE_LOCATION = 3
|
|
49
|
+
|
|
50
|
+
# How many builder bots the core will spawn over the course of the game
|
|
51
|
+
MAX_BUILDERS = 5
|
|
52
|
+
|
|
53
|
+
# How many harvesters we want before switching builder bots to defense duty
|
|
54
|
+
TARGET_HARVESTERS = 3
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pack_pos(pos: Position) -> int:
|
|
58
|
+
"""Encode a position into a single u32 for the communication store.
|
|
59
|
+
|
|
60
|
+
We offset by +1 so that position (0, 0) doesn't encode as 0,
|
|
61
|
+
which we reserve to mean "no data".
|
|
62
|
+
"""
|
|
63
|
+
return ((pos.x + 1) << 16) | (pos.y + 1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def unpack_pos(val: int) -> Position | None:
|
|
67
|
+
"""Decode a position from the communication store. Returns None if empty (0)."""
|
|
68
|
+
if val == 0:
|
|
69
|
+
return None
|
|
70
|
+
return Position((val >> 16) - 1, (val & 0xFFFF) - 1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def nearest_cardinal(d: Direction) -> Direction:
|
|
74
|
+
"""Snap any direction to the nearest cardinal direction.
|
|
75
|
+
|
|
76
|
+
Conveyors and splitters can only face cardinal directions (N/E/S/W),
|
|
77
|
+
so we use this to convert an arbitrary direction into a valid facing.
|
|
78
|
+
"""
|
|
79
|
+
return {
|
|
80
|
+
Direction.NORTH: Direction.NORTH,
|
|
81
|
+
Direction.NORTHEAST: Direction.NORTH,
|
|
82
|
+
Direction.EAST: Direction.EAST,
|
|
83
|
+
Direction.SOUTHEAST: Direction.EAST,
|
|
84
|
+
Direction.SOUTH: Direction.SOUTH,
|
|
85
|
+
Direction.SOUTHWEST: Direction.SOUTH,
|
|
86
|
+
Direction.WEST: Direction.WEST,
|
|
87
|
+
Direction.NORTHWEST: Direction.WEST,
|
|
88
|
+
Direction.CENTRE: Direction.NORTH,
|
|
89
|
+
}[d]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Player:
|
|
93
|
+
def __init__(self):
|
|
94
|
+
# Core tracks how many builder bots it has spawned
|
|
95
|
+
self.num_spawned = 0
|
|
96
|
+
|
|
97
|
+
# Builder bot navigation state
|
|
98
|
+
self.target: Position | None = None # where we're trying to walk to
|
|
99
|
+
self.last_pos: Position | None = None # position last round (for stuck detection)
|
|
100
|
+
self.stuck = 0 # consecutive rounds without moving
|
|
101
|
+
|
|
102
|
+
# Cached core position (read from the store once, then reused)
|
|
103
|
+
self.core_pos: Position | None = None
|
|
104
|
+
|
|
105
|
+
def run(self, ct: Controller) -> None:
|
|
106
|
+
"""Entry point called by the engine every round for each unit.
|
|
107
|
+
|
|
108
|
+
We check what type of entity we are and dispatch to the right handler.
|
|
109
|
+
Each entity type (core, builder bot, gunner) has its own run logic.
|
|
110
|
+
"""
|
|
111
|
+
etype = ct.get_entity_type()
|
|
112
|
+
if etype == EntityType.CORE:
|
|
113
|
+
self._run_core(ct)
|
|
114
|
+
elif etype == EntityType.BUILDER_BOT:
|
|
115
|
+
self._run_builder(ct)
|
|
116
|
+
elif etype == EntityType.GUNNER:
|
|
117
|
+
self._run_gunner(ct)
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Core
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def _run_core(self, ct: Controller) -> None:
|
|
124
|
+
"""Core logic: publish our position so builder bots can orient toward us,
|
|
125
|
+
then try to spawn a builder bot each round until we hit the cap.
|
|
126
|
+
"""
|
|
127
|
+
# Write our position into the store every round so newly spawned bots
|
|
128
|
+
# can read it. Store writes are buffered — they become visible next round.
|
|
129
|
+
pos = ct.get_position()
|
|
130
|
+
ct.write_store(SLOT_CORE_X, pos.x)
|
|
131
|
+
ct.write_store(SLOT_CORE_Y, pos.y)
|
|
132
|
+
|
|
133
|
+
# Don't spawn more than MAX_BUILDERS total
|
|
134
|
+
if self.num_spawned >= MAX_BUILDERS:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Check we can afford a builder bot before trying
|
|
138
|
+
ti = ct.get_global_resources()
|
|
139
|
+
cost = ct.get_builder_bot_cost()
|
|
140
|
+
if ti < cost:
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
# Try to spawn on a random adjacent tile (shuffle to avoid bias)
|
|
144
|
+
dirs = list(DIRECTIONS)
|
|
145
|
+
random.shuffle(dirs)
|
|
146
|
+
for d in dirs:
|
|
147
|
+
spawn_pos = pos.add(d)
|
|
148
|
+
if ct.can_spawn(spawn_pos):
|
|
149
|
+
ct.spawn_builder(spawn_pos)
|
|
150
|
+
self.num_spawned += 1
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
# ------------------------------------------------------------------
|
|
154
|
+
# Builder bot
|
|
155
|
+
# ------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def _run_builder(self, ct: Controller) -> None:
|
|
158
|
+
"""Builder bot logic, executed each round. Priority order:
|
|
159
|
+
1. Build a harvester if adjacent to uncovered ore
|
|
160
|
+
2. Build a gunner if we already have enough harvesters
|
|
161
|
+
3. Heal any damaged friendly buildings nearby
|
|
162
|
+
4. Move toward the next target (ore or exploration)
|
|
163
|
+
5. Broadcast any visible ore to teammates via the store
|
|
164
|
+
"""
|
|
165
|
+
pos = ct.get_position()
|
|
166
|
+
|
|
167
|
+
# On our first turn, read the core's position from the store.
|
|
168
|
+
# We cache it so we don't have to read every round.
|
|
169
|
+
if self.core_pos is None:
|
|
170
|
+
self._read_core_pos(ct)
|
|
171
|
+
|
|
172
|
+
# Stuck detection: if we haven't moved in 3 rounds, we'll pick a new
|
|
173
|
+
# target in _move_toward_target to avoid getting permanently stuck.
|
|
174
|
+
if self.last_pos == pos:
|
|
175
|
+
self.stuck += 1
|
|
176
|
+
else:
|
|
177
|
+
self.stuck = 0
|
|
178
|
+
self.last_pos = pos
|
|
179
|
+
|
|
180
|
+
# --- Build phase (requires action cooldown == 0) ---
|
|
181
|
+
# Try to build a harvester first; if none available, consider a gunner
|
|
182
|
+
if ct.get_action_cooldown() == 0:
|
|
183
|
+
if not self._try_build_harvester(ct):
|
|
184
|
+
# Only build gunners once our economy is up (enough harvesters)
|
|
185
|
+
harvester_count = ct.read_store(SLOT_HARVESTER_COUNT)
|
|
186
|
+
if harvester_count >= TARGET_HARVESTERS:
|
|
187
|
+
self._try_build_gunner(ct)
|
|
188
|
+
|
|
189
|
+
# If we still have an action left (didn't build anything), heal nearby
|
|
190
|
+
if ct.get_action_cooldown() == 0:
|
|
191
|
+
self._try_heal(ct)
|
|
192
|
+
|
|
193
|
+
# --- Move phase (requires move cooldown == 0) ---
|
|
194
|
+
self._move_toward_target(ct)
|
|
195
|
+
|
|
196
|
+
# --- Communication phase (no cooldown needed) ---
|
|
197
|
+
# Share any visible ore location with teammates
|
|
198
|
+
self._share_ore(ct)
|
|
199
|
+
|
|
200
|
+
def _read_core_pos(self, ct: Controller) -> None:
|
|
201
|
+
"""Read the core's position from the communication store.
|
|
202
|
+
|
|
203
|
+
The core writes its position on round 1, so on round 1 these will
|
|
204
|
+
still be 0. We skip storing (0, 0) unless the core really is there.
|
|
205
|
+
"""
|
|
206
|
+
x = ct.read_store(SLOT_CORE_X)
|
|
207
|
+
y = ct.read_store(SLOT_CORE_Y)
|
|
208
|
+
if x > 0 or y > 0:
|
|
209
|
+
self.core_pos = Position(x, y)
|
|
210
|
+
|
|
211
|
+
def _try_build_harvester(self, ct: Controller) -> bool:
|
|
212
|
+
"""Try to build a harvester on an adjacent ore tile.
|
|
213
|
+
|
|
214
|
+
Harvesters can only be placed on ore tiles (Environment.ORE_TITANIUM).
|
|
215
|
+
can_build_harvester() checks for ore, empty tile, and sufficient resources.
|
|
216
|
+
After building, we also try to place a conveyor next to it to start
|
|
217
|
+
routing the titanium back toward the core.
|
|
218
|
+
"""
|
|
219
|
+
ti = ct.get_global_resources()
|
|
220
|
+
cost = ct.get_harvester_cost()
|
|
221
|
+
if ti < cost:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
pos = ct.get_position()
|
|
225
|
+
for d in Direction:
|
|
226
|
+
build_pos = pos.add(d)
|
|
227
|
+
if ct.can_build_harvester(build_pos):
|
|
228
|
+
ct.build_harvester(build_pos)
|
|
229
|
+
# Update the shared harvester counter so other bots know
|
|
230
|
+
count = ct.read_store(SLOT_HARVESTER_COUNT)
|
|
231
|
+
ct.write_store(SLOT_HARVESTER_COUNT, count + 1)
|
|
232
|
+
# Try to add a conveyor next to the harvester to route resources
|
|
233
|
+
self._try_build_conveyor_toward_core(ct, build_pos)
|
|
234
|
+
# Clear our target so we move on instead of lingering near this
|
|
235
|
+
# harvester. Without this the bot would try to walk onto the
|
|
236
|
+
# (non-passable) harvester tile for 3 rounds before stuck
|
|
237
|
+
# detection finally kicks in.
|
|
238
|
+
self.target = None
|
|
239
|
+
return True
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
def _try_build_conveyor_toward_core(self, ct: Controller, harvester_pos: Position) -> None:
|
|
243
|
+
"""Place a conveyor adjacent to a harvester, facing toward the core.
|
|
244
|
+
|
|
245
|
+
Harvesters output titanium stacks to adjacent buildings. By placing a
|
|
246
|
+
conveyor on the core-facing side, resources will start flowing in the
|
|
247
|
+
right direction. For a complete supply chain you'd need more conveyors
|
|
248
|
+
linking all the way back to the core.
|
|
249
|
+
"""
|
|
250
|
+
if self.core_pos is None:
|
|
251
|
+
return
|
|
252
|
+
ti = ct.get_global_resources()
|
|
253
|
+
cost = ct.get_conveyor_cost()
|
|
254
|
+
if ti < cost:
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
# Figure out which cardinal direction points from the harvester toward the core
|
|
258
|
+
toward_core = harvester_pos.direction_to(self.core_pos)
|
|
259
|
+
if toward_core == Direction.CENTRE:
|
|
260
|
+
return # harvester is right on top of the core (unlikely)
|
|
261
|
+
facing = nearest_cardinal(toward_core)
|
|
262
|
+
|
|
263
|
+
# Place the conveyor one step toward the core from the harvester
|
|
264
|
+
conv_pos = harvester_pos.add(facing)
|
|
265
|
+
if ct.can_build_conveyor(conv_pos, facing):
|
|
266
|
+
ct.build_conveyor(conv_pos, facing)
|
|
267
|
+
|
|
268
|
+
def _try_build_gunner(self, ct: Controller) -> bool:
|
|
269
|
+
"""Build a gunner turret on an adjacent tile, facing away from the core.
|
|
270
|
+
|
|
271
|
+
Gunners fire in a straight line in their facing direction, so pointing
|
|
272
|
+
them outward from the core gives them the best chance of hitting
|
|
273
|
+
approaching enemies. We only build if we're close to the core
|
|
274
|
+
(within ~4 tiles) so gunners end up defending the base.
|
|
275
|
+
|
|
276
|
+
Note: gunners need ammo delivered via conveyors to fire. This starter
|
|
277
|
+
bot doesn't set up ammo supply — that's an exercise for the player!
|
|
278
|
+
"""
|
|
279
|
+
ti = ct.get_global_resources()
|
|
280
|
+
cost = ct.get_gunner_cost()
|
|
281
|
+
if ti < cost:
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
pos = ct.get_position()
|
|
285
|
+
# Only build gunners when we're reasonably close to the core
|
|
286
|
+
if self.core_pos is None or pos.distance_squared(self.core_pos) > 18:
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
# Face the gunner away from the core (toward incoming enemies)
|
|
290
|
+
facing = pos.direction_to(self.core_pos).opposite()
|
|
291
|
+
if facing == Direction.CENTRE:
|
|
292
|
+
facing = random.choice(DIRECTIONS)
|
|
293
|
+
|
|
294
|
+
# Try each adjacent tile for a valid gunner placement
|
|
295
|
+
for d in Direction:
|
|
296
|
+
build_pos = pos.add(d)
|
|
297
|
+
if ct.can_build_gunner(build_pos, facing):
|
|
298
|
+
ct.build_gunner(build_pos, facing)
|
|
299
|
+
return True
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
def _try_heal(self, ct: Controller) -> None:
|
|
303
|
+
"""Heal a damaged friendly building or bot on an adjacent tile.
|
|
304
|
+
|
|
305
|
+
Healing costs 1 titanium and restores 4 HP. can_heal() checks that
|
|
306
|
+
there's actually a damaged friendly entity on the tile.
|
|
307
|
+
"""
|
|
308
|
+
pos = ct.get_position()
|
|
309
|
+
for d in Direction:
|
|
310
|
+
check = pos.add(d)
|
|
311
|
+
if ct.can_heal(check):
|
|
312
|
+
ct.heal(check)
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
# ------------------------------------------------------------------
|
|
316
|
+
# Builder bot — movement
|
|
317
|
+
# ------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
def _move_toward_target(self, ct: Controller) -> None:
|
|
320
|
+
"""Navigate toward our current target, picking a new one if needed.
|
|
321
|
+
|
|
322
|
+
Target priority: visible ore > ore shared via store > random position.
|
|
323
|
+
If we've been stuck for 3+ rounds, we give up on the current target
|
|
324
|
+
and pick a fresh one.
|
|
325
|
+
"""
|
|
326
|
+
if ct.get_move_cooldown() != 0:
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
pos = ct.get_position()
|
|
330
|
+
|
|
331
|
+
# Pick a new target if we don't have one, reached it, or are stuck
|
|
332
|
+
if self.target is None or pos == self.target or self.stuck >= 3:
|
|
333
|
+
self.target = self._pick_target(ct)
|
|
334
|
+
self.stuck = 0
|
|
335
|
+
if self.target is None:
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
# Get the direction toward our target
|
|
339
|
+
desired = pos.direction_to(self.target)
|
|
340
|
+
if desired == Direction.CENTRE:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
# Try the ideal direction first, then slightly left/right of it.
|
|
344
|
+
# If all three fail (e.g. boxed in by harvesters), try every remaining
|
|
345
|
+
# direction so the bot can escape instead of sitting stuck for 3 rounds.
|
|
346
|
+
primary = [desired, desired.rotate_left(), desired.rotate_right()]
|
|
347
|
+
for d in primary:
|
|
348
|
+
if self._try_move(ct, d):
|
|
349
|
+
return
|
|
350
|
+
remaining = [d for d in DIRECTIONS if d not in primary]
|
|
351
|
+
random.shuffle(remaining)
|
|
352
|
+
for d in remaining:
|
|
353
|
+
if self._try_move(ct, d):
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
def _try_move(self, ct: Controller, d: Direction) -> bool:
|
|
357
|
+
"""Try to move one step in direction d, laying conveyor infra along the way.
|
|
358
|
+
|
|
359
|
+
Builder bots can walk on any tile that isn't a wall or occupied by another
|
|
360
|
+
builder bot. If the destination is empty and we know where our core is, we
|
|
361
|
+
lay a conveyor pointing toward it so it doubles as part of the resource
|
|
362
|
+
network -- this is optional now, not required for movement.
|
|
363
|
+
"""
|
|
364
|
+
if d == Direction.CENTRE:
|
|
365
|
+
return False
|
|
366
|
+
pos = ct.get_position()
|
|
367
|
+
next_pos = pos.add(d)
|
|
368
|
+
|
|
369
|
+
if ct.is_tile_empty(next_pos) and self.core_pos is not None:
|
|
370
|
+
toward_core = next_pos.direction_to(self.core_pos)
|
|
371
|
+
cardinal = nearest_cardinal(toward_core)
|
|
372
|
+
if ct.can_build_conveyor(next_pos, cardinal):
|
|
373
|
+
ct.build_conveyor(next_pos, cardinal)
|
|
374
|
+
|
|
375
|
+
if ct.can_move(d):
|
|
376
|
+
ct.move(d)
|
|
377
|
+
return True
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
def _pick_target(self, ct: Controller) -> Position:
|
|
381
|
+
"""Choose the next position to navigate toward.
|
|
382
|
+
|
|
383
|
+
Priority:
|
|
384
|
+
1. Closest visible ore tile without a harvester on it — go build one
|
|
385
|
+
2. If economy is established (enough harvesters), head back toward
|
|
386
|
+
the core so we can build gunners for defense
|
|
387
|
+
3. Ore location shared by a teammate via the store — head that way
|
|
388
|
+
4. Random map position — pure exploration
|
|
389
|
+
"""
|
|
390
|
+
pos = ct.get_position()
|
|
391
|
+
|
|
392
|
+
# 1. Scan visible tiles for the nearest uncovered ore
|
|
393
|
+
best_ore = None
|
|
394
|
+
best_dist = float("inf")
|
|
395
|
+
for tile in ct.get_nearby_tiles():
|
|
396
|
+
if ct.get_tile_env(tile) != Environment.ORE_TITANIUM:
|
|
397
|
+
continue
|
|
398
|
+
# Skip ore that already has a building (harvester or otherwise) on it
|
|
399
|
+
if ct.get_tile_building_id(tile) is not None:
|
|
400
|
+
continue
|
|
401
|
+
d = pos.distance_squared(tile)
|
|
402
|
+
if d < best_dist:
|
|
403
|
+
best_dist = d
|
|
404
|
+
best_ore = tile
|
|
405
|
+
if best_ore is not None:
|
|
406
|
+
return best_ore
|
|
407
|
+
|
|
408
|
+
# 2. Once we have enough harvesters, head back to the core so we can
|
|
409
|
+
# build gunners nearby. _try_build_gunner requires being within
|
|
410
|
+
# distance² 18 of the core, so we navigate there.
|
|
411
|
+
harvester_count = ct.read_store(SLOT_HARVESTER_COUNT)
|
|
412
|
+
if harvester_count >= TARGET_HARVESTERS and self.core_pos is not None:
|
|
413
|
+
if pos.distance_squared(self.core_pos) > 8:
|
|
414
|
+
return self.core_pos
|
|
415
|
+
|
|
416
|
+
# 3. Check the store for an ore location shared by a teammate
|
|
417
|
+
shared = unpack_pos(ct.read_store(SLOT_ORE_LOCATION))
|
|
418
|
+
if shared is not None and pos.distance_squared(shared) > 4:
|
|
419
|
+
return shared
|
|
420
|
+
|
|
421
|
+
# 4. No known ore — pick a random position to explore
|
|
422
|
+
w, h = ct.get_map_width(), ct.get_map_height()
|
|
423
|
+
return Position(random.randrange(w), random.randrange(h))
|
|
424
|
+
|
|
425
|
+
def _share_ore(self, ct: Controller) -> None:
|
|
426
|
+
"""Broadcast a visible uncovered ore tile to teammates via the store.
|
|
427
|
+
|
|
428
|
+
Any builder bot that sees ore without a building on it writes the
|
|
429
|
+
location to SLOT_ORE_LOCATION. Other bots can read this to navigate
|
|
430
|
+
toward ore they haven't seen yet.
|
|
431
|
+
"""
|
|
432
|
+
pos = ct.get_position()
|
|
433
|
+
for tile in ct.get_nearby_tiles():
|
|
434
|
+
if ct.get_tile_env(tile) == Environment.ORE_TITANIUM:
|
|
435
|
+
if ct.get_tile_building_id(tile) is None:
|
|
436
|
+
ct.write_store(SLOT_ORE_LOCATION, pack_pos(tile))
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
# ------------------------------------------------------------------
|
|
440
|
+
# Gunner
|
|
441
|
+
# ------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
def _run_gunner(self, ct: Controller) -> None:
|
|
444
|
+
"""Gunner logic: fire at the first enemy in our line of sight.
|
|
445
|
+
|
|
446
|
+
get_gunner_target() returns the closest entity along the gunner's
|
|
447
|
+
facing direction, or None if the line is clear. We only fire if
|
|
448
|
+
can_fire() confirms we have ammo and cooldown is ready.
|
|
449
|
+
"""
|
|
450
|
+
target = ct.get_gunner_target()
|
|
451
|
+
if target is not None and ct.can_fire(target):
|
|
452
|
+
ct.fire(target)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:root,:host{--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-green-400:oklch(79.2% .209 151.711);--color-green-600:oklch(62.7% .194 149.214);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-gray-200:oklch(92.8% .006 264.531);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--animate-spin:spin 1s linear infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"HKGrotesk", monospace;--default-mono-font-family:"HKGrotesk", monospace}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{box-sizing:border-box}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:HKGrotesk,monospace;font-size:13px}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.inset-\[9px\]{inset:9px}.inset-\[22px\]{inset:22px}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing) * 3)}.top-full{top:100%}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-1\/2{left:50%}.left-3{left:calc(var(--spacing) * 3)}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing) * 2)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-\[0\.15rem\]{margin-top:.15rem}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.aspect-square{aspect-ratio:1}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-12{height:calc(var(--spacing) * 12)}.h-24{height:calc(var(--spacing) * 24)}.h-\[6px\]{height:6px}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-\[30vh\]{max-height:30vh}.max-h-\[40vh\]{max-height:40vh}.max-h-full{max-height:100%}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[22rem\]{min-height:22rem}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-58{width:calc(var(--spacing) * 58)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-\[min\(90vw\,48rem\)\]{width:min(90vw,48rem)}.w-fit{width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.max-w-\[3rem\]{max-width:3rem}.max-w-\[4rem\]{max-width:4rem}.max-w-\[40\%\]{max-width:40%}.max-w-\[55\%\]{max-width:55%}.max-w-full{max-width:100%}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[4rem\]{min-width:4rem}.min-w-\[32rem\]{min-width:32rem}.flex-1{flex:1}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-\[calc\(-50\%-2px\)\]{--tw-translate-y: calc(-50% - 2px) ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-45{rotate:45deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.resize{resize:both}.\[appearance\:none\]{appearance:none}.\[appearance\:textfield\]{appearance:textfield}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\[1fr_2rem_2rem\]{grid-template-columns:1fr 2rem 2rem}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.grid-cols-\[minmax\(0\,1fr\)_2\.75rem_2\.75rem\]{grid-template-columns:minmax(0,1fr) 2.75rem 2.75rem}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-none{overscroll-behavior:none}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:calc(var(--radius) + 8px)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[\#6eaaff\]\/30{border-color:#6eaaff4d}.border-\[\#7a6a50\]{border-color:#7a6a50}.border-\[\#ffbf40\]{border-color:#ffbf40}.border-\[\#ffbf40\]\/30{border-color:#ffbf404d}.border-\[\#ffbf40\]\/40{border-color:#ffbf4066}.border-\[var\(--vis-border\)\]{border-color:var(--vis-border)}.border-\[var\(--vis-border-2\)\]{border-color:var(--vis-border-2)}.border-black\/20{border-color:#0003}@supports (color:color-mix(in lab,red,red)){.border-black\/20{border-color:color-mix(in oklab,var(--color-black) 20%,transparent)}}.border-transparent{border-color:#0000}.bg-\[\#ffbf40\]{background-color:#ffbf40}.bg-\[\#ffbf40\]\/15{background-color:#ffbf4026}.bg-\[var\(--vis-bg\)\]{background-color:var(--vis-bg)}.bg-\[var\(--vis-bg-badge\)\]{background-color:var(--vis-bg-badge)}.bg-\[var\(--vis-bg-card\)\]{background-color:var(--vis-bg-card)}.bg-\[var\(--vis-bg-code\)\]{background-color:var(--vis-bg-code)}.bg-\[var\(--vis-bg-elevated\)\]{background-color:var(--vis-bg-elevated)}.bg-\[var\(--vis-bg-panel\)\]{background-color:var(--vis-bg-panel)}.bg-\[var\(--vis-bg-panel-80\)\]{background-color:var(--vis-bg-panel-80)}.bg-\[var\(--vis-bg-winner\)\]{background-color:var(--vis-bg-winner)}.bg-\[var\(--vis-border\)\]{background-color:var(--vis-border)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab,red,red)){.bg-black\/40{background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-red-100{background-color:var(--color-red-100)}.bg-transparent{background-color:#0000}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-1{padding-left:calc(var(--spacing) * 1)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:HKGrotesk,monospace}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#6eaaff\]{color:#6eaaff}.text-\[\#ff6b6b\]{color:#ff6b6b}.text-\[\#ffbf40\]{color:#ffbf40}.text-\[var\(--vis-text-1\)\]{color:var(--vis-text-1)}.text-\[var\(--vis-text-2\)\]{color:var(--vis-text-2)}.text-\[var\(--vis-text-3\)\]{color:var(--vis-text-3)}.text-\[var\(--vis-text-4\)\]{color:var(--vis-text-4)}.text-black{color:var(--color-black)}.text-green-600{color:var(--color-green-600)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-white\/70{color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.text-white\/70{color:color-mix(in oklab,var(--color-white) 70%,transparent)}}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.placeholder-\[var\(--vis-text-4\)\]::placeholder{color:var(--vis-text-4)}.accent-\[\#ffbf40\]{accent-color:#ffbf40}.opacity-25{opacity:.25}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black\/30{--tw-shadow-color:#0000004d}@supports (color:color-mix(in lab,red,red)){.shadow-black\/30{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 30%, transparent) var(--tw-shadow-alpha), transparent)}}.ring-\[\#ffbf40\]{--tw-ring-color:#ffbf40}.ring-\[\#ffbf40\]\/30{--tw-ring-color:oklab(84.2534% .0257247 .151766/.3)}.ring-\[\#ffbf40\]\/40{--tw-ring-color:oklab(84.2534% .0257247 .151766/.4)}.ring-\[\#ffbf40\]\/70{--tw-ring-color:oklab(84.2534% .0257247 .151766/.7)}.ring-black\/10{--tw-ring-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.ring-black\/10{--tw-ring-color:color-mix(in oklab, var(--color-black) 10%, transparent)}}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hover\:border-\[\#ffbf40\]:hover{border-color:#ffbf40}.hover\:border-\[\#ffbf40\]\/30:hover{border-color:#ffbf404d}.hover\:bg-\[\#ffa800\]:hover{background-color:#ffa800}.hover\:bg-\[var\(--vis-bg-elevated\)\]:hover{background-color:var(--vis-bg-elevated)}.hover\:bg-\[var\(--vis-bg-elevated-hover\)\]:hover{background-color:var(--vis-bg-elevated-hover)}.hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:text-\[\#8ec2ff\]:hover{color:#8ec2ff}.hover\:text-\[\#ffa800\]:hover{color:#ffa800}.hover\:text-\[\#ffbf40\]:hover{color:#ffbf40}.hover\:text-\[var\(--vis-text-1\)\]:hover{color:var(--vis-text-1)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-white:hover{color:var(--color-white)}.hover\:ring-1:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-\[\#ffbf40\]\/50:hover{--tw-ring-color:oklab(84.2534% .0257247 .151766/.5)}}.focus\:border-\[\#ffbf40\]:focus{border-color:#ffbf40}.focus\:border-\[\#ffbf40\]\/50:focus{border-color:#ffbf4080}.focus\:ring-\[\#ffbf40\]:focus{--tw-ring-color:#ffbf40}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media(hover:hover){.disabled\:hover\:bg-\[var\(--vis-bg-elevated\)\]:disabled:hover{background-color:var(--vis-bg-elevated)}.disabled\:hover\:text-\[var\(--vis-text-2\)\]:disabled:hover{color:var(--vis-text-2)}.disabled\:hover\:ring-0:disabled:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.data-\[side\=bottom\]\:top-1[data-side=bottom]{top:calc(var(--spacing) * 1)}.data-\[side\=inline-end\]\:top-1\/2\![data-side=inline-end]{top:50%!important}.data-\[side\=inline-end\]\:-left-1[data-side=inline-end]{left:calc(var(--spacing) * -1)}.data-\[side\=inline-end\]\:-translate-y-1\/2[data-side=inline-end]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=inline-start\]\:top-1\/2\![data-side=inline-start]{top:50%!important}.data-\[side\=inline-start\]\:-right-1[data-side=inline-start]{right:calc(var(--spacing) * -1)}.data-\[side\=inline-start\]\:-translate-y-1\/2[data-side=inline-start]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=left\]\:top-1\/2\![data-side=left]{top:50%!important}.data-\[side\=left\]\:-right-1[data-side=left]{right:calc(var(--spacing) * -1)}.data-\[side\=left\]\:-translate-y-1\/2[data-side=left]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=right\]\:top-1\/2\![data-side=right]{top:50%!important}.data-\[side\=right\]\:-left-1[data-side=right]{left:calc(var(--spacing) * -1)}.data-\[side\=right\]\:-translate-y-1\/2[data-side=right]{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=top\]\:-bottom-2\.5[data-side=top]{bottom:calc(var(--spacing) * -2.5)}@media(min-width:40rem){.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:pb-3{padding-bottom:calc(var(--spacing) * 3)}.sm\:pb-6{padding-bottom:calc(var(--spacing) * 6)}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media(min-width:48rem){.md\:order-none{order:0}.md\:w-auto{width:auto}.md\:min-w-0{min-width:calc(var(--spacing) * 0)}.md\:flex-1{flex:1}.md\:justify-start{justify-content:flex-start}}@media(min-width:64rem){.lg\:h-full{height:100%}.lg\:overflow-y-auto{overflow-y:auto}}@media(min-width:80rem){.xl\:gap-4{gap:calc(var(--spacing) * 4)}.xl\:px-6{padding-inline:calc(var(--spacing) * 6)}}.dark\:border-white\/20:is(.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.dark\:border-white\/20:is(.dark *){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.dark\:bg-\[\#0a0a0a\]:is(.dark *){background-color:#0a0a0a}.dark\:bg-\[\#0d0d0d\]:is(.dark *){background-color:#0d0d0d}.dark\:bg-\[\#1c1c1c\]:is(.dark *){background-color:#1c1c1c}.dark\:bg-\[\#1c1c1c\]\/40:is(.dark *){background-color:#1c1c1c66}.dark\:bg-\[\#1c1c1c\]\/50:is(.dark *){background-color:#1c1c1c80}.dark\:bg-\[\#2a2a2a\]:is(.dark *){background-color:#2a2a2a}.dark\:bg-\[\#3a1520\]:is(.dark *){background-color:#3a1520}.dark\:bg-\[\#111111\]:is(.dark *){background-color:#111}.dark\:bg-\[\#182032\]:is(.dark *){background-color:#182032}.dark\:text-\[\#484848\]:is(.dark *){color:#484848}.dark\:text-\[\#ff6b6b\]:is(.dark *){color:#ff6b6b}.dark\:text-green-400:is(.dark *){color:var(--color-green-400)}.dark\:text-red-400:is(.dark *){color:var(--color-red-400)}.dark\:ring-white\/15:is(.dark *){--tw-ring-color:#ffffff26}@supports (color:color-mix(in lab,red,red)){.dark\:ring-white\/15:is(.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}@media(hover:hover){.dark\:hover\:bg-\[\#1c1c1c\]:is(.dark *):hover{background-color:#1c1c1c}.dark\:hover\:bg-\[\#1c1c1c\]\/50:is(.dark *):hover{background-color:#1c1c1c80}.dark\:hover\:bg-\[\#1d2740\]:is(.dark *):hover{background-color:#1d2740}.dark\:hover\:bg-\[\#4a1a28\]:is(.dark *):hover{background-color:#4a1a28}.dark\:hover\:bg-\[\#262626\]:is(.dark *):hover{background-color:#262626}.dark\:hover\:text-\[\#ff8888\]:is(.dark *):hover{color:#f88}}.\[\&\:\:-moz-range-thumb\]\:h-3::-moz-range-thumb{height:calc(var(--spacing) * 3)}.\[\&\:\:-moz-range-thumb\]\:w-3::-moz-range-thumb{width:calc(var(--spacing) * 3)}.\[\&\:\:-moz-range-thumb\]\:rounded-full::-moz-range-thumb{border-radius:3.40282e38px}.\[\&\:\:-moz-range-thumb\]\:border-0::-moz-range-thumb{border-style:var(--tw-border-style);border-width:0}.\[\&\:\:-moz-range-thumb\]\:bg-\[\#ffbf40\]::-moz-range-thumb{background-color:#ffbf40}.\[\&\:\:-webkit-inner-spin-button\]\:appearance-none::-webkit-inner-spin-button{appearance:none}.\[\&\:\:-webkit-outer-spin-button\]\:appearance-none::-webkit-outer-spin-button{appearance:none}.\[\&\:\:-webkit-slider-thumb\]\:h-3::-webkit-slider-thumb{height:calc(var(--spacing) * 3)}.\[\&\:\:-webkit-slider-thumb\]\:w-3::-webkit-slider-thumb{width:calc(var(--spacing) * 3)}.\[\&\:\:-webkit-slider-thumb\]\:cursor-pointer::-webkit-slider-thumb{cursor:pointer}.\[\&\:\:-webkit-slider-thumb\]\:\[appearance\:none\]::-webkit-slider-thumb{appearance:none}.\[\&\:\:-webkit-slider-thumb\]\:rounded-full::-webkit-slider-thumb{border-radius:3.40282e38px}.\[\&\:\:-webkit-slider-thumb\]\:bg-\[\#ffbf40\]::-webkit-slider-thumb{background-color:#ffbf40}}@font-face{font-family:HKGrotesk;src:url(/fonts/HKGrotesk-Regular.otf)format("opentype");font-weight:400;font-style:normal;font-display:swap}:root{--radius:0rem}#root{width:100%;height:100dvh}body,#root,main{min-width:0;max-width:100vw;overflow-x:hidden}main>*{min-width:0}#root canvas{max-width:100%;max-height:100%;display:block;width:100%!important;height:100%!important}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
|