]> Untitled Git - axy/ft/pacman.git/commitdiff
Refactored assets to use proper python assets, and updated makefile
authorAxy <gilliardmarthey.axel@gmail.com>
Thu, 22 Oct 2026 21:11:07 +0000 (23:11 +0200)
committerAxy <gilliardmarthey.axel@gmail.com>
Thu, 22 Oct 2026 21:11:07 +0000 (23:11 +0200)
62 files changed:
.gitignore
Makefile
pyproject.toml
src/pacman/__init__.py
src/pacman/__main__.py [new file with mode: 0644]
src/pacman/assets/enemy/ghostA_D.png [moved from asset/enemy/ghostA_D.png with 100% similarity]
src/pacman/assets/enemy/ghostA_L.png [moved from asset/enemy/ghostA_L.png with 100% similarity]
src/pacman/assets/enemy/ghostA_R.png [moved from asset/enemy/ghostA_R.png with 100% similarity]
src/pacman/assets/enemy/ghostA_U.png [moved from asset/enemy/ghostA_U.png with 100% similarity]
src/pacman/assets/enemy/ghostB_D.png [moved from asset/enemy/ghostB_D.png with 100% similarity]
src/pacman/assets/enemy/ghostB_L.png [moved from asset/enemy/ghostB_L.png with 100% similarity]
src/pacman/assets/enemy/ghostB_R.png [moved from asset/enemy/ghostB_R.png with 100% similarity]
src/pacman/assets/enemy/ghostB_U.png [moved from asset/enemy/ghostB_U.png with 100% similarity]
src/pacman/assets/enemy/ghostC_D.png [moved from asset/enemy/ghostC_D.png with 100% similarity]
src/pacman/assets/enemy/ghostC_L.png [moved from asset/enemy/ghostC_L.png with 100% similarity]
src/pacman/assets/enemy/ghostC_R.png [moved from asset/enemy/ghostC_R.png with 100% similarity]
src/pacman/assets/enemy/ghostC_U.png [moved from asset/enemy/ghostC_U.png with 100% similarity]
src/pacman/assets/enemy/ghostD_D.png [moved from asset/enemy/ghostD_D.png with 100% similarity]
src/pacman/assets/enemy/ghostD_L.png [moved from asset/enemy/ghostD_L.png with 100% similarity]
src/pacman/assets/enemy/ghostD_R.png [moved from asset/enemy/ghostD_R.png with 100% similarity]
src/pacman/assets/enemy/ghostD_U.png [moved from asset/enemy/ghostD_U.png with 100% similarity]
src/pacman/assets/enemy/ghost_blue.png [moved from asset/enemy/ghost_blue.png with 100% similarity]
src/pacman/assets/enemy/ghost_dead.png [moved from asset/enemy/ghost_dead.png with 100% similarity]
src/pacman/assets/enemy/ghost_flash.png [moved from asset/enemy/ghost_flash.png with 100% similarity]
src/pacman/assets/fonts/JetBrainsMonoNL-Regular.ttf [moved from asset/fonts/JetBrainsMonoNL-Regular.ttf with 100% similarity]
src/pacman/assets/gui/button_selected.png [moved from asset/gui/button_selected.png with 100% similarity]
src/pacman/assets/gui/button_unselected.png [moved from asset/gui/button_unselected.png with 100% similarity]
src/pacman/assets/misc/pacgum.png [moved from asset/misc/pacgum.png with 100% similarity]
src/pacman/assets/misc/superpacgum.png [moved from asset/misc/superpacgum.png with 100% similarity]
src/pacman/assets/player/player_DA.png [moved from asset/player/player_DA.png with 100% similarity]
src/pacman/assets/player/player_DB.png [moved from asset/player/player_DB.png with 100% similarity]
src/pacman/assets/player/player_DC.png [moved from asset/player/player_DC.png with 100% similarity]
src/pacman/assets/player/player_LA.png [moved from asset/player/player_LA.png with 100% similarity]
src/pacman/assets/player/player_LB.png [moved from asset/player/player_LB.png with 100% similarity]
src/pacman/assets/player/player_LC.png [moved from asset/player/player_LC.png with 100% similarity]
src/pacman/assets/player/player_RA.png [moved from asset/player/player_RA.png with 100% similarity]
src/pacman/assets/player/player_RB.png [moved from asset/player/player_RB.png with 100% similarity]
src/pacman/assets/player/player_RC.png [moved from asset/player/player_RC.png with 100% similarity]
src/pacman/assets/player/player_UA.png [moved from asset/player/player_UA.png with 100% similarity]
src/pacman/assets/player/player_UB.png [moved from asset/player/player_UB.png with 100% similarity]
src/pacman/assets/player/player_UC.png [moved from asset/player/player_UC.png with 100% similarity]
src/pacman/assets/walls/0.png [moved from asset/walls/0.png with 100% similarity]
src/pacman/assets/walls/1.png [moved from asset/walls/1.png with 100% similarity]
src/pacman/assets/walls/10.png [moved from asset/walls/10.png with 100% similarity]
src/pacman/assets/walls/11.png [moved from asset/walls/11.png with 100% similarity]
src/pacman/assets/walls/12.png [moved from asset/walls/12.png with 100% similarity]
src/pacman/assets/walls/13.png [moved from asset/walls/13.png with 100% similarity]
src/pacman/assets/walls/14.png [moved from asset/walls/14.png with 100% similarity]
src/pacman/assets/walls/15.png [moved from asset/walls/15.png with 100% similarity]
src/pacman/assets/walls/2.png [moved from asset/walls/2.png with 100% similarity]
src/pacman/assets/walls/3.png [moved from asset/walls/3.png with 100% similarity]
src/pacman/assets/walls/4.png [moved from asset/walls/4.png with 100% similarity]
src/pacman/assets/walls/5.png [moved from asset/walls/5.png with 100% similarity]
src/pacman/assets/walls/6.png [moved from asset/walls/6.png with 100% similarity]
src/pacman/assets/walls/7.png [moved from asset/walls/7.png with 100% similarity]
src/pacman/assets/walls/8.png [moved from asset/walls/8.png with 100% similarity]
src/pacman/assets/walls/9.png [moved from asset/walls/9.png with 100% similarity]
src/pacman/ecs/assets.py
src/pacman/ecs/graphics.py
src/pacman/ecs/gui.py
src/pacman/game/__init__.py
uv.lock

