initial commit

This commit is contained in:
2026-11-11 01:51:55 +01:00
commit 2803cebeff
31 changed files with 3510 additions and 0 deletions
@@ -0,0 +1,61 @@
# Design
## Context
Dépôt vierge (aucun code hors OpenSpec). Le programme est un unique script Python sans dépendance externe, destiné à être lancé dans un terminal à côté du jeu. Interface en français ; identifiants de code en anglais. Voir `proposal.md` pour la motivation et `specs/end-portal-triangulation/spec.md` pour le comportement exigé.
## Goals / Non-Goals
**Goals:**
- Géométrie exacte et pure (testable) : intersection de deux droites 2D définies par (lancé, chute) de chaque œil.
- Flux interactif robuste : re-saisie en cas d'entrée invalide ou de lancer sans direction.
- Garde-fous de fiabilité : erreur si droites parallèles, avertissement si angle faible.
**Non-Goals:**
- Pas de GUI, pas de rendu de carte, pas d'export.
- Pas d'arguments en ligne de commande (flux interactif uniquement).
- Pas de calcul reposant sur l'algorithme de génération des strongholds (anneaux, seeds) — le programme ne fait que de la géométrie sur des observations.
- Pas de packaging (`pyproject.toml`), pas de dépendance, pas de gestion de version de Minecraft.
## Decisions
**D1 — Intersection par produits vectoriels, pas par pente.**
Droite 1 : `A + t·d1` avec `d1 = B − A` ; droite 2 : `C + u·d2` avec `d2 = D − C`.
`t = cross(C−A, d2) / cross(d1, d2)` avec `cross(p, q) = p.x·q.z − p.z·q.x` ; le point cherché est `A + t·d1`.
Raison : formule fermée exacte en flottants, sans cas particulier (la forme « pente » `y = mx + p` échoue sur les droites verticales, fréquentes ici). Pas de bibliothèque numérique (YAGNI).
**D2 — Fiabilité mesurée par l'angle entre droites.**
`angle = atan2(|cross(d1, d2)|, |dot(d1, d2)|)` en degrés — angle aigu (0-90°) entre les deux droites, indépendant de l'orientation de chaque vecteur direction : deux yeux volant en directions opposées (deux joueurs partant du même point vers le même stronghold) forment des droites parallèles et doivent être détectés comme tels, ce que la forme sans valeur absolue sur le produit scalaire manquerait (angle ~180°). Deux constantes module :
- `PARALLEL_ANGLE_DEG = 0.5` : sous ce seuil, les droites sont considérées parallèles → message d'erreur, sortie sans coordonnées (code retour non nul).
- `WARN_ANGLE_DEG = 10.0` : sous ce seuil, avertissement affiché avec l'angle, coordonnées quand même affichées.
Raison : une intersection à angle très faible existe mathématiquement mais est à la fois très éloignée et très amplifiée par le bruit de mesure ; il faut distinguer « inexploitable » (erreur) de « peu précis » (avertissement). Les seuils sont des constantes nommées, ajustables en un endroit.
**D3 — Un seul module `end_finder.py`, fonctions pures séparées de l'IO.**
- `parse_point(texte) -> (float, float)` : sépare sur espaces et/ou virgules, exige exactement 2 nombres, lève `ValueError` sinon.
- `angle_between(a, b, c, d) -> float` : degrés entre les directions des deux droites.
- `intersect_lines(a, b, c, d) -> (x, z)` : calcule l'intersection ; `ValueError` si un vecteur direction est nul ; la décision parallèle/avertissement reste dans `main()` (qui orchestre les constantes D2).
- `main()` : boucle de prompts (lancé puis chute de chaque œil), re-saisie en cas d'entrée invalide ou de chute confondue avec le lancé, puis affichage du résultat.
Raison : la géométrie et l'analyse d'entrée sont testables sans mocker l'IO ; `main()` reste fin et non testé automatiquement (trade-off assumé, cf. Risques).
**D4 — Entrées et sorties.**
- Une saisie par point : « `x z` » ou « `x,z` » (décimales acceptées, relevées via F3). L'altitude (y) n'est jamais demandée.
- Re-saisie immédiate (même prompt) en cas d'entrée invalide ; pour une chute confondue avec le lancé (aucune direction), message explicite puis re-saisie de la chute de cet œil.
- Sortie : `Portail de l'End (approx.) : x = <arrondi 1 déc.>, z = <arrondi 1 déc.>` ; avertissement avec angle le cas échéant ; message d'erreur sans coordonnées si parallèles.
**D5 — Tests : stdlib `unittest`, sans dépendance de test.**
`tests/test_end_finder.py` couvre : parsing (formats, cas invalides), `intersect_lines` (cas nominal exact, décimales, lancers verticaux/quasi parallèles, vecteur nul), `angle_between`. Lancement depuis la racine : `python -m unittest`. Le comportement interactif complet de `main()` est validé manuellement (testé à la main une fois).
## Risks / Trade-offs
- [Précision intrinsèque de la méthode] La chute d'un œil est quantifiée au bloc et son déplacement horizontal est faible : la direction (lancé → chute) est bruitée de plusieurs degrés. → Le résultat est affiché comme approximatif (arrondi), et le README recommande un lancer de confirmation près du point trouvé. Pas d'instrumentation au-delà (YAGNI).
- [Deux strongholds visés] Si les deux lancers, trop éloignés l'un de l'autre, pointent vers des strongholds différents, l'intersection est fausse sans qu'aucun garde-fou ne le détecte. → README : garder les deux lancers à quelques centaines de blocs l'un de l'autre.
- [Seuils arbitraires D2] 0,5° / 10° sont des choix de bon sens, pas des mesures. → Constantes nommées, modifiables en un seul endroit.
- [main() non couvert par les tests] Les seuils d'affichage vivent dans `main()`. → Le flux est court et relu en revue ; la géométrie, elle, est couverte à 100 %.
## Migration Plan
Nouveau projet : création pure, aucun rollback nécessaire.
## Open Questions
Aucune.