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
View File
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-10
@@ -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.
@@ -0,0 +1,27 @@
# Proposal
## Why
En jeu, localiser le portail de l'End (dans un stronghold) en suivant des yeux de l'Ender à l'écran F3 est fastidieux et imprécis à faire de tête. Deux lancers d'œil de l'Ender réalisés à deux endroits distincts suffisent pourtant à trianguler la position du stronghold : chaque œil vole vers le stronghold, donc la droite (lancé → chute) passe par lui, et l'intersection des deux droites donne sa position. Ce petit programme fait ce calcul à la place du joueur.
## What Changes
- Nouveau script Python interactif unique, sans aucune dépendance externe (stdlib uniquement) : il demande les positions de **lancé** et de **chute** de deux yeux de l'Ender (coordonnées x, z relevées en jeu), puis affiche les coordonnées approximatives du portail de l'End.
- Garde-fous de fiabilité : message d'erreur explicite si les deux droites sont parallèles (aucune intersection exploitable) ou si un lancer n'a pas de direction (chute ≈ lancé) ; avertissement si les droites sont quasi parallèles (angle faible → résultat peu fiable).
- Entrées tolérantes : décimales acceptées (F3), séparateur espace ou virgule, re-demande en cas de saisie invalide plutôt que de planter.
- Tests unitaires (stdlib `unittest`) couvrant la géométrie (intersection, cas parallèles) et l'analyse des saisies.
- README court : mode d'emploi et rappel de la méthode de relevé des coordonnées en jeu.
## Capabilities
### New Capabilities
- `end-portal-triangulation` : calcul de la position approximative du portail de l'End (stronghold) par intersection des trajectoires de deux yeux de l'Ender, à partir des positions de lancé et de chute de chaque œil.
### Modified Capabilities
- Néant (dépôt vierge, aucune spec existante).
## Impact
- Créations : `end_finder.py` (script unique à la racine), `tests/test_triangulation.py`, `README.md`.
- Aucune dépendance externe, aucun outil de packaging (pas de `pyproject.toml` — YAGNI, lançable par `python end_finder.py`).
- Aucun code existant impacté (dépôt vide hors OpenSpec).
@@ -0,0 +1,43 @@
# Spec Delta
## Purpose
Permet de localiser le portail de l'End (stronghold) à partir de deux yeux de l'Ender lancés depuis deux endroits distincts, en calculant l'intersection des droites (lancé → chute) de chaque œil.
## ADDED Requirements
### Requirement: Calcul de la position du portail par intersection
Étant donné deux lancers d'œil de l'Ender, chacun défini par une position de lancé et une position de chute (coordonnées x et z, l'altitude étant ignorée), le système SHALL calculer l'intersection des deux droites (lancé → chute) et l'afficher comme position approximative du portail de l'End.
#### Scenario: Deux lancers exploitables
- **WHEN** l'utilisateur saisit les positions de lancé et de chute de deux yeux dont les trajectoires ne sont ni parallèles ni quasi parallèles
- **THEN** le système affiche les coordonnées (x, z) du point d'intersection, clairement identifiées comme position approximative du portail de l'End
#### Scenario: Coordonnées décimales
- **WHEN** l'utilisateur saisit des positions décimales (ex. relevées via l'écran F3)
- **THEN** le système les accepte telles quelles et le résultat est calculé avec ces valeurs
### Requirement: Saisie interactive des quatre positions
Le système SHALL demander à l'utilisateur, dans un flux interactif, les positions de lancé puis de chute de chaque œil, en n'acceptant que deux nombres par saisie (x et z, séparés par une espace ou une virgule).
#### Scenario: Saisie invalide
- **WHEN** l'utilisateur saisit une valeur non numérique, ou un nombre incorrect de valeurs
- **THEN** le système affiche un message d'erreur et redemande la même saisie, sans s'interrompre
#### Scenario: Lancer sans direction
- **WHEN** la position de chute saisie est confondue avec la position de lancé du même œil (aucune direction exploitable)
- **THEN** le système affiche un message expliquant que ce lancer est inexploitable et redemande une saisie utilisable pour ce lancer
### Requirement: Détection des cas non fiables
Le système SHALL vérifier la fiabilité de la triangulation avant d'afficher un résultat.
#### Scenario: Droites parallèles
- **WHEN** les deux droites (lancé → chute) sont parallèles ou quasi parallèles au point de rendre l'intersection inexploitable
- **THEN** le système affiche une erreur explicite et ne présente aucune coordonnée de portail
#### Scenario: Droites quasi parallèles
- **WHEN** l'angle entre les deux droites est faible (sous le seuil défini) sans être inexploitable
- **THEN** le système affiche les coordonnées du portail accompagnées d'un avertissement indiquant que la triangulation est peu précise, avec l'angle entre les droites
@@ -0,0 +1,19 @@
# Tasks
## 1. Noyau géométrique et tests unitaires
- [x] 1.1 Créer `end_finder.py` avec les fonctions pures `parse_point` (deux nombres, espace ou virgule), `angle_between` (angle en degrés entre les deux droites) et `intersect_lines` (intersection par produits vectoriels, `ValueError` si vecteur direction nul), ainsi que les constantes `PARALLEL_ANGLE_DEG = 0.5` et `WARN_ANGLE_DEG = 10.0` — vérifier via les tests du 1.2
- [x] 1.2 Créer `tests/test_end_finder.py` (stdlib `unittest`) couvrant : parsing des formats valides (`x z`, `x,z`, décimales) et rejets des invalides ; intersection nominale à résultat exact connu ; droites verticales ; droites exactement parallèles ; vecteur nul ; cohérence de `angle_between` — vérifier avec `python -m unittest` depuis la racine (tout passe)
## 2. Flux interactif et validation manuelle
- [x] 2.1 Implémenter `main()` : prompts en français (lancé puis chute de chaque œil), re-saisie en cas d'entrée invalide, re-saisie de la chute si confondue avec le lancé, application des seuils (parallèle → erreur sans coordonnées et sortie non nulle ; angle faible → avertissement avec angle), affichage arrondi à 1 décimale — vérifier manuellement avec `python end_finder.py` sur un cas nominal aux coordonnées connues
- [x] 2.2 Valider manuellement les garde-fous : deux lancers alignés (droites parallèles → message d'erreur, aucune coordonnée affichée) et deux lancers formant un angle sous 10° (coordonnées affichées + avertissement) — vérifier la conformité aux scénarios du spec `end-portal-triangulation`
## 3. Documentation
- [x] 3.1 Rédiger `README.md` : but du programme, mode d'emploi (`python end_finder.py`), méthode de relevé des coordonnées en jeu (F3 au moment du lancer et à la chute), conseil de garder les deux lancers à quelques centaines de blocs l'un de l'autre et de confirmer par un troisième lancer près du point trouvé — vérifier que la commande documentée s'exécute telle quelle
## Workflow follow-up
- Archiver le changement (`opsx-archive`) une fois la revue de code approuvée.