index 6dde557980f1cb6e1bf9413695c3256b2bf85a02..eed60d30463f01a29676c5b7dbc8979aa7a49dc5 100644 (file)
@@ -8,3 +8,4 @@ a.txt
 **/__pycache__
 .mypy_cache
 .ruff_cache
+dist/
index 5eccacb6cbec77983dd6a163ca9e99fe1a335726..824a78f30f0f66047273e21c423a7501148a3f83 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,32 +1,40 @@
-MAIN=pacman
-PYTHON=python3
-CONFIG=config.json
+PACMAN_CONFIG ?= config.json
 
-
-.PHONY: install run venv debug clean lint lint--strict
+.PHONY: install run venv debug clean lint lint-strict
 
 install:
-       @uv sync
+       uv sync
 
 run:
-       @uv run $(MAIN) $(CONFIG)
-
+       uv run -m pacman $(PACMAN_CONFIG)
 
 debug:
-       @$(PYTHON) -m pdb $(MAIN) $(CONFIG)
+       uv run -m pdb -m pacman $(PACMAN_CONFIG)
 
 clean:
-       @find . -type d -name "__pycache__" -exec rm -r {} +
-       @find . -type d -name ".mypy_cache" -exec rm -r {} +
-       @find . -type d -name "*.pyc" -delete
+       find . -type d -name "__pycache__" -exec rm -r {} +
+       find . -type d -name ".mypy_cache" -exec rm -r {} +
+       find . -type d -name ".ruff_cache" -exec rm -r {} +
+       find . -type d -name "*.pyc" -delete
 #      @rm -rf .venv
 
 lint:
-       @uv run flake8 src
-       @uv run mypy src --warn-return-any \
+       uv run flake8 src
+       uv run mypy src --warn-return-any \
        --warn-unused-ignores --ignore-missing-imports \
        --disallow-untyped-defs
+       uv run ruff check
+       uv run ty check
 
 lint-strict:
-       @uv run flake8 src
-       @uv run mypy src --strict
\ No newline at end of file
+       uv run flake8 src
+       uv run mypy src --strict
+       uv run ruff check
+       uv run ty check
+
+check:
+       uv run ruff check
+       uv run ty check
+
+build:
+       uv build
index d357ecb458530f1270a9f442aea6ccea5a1d2852..1ce7867ee388016f6fdafd0aa1da739c9e170396 100644 (file)
@@ -16,6 +16,7 @@ dev = [
     "flake8>=7.3.0",
     "mypy>=2.3.1",
     "ruff>=0.16.4",
+    "shiv>=1.0.8",
     "ty>=0.0.75",
 ]
 
index 4f0ad42fd7b91c973d87c4a27972847b9ad9c9d5..847b18a29dc84cc839f6846889a391bca3149545 100644 (file)
@@ -1,10 +1,10 @@
+"""The pacman project."""
+
 from pacman.game import run_game
 
 
 def main() -> None:
     """Run the game."""
-    run_game()
-    return None
     try:
         run_game()
     except Exception as e:
diff --git a/src/pacman/__main__.py b/src/pacman/__main__.py
new file mode 100644 (file)
index 0000000..4bb3bd4
--- /dev/null
@@ -0,0 +1,6 @@
+"""The pacman executable file."""
+
+from pacman import main
+
+if __name__ == "__main__":
+    main()
index ddc3105b3f762a6911b44191f501e727e6a38ee8..9f2662f01b0eed59df6644742cd7b77d1f43d15b 100644 (file)
@@ -2,8 +2,6 @@
 
 from abc import ABC, abstractmethod
 from collections.abc import Callable
-from os import PathLike
-from os.path import abspath
 from typing import Self, get_args, override
 
 from pacman.ecs.world import Resource, World
@@ -15,14 +13,14 @@ class Asset(ABC):
 
     @classmethod
     @abstractmethod
-    def load(cls, ty: type, path: str) -> Self:
+    def load(cls, ty: type, ident: str) -> Self:
         """Load the type from the specified identifier.
 
         Args:
           ty:
             The type, including generics, of cls.
-          path:
-            The identifier of the asset, usually a file path.
+          ident:
+            The identifier of the asset.
         """
 
 
@@ -34,11 +32,12 @@ class Assets[T: Asset](Resource):
         self._lookup: dict[str, T] = {}
         self._asset_ty: type[T] = get_args(resolve_type_aliases(ty))[0]
 
-    def load(self, s: str | PathLike[str]) -> T:
+    def load(self, s: str) -> T:
         """Load the given asset or fetch it from cache."""
-        s = abspath(s)
         if s not in self._lookup:
-            self._lookup[s] = self._asset_ty.load(self._asset_ty, s)
+            self._lookup[s] = self._asset_ty.load(
+                self._asset_ty, "assets/" + s
+            )
         return self._lookup[s]
 
     @override
index 41fa898071ffd8d5efef8430f743c24839737643..b9a1eeadf28e81dbca11c45f927bf1c5a00232c7 100644 (file)
@@ -2,12 +2,14 @@
 
 from collections.abc import Iterator
 from dataclasses import dataclass
+from importlib.resources import files
 from time import time
 from typing import cast, override
 
 import pygame
 from pygame import K_RETURN, K_a, K_d, K_s, K_w, Vector2
 
+import pacman
 from pacman.ecs.assets import Asset
 from pacman.ecs.coord import Coord2D, CoordAbs2D, PropagateTransform
 from pacman.ecs.schedule import StartupSchedule, SystemSet, UpdateSchedule
@@ -108,9 +110,12 @@ class Sprite2D(Component, Asset):
 
     @override
     @classmethod
-    def load(cls, ty: type, path: str) -> "Sprite2D":
+    def load(cls, ty: type, ident: str) -> "Sprite2D":
         del ty
-        return Sprite2D(pygame.image.load(path))
+
+        return Sprite2D(
+            pygame.image.load(files(pacman).joinpath(ident).open("rb"))
+        )
 
     def copy(self) -> "Sprite2D":
         """Make a shallow copy of this sprite."""
index cb93ed712f053b02726ce18eeb7d341f84f4241d..88f24c75b208d9089a1937396c739c19cfdcaf54 100644 (file)
@@ -3,11 +3,13 @@
 from collections.abc import Callable
 from dataclasses import dataclass, field
 from enum import Enum, auto
+from importlib.resources import files
 from typing import cast, override
 
 import pygame
 from pygame import Color, Vector2
 
+import pacman
 from pacman.ecs.assets import Asset
 from pacman.ecs.coord import (
     CoordAbs2D,
@@ -42,10 +44,14 @@ class Font(Asset, Component):
 
     @override
     @classmethod
-    def load(cls, ty: type, path: str) -> "Font":
+    def load(cls, ty: type, ident: str) -> "Font":
         del ty
-        path, size = path.split(":")
-        return Font(pygame.font.Font(path, int(size)))
+        ident, size = ident.split(":")
+        return Font(
+            pygame.font.Font(
+                files(pacman).joinpath(ident).open("rb"), int(size)
+            )
+        )
 
     def center_text_for(self, text: str, dims: Vector2) -> CoordBundle:
         """Compute how to center the text in the given rectangle."""
index 271a16c27a997cc77831464ad1ed9b8d5aaa425c..ceff113110a0b58fcf0ba041f65c559899114f08 100644 (file)
@@ -1,3 +1,5 @@
+"""All the gameplay logic of pacman."""
+
 import sys
 from collections.abc import Callable
 from dataclasses import dataclass
@@ -5,7 +7,9 @@ from enum import Enum, auto
 from random import random, shuffle
 from typing import cast, override
 
-from mazegenerator import MazeGenerator  # type: ignore[import-untyped]
+from mazegenerator import (  # type: ignore[import-untyped, unused-ignore]
+    MazeGenerator,
+)
 from pydantic import BaseModel, Field
 from pygame import K_BACKSPACE, K_ESCAPE, K_RETURN, K_a, K_c, K_n, K_z, Vector2
 from pygame.math import clamp
@@ -355,7 +359,7 @@ def create_maze(
                 CoordBundle(*(Vector2(x, y).elementwise() * cell_dims).xy),
                 Cell(cell, (x, y)),
                 ShouldRender(),
-                assets.load(f"./asset/walls/{cell}.png"),
+                assets.load(f"walls/{cell}.png"),
             )
             for y, line in enumerate(maze.maze)
             for x, cell in enumerate(line)
@@ -387,7 +391,7 @@ def spawn_superpacgums(
                 (
                     ChildOf(cell),
                     CoordBundle(),
-                    assets.load("./asset/misc/superpacgum.png"),
+                    assets.load("misc/superpacgum.png"),
                     ShouldRender(),
                     SuperPacGum(),
                 )
@@ -418,7 +422,7 @@ def spawn_pacgums(
             (
                 ChildOf(ent),
                 CoordBundle(),
-                assets.load("./asset/misc/pacgum.png"),
+                assets.load("misc/pacgum.png"),
                 ShouldRender(),
                 PacGum(),
             )
@@ -498,9 +502,7 @@ def spawn_pacman(
             AnimationSet(
                 [
                     [
-                        assets.load(
-                            f"./asset/player/player_{direction}{frame}.png"
-                        )
+                        assets.load(f"player/player_{direction}{frame}.png")
                         for frame in "ABCB"
                     ]
                     for direction in "URDL"
@@ -547,18 +549,18 @@ def spawn_ghosts(
                     [
                         [
                             assets.load(
-                                "./asset/enemy/ghost"
+                                "enemy/ghost"
                                 + f"{variant.name}_{direction}.png"
                             )
                         ]
                         for direction in "URDL"
                     ]
                     + [
-                        [assets.load("./asset/enemy/ghost_blue.png")],
-                        [assets.load("./asset/enemy/ghost_dead.png")],
+                        [assets.load("enemy/ghost_blue.png")],
+                        [assets.load("enemy/ghost_dead.png")],
                         [
-                            assets.load("./asset/enemy/ghost_flash.png"),
-                            assets.load("./asset/enemy/ghost_blue.png"),
+                            assets.load("enemy/ghost_flash.png"),
+                            assets.load("enemy/ghost_blue.png"),
                         ],
                     ],
                     6,
@@ -954,7 +956,7 @@ def spawn_game_over(
             GameOver(),
         ),
     )
-    font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:200")
+    font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:200")
     text = state.state
     world.spawn(
         tuple[ChildOf, ShouldRender, CoordBundle, TextDisplay],
@@ -966,7 +968,7 @@ def spawn_game_over(
         ),
     )
 
-    font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:40")
+    font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:40")
     world.spawn(
         tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay],
         (
@@ -989,7 +991,7 @@ def spawn_game_over(
         ),
     )
 
-    font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50")
+    font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:50")
     world.spawn(
         tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay],
         (
@@ -1087,7 +1089,7 @@ def spawn_hud(world: World, fonts: Assets[Font]) -> None:
                 ComputedText(cast(System[str], fn)),
                 TextDisplay(
                     "",
-                    fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"),
+                    fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:30"),
                 ),
                 GameGui(),
             )
@@ -1144,16 +1146,16 @@ def spawn_button(
                 ShouldRender(),
                 AnimationSet(
                     [
-                        [sprites.load("./asset/gui/button_unselected.png")],
-                        [sprites.load("./asset/gui/button_selected.png")],
-                        [sprites.load("./asset/gui/button_selected.png")],
+                        [sprites.load("gui/button_unselected.png")],
+                        [sprites.load("gui/button_selected.png")],
+                        [sprites.load("gui/button_selected.png")],
                     ],
                     1,
                 ),
                 Button(cb),
             ),
         )
-        font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30")
+        font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:30")
         world.spawn(
             tuple[CoordBundle, ShouldRender, TextDisplay, ChildOf],
             (
@@ -1184,7 +1186,7 @@ def spawn_cheat_info(world: World, fonts: Assets[Font]) -> None:
             ShouldRender(),
             TextDisplay(
                 "CHEAT MODE ENABLED",
-                fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:100"),
+                fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:100"),
             ),
             CheatMode(),
         ),
@@ -1208,7 +1210,7 @@ Press 'c' to start chase
                 ShouldRender(),
                 TextDisplay(
                     s,
-                    fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:22"),
+                    fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:22"),
                 ),
                 CoordBundle(depth=-20),
                 CheatMode(),
@@ -1352,7 +1354,7 @@ When you eat a super pacgum, you can eat ghost.
                 ShouldRender(),
                 TextDisplay(
                     "Instructions",
-                    fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50"),
+                    fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:50"),
                 ),
             ),
         )
@@ -1373,9 +1375,7 @@ When you eat a super pacgum, you can eat ghost.
                     ShouldRender(),
                     TextDisplay(
                         s,
-                        fonts.load(
-                            "./asset/fonts/JetBrainsMonoNL-Regular.ttf:20"
-                        ),
+                        fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:20"),
                     ),
                 )
                 for i, s in enumerate(text.split("\n"))
@@ -1404,7 +1404,7 @@ def set_up_highsores(
                 ShouldRender(),
                 TextDisplay(
                     "Highscores",
-                    fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50"),
+                    fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:50"),
                 ),
             ),
         )
@@ -1424,9 +1424,7 @@ def set_up_highsores(
                     ShouldRender(),
                     TextDisplay(
                         f"{i + 1}. {highscores[i][0]} - {highscores[i][1]}",
-                        fonts.load(
-                            "./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"
-                        ),
+                        fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:30"),
                     ),
                 )
                 for i in range(min(len(highscores), 10))
diff --git a/uv.lock b/uv.lock
index cc180d600e1998dc4871f718f406a268ed4f65d2..f19fdb7dc7dbf22e0da1bc01af8c0f5d9c548d5a 100644 (file)
--- a/uv.lock
+++ b/uv.lock
@@ -79,6 +79,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/25/6c/b400476d3ceba681ab929787edc9554f6d88fcc69435eb681b00fc0457a5/ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0", size = 1083655, upload-time = "2026-08-07T11:29:00.349Z" },
 ]
 
+[[package]]
+name = "click"
+version = "8.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
+]
+
 [[package]]
 name = "flake8"
 version = "7.3.0"
@@ -268,6 +277,7 @@ dev = [
     { name = "flake8" },
     { name = "mypy" },
     { name = "ruff" },
+    { name = "shiv" },
     { name = "ty" },
 ]
 
@@ -283,6 +293,7 @@ dev = [
     { name = "flake8", specifier = ">=7.3.0" },
     { name = "mypy", specifier = ">=2.3.1" },
     { name = "ruff", specifier = ">=0.16.4" },
+    { name = "shiv", specifier = ">=1.0.8" },
     { name = "ty", specifier = ">=0.0.75" },
 ]
 
@@ -295,6 +306,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
 ]
 
+[[package]]
+name = "pip"
+version = "26.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fa/e8/0842d757e483e895e670f655084cfe8b8c72580c6769a76b30b2789a0608/pip-26.2.2.tar.gz", hash = "sha256:3a8e5c800492787f33a4114ef54444546c340df428974ebba17e0fd47e11af2f", size = 1863336, upload-time = "2026-10-20T16:36:29.008Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl", hash = "sha256:fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70", size = 1819850, upload-time = "2026-10-20T16:36:27.046Z" },
+]
+
 [[package]]
 name = "pycodestyle"
 version = "2.14.0"
@@ -424,6 +444,29 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" },
 ]
 
+[[package]]
+name = "setuptools"
+version = "84.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/b3/c19426f783614d625699a4b247faf628ea9ab1db228674491767b417f9e1/setuptools-84.2.1.tar.gz", hash = "sha256:3ff8b3b5492a0aa66960f4419365a86b84e013070d4313e4d4a6894f5d64d6bc", size = 1230289, upload-time = "2026-10-20T19:01:37.742Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ef/55/6dc6d6bb953bdfc52519a0059592c52b6e3b8ad51cf8256dffceb55b0610/setuptools-84.2.1-py3-none-any.whl", hash = "sha256:ca140b45d5325a9478f880e351484f62fef15f336462e449ca0c03c873d589c2", size = 876263, upload-time = "2026-10-20T19:01:35.678Z" },
+]
+
+[[package]]
+name = "shiv"
+version = "1.0.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "pip" },
+    { name = "setuptools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/85/004e7123b4821c64be6d9bfed27f63147b4dde929a1cd848f137befeada4/shiv-1.0.8.tar.gz", hash = "sha256:2a68d69e98ce81cb5b8fdafbfc1e27efa93e6d89ca14bfae33482e4176f561d6", size = 32806, upload-time = "2024-11-01T19:47:46.189Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e7/ec/afbb46f7c1ab071a50d92424daf149420ca1f0e02dc51239485747151d6c/shiv-1.0.8-py2.py3-none-any.whl", hash = "sha256:a60e4b05a2d2f8b820d567b1d89ee59af731759771c32c282d03c4ceae6aba24", size = 20516, upload-time = "2024-11-01T19:47:45.061Z" },
+]
+
 [[package]]
 name = "ty"
 version = "0.0.75"