idea_world_labDEV JOURNAL
vendredi 26 juin 2026

Flux de passage du code source à l'analyse AST

Date de rédaction : 26 juin 2026

Objectif

Ce document renforce l'introduction de docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md.

La feuille de route de référence a déjà organisé le flux suivant.

project source
  -> AST chunks or direct chunks
  -> official docs JSONL retrieval
  -> on-demand LLM verification
  -> score DB
  -> classified filesystem

Ici, nous décrivons comment transmettre les entrées de l'utilisateur (prompt et code source) à l'AST Parser.

Principes fondamentaux

Le prompt et le code source font partie du même groupe d'entrées, mais leurs rôles diffèrent.

Entrée Rôle
Prompt de l'utilisateur Intention d'analyse, type de question, orientation de la sortie attendue
Code source Cible réelle que l'AST Parser doit analyser
Chemin de fichier / informations de projet Métadonnées pour identifier l'unité d'analyse et classer par projet

L'AST Parser analyse le code source. Le prompt n'est pas une cible d'analyse syntaxique.

Cependant, le prompt est utilisé aux étapes suivantes pour décider quel fragment examiner en priorité, quel Retriever employer, et quelles requêtes de validation formuler à Qwen 3.6.

Paquet d'entrée

L'entrée de l'utilisateur est d'abord regroupée en une seule requête d'analyse.

analysis_request
  prompt
  source
  source_kind
  project_id
  source_file
  source_sha256
  source_origin

Chaque élément a la signification suivante.

Élément Description
prompt Question saisie par l'utilisateur. Exemple : « Quel est le sens de ce code ? », « Conversion vers Godot 4 nécessaire ? »
source Texte du code source original. Le conserver tel quel autant que possible.
source_kind Type d'entrée, tel que gdscript, scene, resource, project_config, markdown, unknown, etc.
project_id Identifiant permettant de distinguer le dépôt GitHub ou le projet local
source_file Chemin du fichier dans le projet. Si c’est un simple collage de code, on utilise un chemin temporaire.
source_sha256 Hachage du texte source original. Calculé au moment de la création de la requête, que le code soit collé ou fourni via un fichier.
source_origin Origine, par ex. github, local_directory, uploaded_file, pasted_snippet, etc.

À ce stade, le schéma JSONL de sortie ou les colonnes de la base de données de scores ne sont pas encore définis. Le but est de permettre à l'AST Parser ainsi qu'aux étapes suivantes Retriever/LLM de suivre les mêmes entrées selon les mêmes critères.

Collage d'un code source unique

C’est le cas où l'utilisateur saisit directement le prompt et le code source via l'interface web ou la ligne de commande.

Exemple :

extends KinematicBody2D

func _physics_process(delta):
    move_and_slide()

Flux de traitement :

  1. L’entrée est encapsulée dans analysis_request.
  2. source_origin est définie comme pasted_snippet.
  3. source_file utilise un identifiant temporaire tel que pasted://snippet-<id>.gd puisqu’il n’y a pas de chemin de fichier réel.
  4. source_sha256 est calculé immédiatement à partir du code source collé.
  5. source_kind est estimé à partir de l’extension, du contenu du code et du langage indiqué par l’utilisateur.
  6. Le parseur AST reçoit source, source_kind, source_file et source_sha256.
  7. Le prompt est conservé dans analysis_request.prompt sans être mélangé au parseur.

Entrée du répertoire du projet

C’est le cas où l’on analyse un dépôt GitHub ou un dossier local.

Flux de traitement :

  1. Le répertoire racine du projet est encapsulé dans project_id.
  2. Le système de fichiers est parcouru en se basant sur les chemins relatifs.
  3. Pour chaque fichier, on enregistre d’abord s’il doit être inclus ou exclu.
  4. Les fichiers exclus sont listés dans excluded_files afin d’être visibles dans l’interface web ou les journaux.
  5. Les fichiers inclus sont développés en blocs de texte brut précédés d’un en‑tête # <relative/path>.
  6. Les fichiers .gd sont envoyés au parseur AST pour créer des fonctions/morceaux de code dans l’ordre d’origine.
  7. Les fichiers non destinés au parseur AST sont fragmentés selon des règles spécifiques à leur type.
  8. Chaque fragment, avec son chemin d’origine, son ordre d’origine et son hachage, est transmis à la requête Retriever.

Les fichiers cibles initiaux sont les mêmes que ceux de la feuille de route de référence.

Fichier Direction AST/fragmentation
.gd AST GDScript, fonctions, classes, signaux, variables, candidats d’appels API
.tscn type de nœud de scène, référence de script, référence de ressource, propriété exportée
.tres type de ressource, classe de script, indice de matériel/shader/ressource
project.godot indice de version Godot, autoload, renderer, fonctionnalités, carte d’entrée
README/documentation Exclu par défaut de l’analyse AST du code source. La raison de l’exclusion est consignée dans l’UI/journal.

Déploiement du texte source du projet

L’entrée au niveau du projet est d’abord développée en blocs de texte brut par fichier. Ce texte n’est pas le prompt final à injecter directement dans le LLM, mais une représentation intermédiaire qui préserve les limites de fichiers et les chemins relatifs.

# project.godot
; original project.godot content

# scripts/player.gd
extends KinematicBody2D

func _physics_process(delta):
    move_and_slide(velocity)

# scenes/player.tscn
[gd_scene load_steps=2 format=3]
[node name="Player" type="CharacterBody2D"]

Règles :

  • L’en‑tête du fichier doit être sous la forme # <chemin/relatif>.
  • Le chemin relatif est basé sur la racine du projet.
  • Le corps du fichier doit rester le plus proche possible du texte original.
  • Ce bloc de texte brut est utilisé pour vérifier les limites des fichiers et le débogage.
  • Dans le traitement réel, on ne transmet pas l’ensemble de ce texte brut d’un seul coup à un LLM ou à un analyseur AST.
  • Chaque fichier séparé par un en‑tête subit d’abord une décision d’inclusion/exclusion.
  • Les fichiers exclus sont listés dans excluded_files.
  • Les fichiers .gd inclus passent par analysis_request avant d’être envoyés à l’analyseur AST.
  • Les fichiers texte/paramètres non .gd inclus sont directement envoyés au fragmentateur.

Ainsi, on conserve l’ensemble du “code source du projet” fourni par l’utilisateur sous une forme lisible, tout en pouvant, aux étapes suivantes, suivre les fichiers/fragments exclus, les fragments AST, les fragments directs et les charges du récupérateur par fichier ou fragment.

Plan de vérification basé sur le projet cloné

Le flux du parseur AST vers le récupérateur est difficile à valider uniquement avec un exemple abstrait. On va donc cloner un projet Godot réel, puis vérifier son arborescence de fichiers, le déploiement du texte brut, la séparation des fragments AST/direct, et le flux de requêtes du récupérateur.

Le projet de référence est défini comme suit.

Élément Valeur
dépôt godotengine/godot-demo-projects
chemin du projet 2d/dodge_the_creeps
raison du choix C’est une démo officielle de Godot, contenant des fichiers .gd, .tscn, project.godot, README et des références d’actifs, ce qui facilite la vérification du flux d’entrée du projet.
critère de sauvegarde Le code cloné n’est pas commité dans le dépôt. La documentation ne conserve que l’arborescence et le plan de traitement.

Critères de clonage :

git clone --depth 1 --filter=blob:none --sparse https://github.com/godotengine/godot-demo-projects.git /tmp/idea_world_godot_demo_projects
cd /tmp/idea_world_godot_demo_projects
git sparse-checkout set 2d/dodge_the_creeps

Ce clone est destiné à la vérification du flux. Aucun code source ou asset du projet cloné n’est copié dans le dépôt.

L’objet de la vérification est le chemin complet de chaque fichier du projet cloné. D’abord, dépliez tous les chemins complets dans un ordre trié, puis divisez chaque fichier en fragments lisibles par le LLM. Aucun DB intermédiaire n’est créé ici. L’essentiel n’est pas « de lire tout le dépôt en une fois », mais « de créer des fragments selon l’ordre des chemins et d’appeler plusieurs fois chaque fragment ».

2d/dodge_the_creeps
  .gitignore
  LICENSE
  README.md
  art/House In a Forest Loop.ogg
  art/House In a Forest Loop.ogg.import
  art/enemyFlyingAlt_1.png
  art/enemyFlyingAlt_1.png.import
  art/enemyFlyingAlt_2.png
  art/enemyFlyingAlt_2.png.import
  art/enemySwimming_1.png
  art/enemySwimming_1.png.import
  art/enemySwimming_2.png
  art/enemySwimming_2.png.import
  art/enemyWalking_1.png
  art/enemyWalking_1.png.import
  art/enemyWalking_2.png
  art/enemyWalking_2.png.import
  art/gameover.wav
  art/gameover.wav.import
  art/playerGrey_up1.png
  art/playerGrey_up1.png.import
  art/playerGrey_up2.png
  art/playerGrey_up2.png.import
  art/playerGrey_walk1.png
  art/playerGrey_walk1.png.import
  art/playerGrey_walk2.png
  art/playerGrey_walk2.png.import
  fonts/FONTLOG.txt
  fonts/LICENSE.txt
  fonts/Xolonium-Regular.ttf
  fonts/Xolonium-Regular.ttf.import
  hud.gd
  hud.gd.uid
  hud.tscn
  icon.webp
  icon.webp.import
  main.gd
  main.gd.uid
  main.tscn
  mob.gd
  mob.gd.uid
  mob.tscn
  player.gd
  player.gd.uid
  player.tscn
  project.godot
  screenshots/.gdignore
  screenshots/dodge.png

Critères de fragmentation par fichier :

Type de fichier Méthode de fragmentation Mode d’appel ultérieur
.gd L’AST Parser crée des fragments de fonctions, signaux, variables, extensions de classe, appels d’API dans l’ordre original. Pour chaque fragment AST, le Retriever est appelé et on envoie à LLM : prompt + fragment AST + résultat de recherche.
project.godot N’est pas passé à l’AST Parser. S’il est inclus, on crée des fragments de section/clé‑valeur, scène principale, fonctionnalité, carte d’entrée. Le fragment est envoyé directement au Retriever ou ajouté comme contexte environnant au jugement AST du fichier .gd.
.tscn, .tres N’est pas passé à l’AST Parser. S’il est inclus, on crée des fragments de bloc de nœud, ext_resource, sub_resource, connexion. Le fragment est envoyé directement au Retriever ou ajouté comme contexte environnant au jugement AST du fichier .gd lié.
.md, .txt, LICENSE Exclu par défaut de l’analyse AST du code source. On consigne l’exclusion et la raison dans l’UI/le journal. Si ce n’est pas un mode d’analyse de document séparé, le fragment n’est pas envoyé au Retriever.
.import, .uid, .gitignore, .gdignore N’est pas passé à l’AST Parser. S’il est inclus, on crée des fragments de ligne ou de clé‑valeur. Le fragment est envoyé directement au Retriever ou utilisé uniquement comme contexte environnant pour vérifier les relations de chemin de fichier/ressource.
Images, audio, polices, etc. (binaires) Pas inclus dans l’AST Parser et les octets originaux ne sont pas fournis à LLM. Seuls le chemin et la raison de l’exclusion sont enregistrés. Le chemin n’est utilisé que comme information relationnelle lorsqu’il apparaît dans des fragments texte comme .tscn ou .import.

En d’autres termes, les seules cibles qui entrent dans l’AST Parser sont les fichiers .gd. Le dépôt entier est déroulé dans l’ordre des chemins, mais le résultat n’est pas un « grand texte envoyé à LLM en une fois ». L’en‑tête # <relative/path> sert à indiquer d’où provient chaque fichier et comment il a été découpé en fragments de fonctions/code/configuration avant d’être transmis au Retriever. Les fichiers exclus, comme les .md, restent dans la liste d’exclusion, tandis que les fichiers .gd inclus sont découpés par l’AST Parser en fragments de fonctions ou de code dans l’ordre. Les appels ultérieurs à LLM sont toujours effectués plusieurs fois avec : prompt + fragment actuel + résultat de recherche du Retriever.

for each file in repository path order:
  record file path under "# <relative/path>"
  if file is excluded by source-analysis policy:
    save excluded_files entry with reason
    continue
  if file.path endswith ".gd":
    ast_chunks = ast_parse(full_file_text)
    for each ast_chunk in ast_chunks:
      retrieved = retrieve(build_query_from_chunk(prompt, ast_chunk))
      response = call_llm(prompt + ast_chunk + retrieved)
      validate(response)
      save_chunk_result(response)
  else:
    text_chunks = split_without_ast(file)
    for each text_chunk in text_chunks:
      retrieved = retrieve(build_query_from_chunk(prompt, text_chunk))
      response = call_llm(prompt + text_chunk + retrieved)
      validate(response)
      save_chunk_result(response)

L'unité d'entrée finale du LLM est toujours sous la forme suivante.

llm_judgment_request
  user_prompt
  project_id
  source_file
  chunk_id
  chunk_order
  chunk_kind
  chunk_text
  retrieved_evidence

Entrée de recherche du récupérateur

Le récupérateur ne fait que rechercher les extraits de code que je fournis dans la base de données. Le chemin du fichier, l'ID du fragment et l'ordre du fragment ne sont pas des critères de recherche, mais des informations de suivi. Ainsi, dans l'implémentation, les deux objets sont séparés.

chunk_trace
  project_id
  source_file
  source_sha256
  chunk_id
  chunk_order
  chunk_kind
retriever_query
  query_text
  query_terms
  raw_chunk_text
  symbol_candidates
  api_call_candidates
  reference_candidates
  prompt_terms

chunk_trace n’est utilisé que pour l’interface utilisateur, les journaux, le redémarrage, le débogage et la connexion des réponses LLM. La fonction de recherche du récupérateur ne reçoit que retriever_query.

AST/direct chunk
  -> build_retriever_query(chunk_text, extracted_candidates, user_prompt)
  -> retriever.search(retriever_query)
  -> orchestrator attaches chunk_trace to returned hits
  -> prompt + chunk_text + retrieved_hits -> LLM judgment

En d'autres termes, la recherche se fait avec les valeurs suivantes.

Utilisé pour la recherche Non utilisé pour la recherche
chunk_text source_file
symbol_candidates chunk_id
api_call_candidates chunk_order
reference_candidates source_sha256
prompt_terms project_id

Le besoin d'un chemin de fichier n'est pas pour la recherche mais pour le suivi. Par exemple, il faut une trace comme player.gd:function:_process afin que l'interface web puisse afficher « Cette réponse provient du quatrième fragment de player.gd ». Cependant, lorsque le Retriever recherche des preuves dans la base de données, il doit rechercher non pas le chemin player.gd mais le code à l'intérieur de la fonction _process et les candidats API.

Par exemple, le fragment de la fonction _process de player.gd sépare d'abord les informations de suivi et les informations de recherche.

chunk_trace
  project_id: "github:godotengine/godot-demo-projects/2d/dodge_the_creeps"
  source_file: "player.gd"
  source_sha256: "87f4fcf7481dba031f74a363475cee75b81c3d42eb1347b318f6b824e37329a6"
  chunk_id: "player.gd:function:_process"
  chunk_order: 4
  chunk_kind: "function"
retriever_query
  raw_chunk_text: |
    func _process(delta):
    	var velocity = Vector2.ZERO # The player's movement vector.
    	if Input.is_action_pressed(&"move_right"):
    		velocity.x += 1
    	if Input.is_action_pressed(&"move_left"):
    		velocity.x -= 1
    	if Input.is_action_pressed(&"move_down"):
    		velocity.y += 1
    	if Input.is_action_pressed(&"move_up"):
    		velocity.y -= 1

    	if velocity.length() > 0:
    		velocity = velocity.normalized() * speed
    		$AnimatedSprite2D.play()
    	else:
    		$AnimatedSprite2D.stop()

    	position += velocity * delta
    	position = position.clamp(Vector2.ZERO, screen_size)

    	if velocity.x != 0:
    		$AnimatedSprite2D.animation = &"right"
    		$AnimatedSprite2D.flip_v = false
    		$Trail.rotation = 0
    		$AnimatedSprite2D.flip_h = velocity.x < 0
    	elif velocity.y != 0:
    		$AnimatedSprite2D.animation = &"up"
    		rotation = PI if velocity.y > 0 else 0
  symbol_candidates:
    - "Vector2"
    - "AnimatedSprite2D"
  api_call_candidates:
    - "Input.is_action_pressed"
    - "Vector2.ZERO"
    - "velocity.normalized"
    - "position.clamp"
  reference_candidates: []
  prompt_terms:
    - "Godot 3"
    - "Godot 4"
    - "migration"
  query_terms:
    - "Input.is_action_pressed"
    - "Vector2.ZERO"
    - "velocity.normalized"
    - "position.clamp"
    - "Vector2"
    - "AnimatedSprite2D"
    - "_process"
    - "Godot 3"
    - "Godot 4"
    - "migration"
  query_text: "Input.is_action_pressed Vector2.ZERO velocity.normalized position.clamp Vector2 AnimatedSprite2D _process Godot 3 Godot 4 migration"

Critères du code de recherche Retriever

Le code ci‑dessous constitue la référence pour fixer la direction de l’implémentation. On ne transmet que le RetrieverQuery au Retriever. Le ChunkTrace est conservé par l’orchestrateur en dehors du Retriever, puis ré‑associé aux résultats de recherche.

from dataclasses import dataclass, field
import re


@dataclass(frozen=True)
class ChunkTrace:
    project_id: str
    source_file: str
    source_sha256: str
    chunk_id: str
    chunk_order: int
    chunk_kind: str


@dataclass(frozen=True)
class RetrieverQuery:
    query_text: str
    query_terms: list[str]
    raw_chunk_text: str
    symbol_candidates: list[str] = field(default_factory=list)
    api_call_candidates: list[str] = field(default_factory=list)
    reference_candidates: list[str] = field(default_factory=list)
    prompt_terms: list[str] = field(default_factory=list)


@dataclass(frozen=True)
class RetrievalHit:
    table_name: str
    record_id: str
    score: float
    title: str
    content: str
    metadata: dict


IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)?")


def unique_keep_order(values: list[str]) -> list[str]:
    seen: set[str] = set()
    result: list[str] = []
    for value in values:
        normalized = value.strip()
        if normalized and normalized not in seen:
            seen.add(normalized)
            result.append(normalized)
    return result


def extract_identifiers(chunk_text: str) -> list[str]:
    return unique_keep_order(IDENTIFIER_RE.findall(chunk_text))


def extract_prompt_terms(user_prompt: str) -> list[str]:
    terms: list[str] = []
    prompt = user_prompt.lower()
    if "godot 3" in prompt or "godot3" in prompt:
        terms.append("Godot 3")
    if "godot 4" in prompt or "godot4" in prompt:
        terms.append("Godot 4")
    if "변환" in user_prompt or "마이그레이션" in user_prompt or "migration" in prompt:
        terms.extend(["migration", "deprecated", "renamed"])
    if "설명" in user_prompt or "뜻" in user_prompt or "explain" in prompt:
        terms.extend(["description", "usage"])
    return terms


def build_retriever_query(
    *,
    user_prompt: str,
    chunk_text: str,
    symbol_candidates: list[str],
    api_call_candidates: list[str],
    reference_candidates: list[str],
) -> RetrieverQuery:
    prompt_terms = extract_prompt_terms(user_prompt)
    query_terms = unique_keep_order(
        api_call_candidates
        + symbol_candidates
        + reference_candidates
        + extract_identifiers(chunk_text)
        + prompt_terms
    )
    return RetrieverQuery(
        query_text=" ".join(query_terms),
        query_terms=query_terms,
        raw_chunk_text=chunk_text,
        symbol_candidates=symbol_candidates,
        api_call_candidates=api_call_candidates,
        reference_candidates=reference_candidates,
        prompt_terms=prompt_terms,
    )


def build_embedding_text(query: RetrieverQuery) -> str:
    return "\n".join(
        [
            query.raw_chunk_text,
            " ".join(query.api_call_candidates),
            " ".join(query.symbol_candidates),
            " ".join(query.reference_candidates),
            " ".join(query.prompt_terms),
        ]
    )


def retrieve_for_query(db, embedder, query: RetrieverQuery, limit_per_table: int = 5) -> list[RetrievalHit]:
    query_embedding = embedder.embed_query(build_embedding_text(query))
    tables = ["docs_chunks", "api_mapping", "label_prototypes"]
    hits: list[RetrievalHit] = []
    for table_name in tables:
        hits.extend(search_table(db, table_name, query, query_embedding, limit_per_table))
    hits.sort(key=lambda hit: hit.score, reverse=True)
    return hits


def search_table(db, table_name: str, query: RetrieverQuery, query_embedding: list[float], limit: int) -> list[RetrievalHit]:
    sql = """
    select
      %(table_name)s as table_name,
      id::text as record_id,
      title,
      content,
      metadata,
      1 - (embedding <=> %(query_embedding)s::vector) as vector_score,
      ts_rank(search_tsv, websearch_to_tsquery('simple', %(query_text)s)) as text_score,
      similarity(search_text, %(query_text)s) as trigram_score
    from godot_rag.dynamic_retrieval_view
    where table_name = %(table_name)s
      and (
        embedding is not null
        or
        search_tsv @@ websearch_to_tsquery('simple', %(query_text)s)
        or search_text %% %(query_text)s
      )
    order by
      embedding <=> %(query_embedding)s::vector asc,
      ts_rank(search_tsv, websearch_to_tsquery('simple', %(query_text)s)) desc,
      similarity(search_text, %(query_text)s) desc
    limit %(limit)s
    """
    rows = db.fetch_all(
        sql,
        {
            "table_name": table_name,
            "query_text": query.query_text,
            "query_embedding": query_embedding,
            "limit": limit,
        },
    )
    return [
        RetrievalHit(
            table_name=row["table_name"],
            record_id=row["record_id"],
            score=float(row["vector_score"] or 0) + float(row["text_score"] or 0) + float(row["trigram_score"] or 0),
            title=row["title"],
            content=row["content"],
            metadata={
                **row["metadata"],
                "query_text": query.query_text,
                "query_terms": query.query_terms,
            },
        )
        for row in rows
    ]


def retrieve_with_trace(db, embedder, trace: ChunkTrace, query: RetrieverQuery) -> dict:
    hits = retrieve_for_query(db, embedder, query)
    return {
        "trace": trace,
        "retriever_query": query,
        "hits": hits,
    }

Le point important dans le code ci‑dessus est le suivant :

  • La fonction de recherche du Retriever ne prend pas source_file, chunk_id, chunk_order comme critères de recherche.
  • Le Retriever effectue une recherche vectorielle avec le query_embedding créé à partir du fragment de code, et query.query_text est utilisé pour la recherche auxiliaire par mots‑clés/trigrammes.
  • Le query_embedding est construit en combinant raw_chunk_text, les candidats API, les candidats de symboles et les intentions du prompt.
  • query.query_text est créé à partir des API, symboles, identifiants et intentions du prompt extraits du fragment de code interne au fichier.
  • Le chemin du fichier et l’ordre des chunks ne sont conservés que dans ChunkTrace.
  • retrieve_with_trace est la fonction orchestrateur qui regroupe les résultats et le trace après la recherche. La recherche elle‑même est effectuée par retrieve_for_query.
  • docs_chunks, api_mapping, label_prototypes sont recherchés avec le même RetrieverQuery.

godot_rag.dynamic_retrieval_view n’est pas une nouvelle logique de décision, mais une vue de normalisation pour la recherche. Même si la structure du payload des trois tables diffère, le Retriever voit les mêmes colonnes.

dynamic_retrieval_view
  table_name
  id
  title
  content
  embedding
  search_text
  search_tsv
  metadata

Les cibles de recherche sont déployées comme suit. Ici aussi, les champs de source tels que source_file, source_url sont conservés comme métadonnées, mais ne sont pas inclus dans la chaîne de recherche principale. La recherche doit se faire sur les API/symboles/modèles du fragment de code.

Table title content Valeur dans search_text
docs_chunks Titre du document, chemin de section, symbole Fragment du corps du document doc_type, symbol, section_path, content, api_symbols
api_mapping Nom de l'API modifiée ou nom d'API précédent Description de la migration, raison du changement, exemple old_symbol, new_symbol, change_type, description, before_code, after_code
label_prototypes Nom du prototype ou étiquette Mode d'utilisation de la fonction, composition des arguments, exemple de modification du modèle d'appel label, input_pattern, prompt_pattern, before_code, after_code, expected_response, explanation

Exemple de forme de vue SQL :

create or replace view godot_rag.dynamic_retrieval_view as
select
  'docs_chunks' as table_name,
  id,
  coalesce(payload->>'title', payload->>'symbol', source_file) as title,
  coalesce(payload->>'content', '') as content,
  embedding,
  concat_ws(
    ' ',
    payload->>'doc_type',
    payload->>'symbol',
    payload->>'section_path',
    payload->>'content',
    payload->>'api_symbols'
  ) as search_text,
  search_tsv,
  payload || jsonb_build_object('source_file', source_file, 'source_url', payload->>'source_url') as metadata
from godot_rag.docs_chunks
union all
select
  'api_mapping' as table_name,
  id,
  concat_ws(' -> ', payload->>'old_symbol', payload->>'new_symbol') as title,
  concat_ws(' ', payload->>'description', payload->>'rationale') as content,
  embedding,
  concat_ws(
    ' ',
    payload->>'old_symbol',
    payload->>'new_symbol',
    payload->>'change_type',
    payload->>'description',
    payload->>'before_code',
    payload->>'after_code'
  ) as search_text,
  search_tsv,
  payload || jsonb_build_object('source_file', source_file, 'source_url', payload->>'source_url') as metadata
from godot_rag.api_mapping
union all
select
  'label_prototypes' as table_name,
  id,
  coalesce(payload->>'label', payload->>'name', source_file) as title,
  concat_ws(' ', payload->>'expected_response', payload->>'explanation') as content,
  embedding,
  concat_ws(
    ' ',
    payload->>'label',
    payload->>'input_pattern',
    payload->>'prompt_pattern',
    payload->>'before_code',
    payload->>'after_code',
    payload->>'expected_response',
    payload->>'explanation'
  ) as search_text,
  search_tsv,
  payload || jsonb_build_object('source_file', source_file) as metadata
from godot_rag.label_prototypes;

Dans cette vue, la valeur réellement recherchée par le Retriever est query_text. Par exemple, pour le chunk _process, il recherche comme suit.

query_text =
  "Input.is_action_pressed Vector2.ZERO velocity.normalized position.clamp Vector2 AnimatedSprite2D _process Godot 3 Godot 4 migration"

Les résultats de recherche ne sont pas des réponses définitives à insérer directement dans le LLM, mais des preuves candidates. Lors d’une demande de jugement au LLM, on fournit à la fois le fragment et les candidats de recherche, et Qwen 3.6 vérifie à nouveau « si cette preuve est liée au fragment actuel ».

Critères de débogage de transmission

Pour empêcher l’IA de choisir aléatoirement « seules les fonctions essentielles » dans un fichier .gd et de les envoyer, ou, à l’inverse, d’envoyer l’ensemble du fichier comme une seule requête LLM, il faut pouvoir vérifier les avant‑et‑après par diff. L’objet de la vérification n’est pas « le fichier source complet a‑t‑il été envoyé au LLM », mais « les morceaux de fonctions/code créés à partir du fichier source ont‑ils été suivis dans l’ordre original, sans omission, et chaque morceau a‑t‑il été transmis via une requête Retriever ».

Lors de l’étape de déploiement du fichier, on commence par créer des blocs de texte source séparés par chemin.

# player.gd
extends Area2D

signal hit

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

func _ready():
	screen_size = get_viewport_rect().size
	hide()


func _process(delta):
	var velocity = Vector2.ZERO # The player's movement vector.
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1
	if Input.is_action_pressed(&"move_down"):
		velocity.y += 1
	if Input.is_action_pressed(&"move_up"):
		velocity.y -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

	if velocity.x != 0:
		$AnimatedSprite2D.animation = &"right"
		$AnimatedSprite2D.flip_v = false
		$Trail.rotation = 0
		$AnimatedSprite2D.flip_h = velocity.x < 0
	elif velocity.y != 0:
		$AnimatedSprite2D.animation = &"up"
		rotation = PI if velocity.y > 0 else 0


func start(pos):
	position = pos
	rotation = 0
	show()
	$CollisionShape2D.disabled = false


func _on_body_entered(_body):
	hide() # Player disappears after being hit.
	hit.emit()
	# Must be deferred as we can't change physics properties on a physics callback.
	$CollisionShape2D.set_deferred(&"disabled", true)

Ensuite, il laisse la liste des fragments créée par l'AST Parser.

ast_chunk_trace
  source_file: "player.gd"
  source_kind: "gdscript"
  source_sha256: "87f4fcf7481dba031f74a363475cee75b81c3d42eb1347b318f6b824e37329a6"
  chunks:
    - chunk_order: 1
      chunk_id: "player.gd:class_extends:1"
      node_kind: "class_extends"
      code_text: "extends Area2D"
    - chunk_order: 2
      chunk_id: "player.gd:signal:hit"
      node_kind: "signal"
      code_text: "signal hit"
    - chunk_order: 3
      chunk_id: "player.gd:function:_ready"
      node_kind: "function"
      code_text: |
        func _ready():
        	screen_size = get_viewport_rect().size
        	hide()
    - chunk_order: 4
      chunk_id: "player.gd:function:_process"
      node_kind: "function"
      code_text: |
        func _process(delta):
        	var velocity = Vector2.ZERO # The player's movement vector.
        	if Input.is_action_pressed(&"move_right"):
        		velocity.x += 1
        	if Input.is_action_pressed(&"move_left"):
        		velocity.x -= 1
        	if Input.is_action_pressed(&"move_down"):
        		velocity.y += 1
        	if Input.is_action_pressed(&"move_up"):
        		velocity.y -= 1

        	if velocity.length() > 0:
        		velocity = velocity.normalized() * speed
        		$AnimatedSprite2D.play()
        	else:
        		$AnimatedSprite2D.stop()

        	position += velocity * delta
        	position = position.clamp(Vector2.ZERO, screen_size)

        	if velocity.x != 0:
        		$AnimatedSprite2D.animation = &"right"
        		$AnimatedSprite2D.flip_v = false
        		$Trail.rotation = 0
        		$AnimatedSprite2D.flip_h = velocity.x < 0
        	elif velocity.y != 0:
        		$AnimatedSprite2D.animation = &"up"
        		rotation = PI if velocity.y > 0 else 0
    - chunk_order: 5
      chunk_id: "player.gd:function:start"
      node_kind: "function"
      code_text: |
        func start(pos):
        	position = pos
        	rotation = 0
        	show()
        	$CollisionShape2D.disabled = false
    - chunk_order: 6
      chunk_id: "player.gd:function:_on_body_entered"
      node_kind: "function"
      code_text: |
        func _on_body_entered(_body):
        	hide() # Player disappears after being hit.
        	hit.emit()
        	# Must be deferred as we can't change physics properties on a physics callback.
        	$CollisionShape2D.set_deferred(&"disabled", true)

Le payload du Retriever est également conservé par morceaux juste avant l'envoi.

retrieval_payload
  source_file: "player.gd"
  chunk_order: 4
  chunk_id: "player.gd:function:_process"
  chunk_text: |
    func _process(delta):
    	var velocity = Vector2.ZERO # The player's movement vector.
    	if Input.is_action_pressed(&"move_right"):
    		velocity.x += 1
    	if Input.is_action_pressed(&"move_left"):
    		velocity.x -= 1
    	if Input.is_action_pressed(&"move_down"):
    		velocity.y += 1
    	if Input.is_action_pressed(&"move_up"):
    		velocity.y -= 1

    	if velocity.length() > 0:
    		velocity = velocity.normalized() * speed
    		$AnimatedSprite2D.play()
    	else:
    		$AnimatedSprite2D.stop()

    	position += velocity * delta
    	position = position.clamp(Vector2.ZERO, screen_size)

    	if velocity.x != 0:
    		$AnimatedSprite2D.animation = &"right"
    		$AnimatedSprite2D.flip_v = false
    		$Trail.rotation = 0
    		$AnimatedSprite2D.flip_h = velocity.x < 0
    	elif velocity.y != 0:
    		$AnimatedSprite2D.animation = &"up"
    		rotation = PI if velocity.y > 0 else 0

Règles de validation :

expanded_source_block["player.gd"].sha256 == ast_chunk_trace.source_sha256
ast_chunk_trace.chunks[].chunk_order is strictly increasing
retrieval_payload.chunk_id exists in ast_chunk_trace.chunks[].chunk_id
retrieval_payload.chunk_order matches ast_chunk_trace.chunks[].chunk_order

Si cette validation échoue, l’envoi n’est pas effectué. Cette règle est particulièrement importante dans les fichiers .gd. L’objectif n’est pas d’insérer l’ensemble du fichier .gd dans le LLM, mais de vérifier que les fragments de fonctions/code provenant de .gd arrivent jusqu’au Retriever sans perdre le chemin d’accès d’origine ni l’ordre d’origine.

Lorsque ce projet est fourni en entrée, il est d’abord développé en un groupe de texte brut vérifiable par l’humain. Le bloc ci‑dessous montre le texte complet des fichiers texte, à l’exception des actifs binaires, présenté par unité # <relative/path>. Cela inclut également des fichiers qui sont généralement exclus de l’analyse du code source, comme .md ou LICENSE. Dans l’exemple de documentation, le contenu à l’intérieur du chemin relatif est laissé complet, sans omission, afin de vérifier les critères de suivi. Cependant, si une politique d’exclusion s’applique, les fichiers exclus ne sont pas envoyés au Retriever et sont répertoriés dans excluded_files avec la raison correspondante.


# .gitignore
.import
logs/

# LICENSE
MIT License

Copyright (c) 2017 KidsCanCode

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

# README.md
# Dodge the Creeps

This is a simple game where your character must move
and avoid the enemies for as long as possible.

This is a finished version of the game featured in the
["Your first 2D game"](https://docs.godotengine.org/en/latest/getting_started/first_2d_game/index.html)
tutorial in the documentation. For more details,
consider following the tutorial in the documentation.

Language: GDScript

Renderer: Compatibility

> [!NOTE]
>
> There is a C# version available [here](https://github.com/godotengine/godot-demo-projects/tree/master/mono/dodge_the_creeps).

Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2712

## Screenshots

![GIF from the documentation](https://docs.godotengine.org/en/latest/_images/dodge_preview.gif)

![Screenshot](/content/docs/fr/roadmaps/screenshots/dodge.png)

## Copying

`art/House In a Forest Loop.ogg` Copyright &copy; 2012 [HorrorPen](https://opengameart.org/users/horrorpen), [CC-BY 3.0: Attribution](https://creativecommons.org/licenses/by/3.0/). Source: https://opengameart.org/content/loop-house-in-a-forest

Images are from "Abstract Platformer". Created in 2016 by kenney.nl, [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). Source: https://www.kenney.nl/assets/abstract-platformer

Font is "Xolonium". Copyright &copy; 2011-2016 Severin Meyer <sev.ch@web.de>, with Reserved Font Name Xolonium, SIL open font license version 1.1. Details are in `fonts/LICENSE.txt`.

# art/House In a Forest Loop.ogg.import
[remap]

importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://sgfduhhw4pno"
path="res://.godot/imported/House In a Forest Loop.ogg-1a6a72ae843ad792b7039931227e8d50.oggvorbisstr"

[deps]

source_file="res://art/House In a Forest Loop.ogg"
dest_files=["res://.godot/imported/House In a Forest Loop.ogg-1a6a72ae843ad792b7039931227e8d50.oggvorbisstr"]

[params]

loop=true
loop_offset=0.0
bpm=0.0
beat_count=0
bar_beats=4

# art/enemyFlyingAlt_1.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dun74wipekpfq"
path="res://.godot/imported/enemyFlyingAlt_1.png-559f599b16c69b112c1b53f6332e9489.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/enemyFlyingAlt_1.png"
dest_files=["res://.godot/imported/enemyFlyingAlt_1.png-559f599b16c69b112c1b53f6332e9489.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/enemyFlyingAlt_2.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://vusf51hepduk"
path="res://.godot/imported/enemyFlyingAlt_2.png-31dc7310eda6e1b721224f3cd932c076.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/enemyFlyingAlt_2.png"
dest_files=["res://.godot/imported/enemyFlyingAlt_2.png-31dc7310eda6e1b721224f3cd932c076.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/enemySwimming_1.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://d182mv7y80xqy"
path="res://.godot/imported/enemySwimming_1.png-dd0e11759dc3d624c8a704f6e98a3d80.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/enemySwimming_1.png"
dest_files=["res://.godot/imported/enemySwimming_1.png-dd0e11759dc3d624c8a704f6e98a3d80.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/enemySwimming_2.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dmgglhdyowipd"
path="res://.godot/imported/enemySwimming_2.png-4c0cbc0732264c4ea3290340bd4a0a62.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/enemySwimming_2.png"
dest_files=["res://.godot/imported/enemySwimming_2.png-4c0cbc0732264c4ea3290340bd4a0a62.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/enemyWalking_1.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dgwhuvn7qb4iy"
path="res://.godot/imported/enemyWalking_1.png-5af6eedbe61b701677d490ffdc1e6471.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/enemyWalking_1.png"
dest_files=["res://.godot/imported/enemyWalking_1.png-5af6eedbe61b701677d490ffdc1e6471.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/enemyWalking_2.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dyw702efe6meu"
path="res://.godot/imported/enemyWalking_2.png-67c480ed60c35e95f5acb0436246b935.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/enemyWalking_2.png"
dest_files=["res://.godot/imported/enemyWalking_2.png-67c480ed60c35e95f5acb0436246b935.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/gameover.wav.import
[remap]

importer="wav"
type="AudioStreamWAV"
uid="uid://td2mgko63p61"
path="res://.godot/imported/gameover.wav-98c95c744b35280048c2bd093cf8a356.sample"

[deps]

source_file="res://art/gameover.wav"
dest_files=["res://.godot/imported/gameover.wav-98c95c744b35280048c2bd093cf8a356.sample"]

[params]

force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=true
edit/normalize=true
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

# art/playerGrey_up1.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://bcow5c46vixno"
path="res://.godot/imported/playerGrey_up1.png-6bd114d0a6beac91f48e3a7314d44564.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/playerGrey_up1.png"
dest_files=["res://.godot/imported/playerGrey_up1.png-6bd114d0a6beac91f48e3a7314d44564.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/playerGrey_up2.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dw3lwgwhpbfx8"
path="res://.godot/imported/playerGrey_up2.png-d6aba85f5f2675ebc7045efa7552ee79.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/playerGrey_up2.png"
dest_files=["res://.godot/imported/playerGrey_up2.png-d6aba85f5f2675ebc7045efa7552ee79.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/playerGrey_walk1.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://b2aofu01vxvea"
path="res://.godot/imported/playerGrey_walk1.png-c4773fe7a7bf85d7ab732eb4458c2742.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/playerGrey_walk1.png"
dest_files=["res://.godot/imported/playerGrey_walk1.png-c4773fe7a7bf85d7ab732eb4458c2742.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# art/playerGrey_walk2.png.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://ddjou2q6gxlfr"
path="res://.godot/imported/playerGrey_walk2.png-34d2d916366100182d08037c51884043.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://art/playerGrey_walk2.png"
dest_files=["res://.godot/imported/playerGrey_walk2.png-34d2d916366100182d08037c51884043.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# fonts/FONTLOG.txt
Please distribute this file along with the Xolonium fonts when possible.


Source

	Find the sourcefiles of Xolonium at
	<gitlab.com/sev/xolonium>


Credits

	Xolonium is created with FontForge <fontforge.org>,
	Inkscape <inkscape.org>, Python <python.org>, and
	FontTools <github.com/fonttools>.

	It originated as a custom font for the open-source
	game Xonotic <xonotic.org>. With many thanks to the
	Xonotic community for your support.


Supported OpenType features

	case  Provides case sensitive placement of punctuation,
	      brackets, and math symbols for uppercase text.
	frac  Replaces number/number sequences with diagonal fractions.
	      Numbers that touch a slash should not exceed 10 digits.
	kern  Provides kerning for Latin, Greek, and Cyrillic scripts.
	locl  Dutch: Replaces j with a stressed version if it follows í.
	      Sami: Replaces n-form Eng with the preferred N-form version.
	      Romanian and Moldovan: Replaces ŞşŢţ with the preferred ȘșȚț.
	pnum  Replaces monospaced digits with proportional versions.
	sinf  Replaces digits with scientific inferiors below the baseline.
	subs  Replaces digits with subscript versions on the baseline.
	sups  Replaces digits with superscript versions.
	zero  Replaces zero with a slashed version.


Supported glyph sets

	Adobe Latin 3
	OpenType W1G
	ISO 8859-1   Western European
	ISO 8859-2   Central European
	ISO 8859-3   South European
	ISO 8859-4   North European
	ISO 8859-5   Cyrillic
	ISO 8859-7   Greek
	ISO 8859-9   Turkish
	ISO 8859-10  Nordic
	ISO 8859-13  Baltic Rim
	ISO 8859-14  Celtic
	ISO 8859-15  Western European
	ISO 8859-16  South-Eastern European


Available glyphs

	 !"#$%&'()*+,-./0123456789:;<=>?
	@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
	`abcdefghijklmnopqrstuvwxyz{|}~

	 ¡¢£¤¥¦§¨©ª«¬ ®¯°±²³´µ¶·¸¹º»¼½¾¿
	ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß
	àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
	ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğ
	ĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľ
	ĿŀŁłŃńŅņŇňŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞş
	ŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽž
	ƒǺǻǼǽǾǿȘșȚțȷ

	ˆˇˉ˘˙˚˛˜˝

	ͺ;΄΅Ά·ΈΉΊΌΎΏΐ
	ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰ
	αβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ

	ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОП
	РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп
	рстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџ
	ѢѣѲѳѴѵҐґҒғҔҕҖҗҘҙҚқҜҝҞҟҠҡҢңҤҥҦҧҨҩ
	ҪҫҬҭҮүҰұҲҳҴҵҶҷҸҹҺһҼҽӀӁӂӇӈӋӌӏӐӑӒӓ
	ӔӕӖӗӘәӜӝӞӟӠӡӢӣӤӥӦӧӨөӮӯӰӱӲӳӴӵӶӷӸӹ
	Ԥԥ

	ḂḃḊḋḞḟṀṁṖṗṠṡṪṫẀẁẂẃẄẅẞỲỳ

	     ‒–—―‘’‚‛“”„‟†‡•…‰′″‹›‽‾⁄
	⁰⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎
	₤₦₩₫€₯₱₹₺₽₿
	℅ℓ№℗™Ω℮
	⅛⅜⅝⅞
	←↑→↓
	∂∆∏∑−∕∙√∞∟∫≈≠≤≥
	⌖
	■▬▮▰▲▶▼◀◆◊●◢◣◤◥
	☄★☠☢☣⚙⚛⚠⚡⛔
	❇❈❌❤❰❱❲❳
	fffiflffiffl
	🌌🌍🌎🌏👽💣🔥🔫
	😁😃😄😆😇😈😉😊😎😐😒😕😘
	😛😝😞😟😠😣😭😮😲😴😵
	🚀


Debugging glyphs

	  U+EFFD  Font version
	  U+F000  Font hinting indicator


Changelog

	Xolonium 4.1  2016-11-22  Severin Meyer  <sev.ch@web.de>
		Reverted frac OpenType feature to a more stable implementation

	Xolonium 4.0  2016-10-08  Severin Meyer  <sev.ch@web.de>
		Decreased width of most glyphs
		Thinner vertical stems in Xolonium-Regular
		Thicker horizontal stems in Xolonium-Bold
		Revised diagonal stems
		Lowered middle bars
		Revised diacritical bars
		Added glyphs:
			ӏẞ₿
			U+2007 U+2008 U+2009 U+200A U+202F
			U+EFFD U+F000
		Revised glyphs:
			$&,JKQRXkwxy~¢¤ßǻ˜ζκλμξφЖУжћѴѵ∕₱₺₦₩€ℓ№≈ffffiffl
			❤🌍🌎🌏😁😄😇😈😉😊😘😭😮😴🚀
		Removed uncommon glyphs:
			ʼnſʼҌҍҎҏҾҿӃӄӇӈӚӛӪӫӬӭ
			U+0312 U+0313 U+0326
		Simplified OpenType features pnum, zero, and case
		Removed OpenType feature dlig
		Revised vertical metrics
		Merged outlines of composite glyphs in otf version
		Added ttf version with custom outlines and instructions
		Added woff and woff2 version

	Xolonium 3.1  2015-06-10  Severin Meyer  <sev.ch@web.de>
		Added currency glyphs:
			₦₩₫₱₹₺₽
		Revised glyph:
			₯
		Relicensed public release under the SIL Open Font License 1.1

	Xolonium 3.0  2015-05-04  Severin Meyer  <sev.ch@web.de>
		Decreased width of glyphs
		Decreased descender height
		Increased height of super/subscript glyphs
		Revised width of dashes, underscore, and overscore
		Sharper bends with more circular proportions
		Decreased stroke thickness of mathematical glyphs
		Revised diacritical marks
		Revised diacritical bars
		Revised Cyrillic hooks
		Revised glyphs:
			GQRYjmuwßŊŒſƒǻfffiffiffl
			ΞΨΩδζιξπςστυφω
			ЉЄДЛУЭЯбдлэяєљђєћѢѣҨҩҼҽӃӄӘә
			#$&'()*,/69?@[]{}~¡£¤¥§©®¿
			‹›₤€₯ℓ№℗℮←↑→↓∂∏∑∞≈▰☄❈❰❱❲❳😝
		Raised vertical position of mathematical glyphs
		Unified advance width of numeral and monetary glyphs
		Unified advance width of mathematical glyphs
		Revised bearings
		Rewrote kern feature
		Bolder Xolonium-Bold with improved proportions
		Updated glyph names to conform to the AGLFN 1.7
		Revised hints and PS Private Dictionary
		Added glyphs:
			ӶӷԤԥ
		Added OpenType features:
			case frac liga locl pnum sinf subs sups zero

	Xolonium 2.4  2014-12-23  Severin Meyer  <sev.ch@web.de>
		Added dingbats:
			⛔💣🔥
		Revised size and design of emoticons
		Revised dingbats:
			⌖☄☠☣⚙⚛⚠⚡❇❈🌌🌍🌎🌏🔫
		Removed dingbat:
			💥

	Xolonium 2.3  2014-08-14  Severin Meyer  <sev.ch@web.de>
		Bugfixed ε and έ, thanks to bowzee for the feedback

	Xolonium 2.2  2014-03-01  Severin Meyer  <sev.ch@web.de>
		Added dingbats:
			⌖◆●❌💥
		Revised dingbats:
			•←↑→↓◊☄★☠☣⚙⚛⚠⚡❇❈❤🌌🌍🌎🌏👽🔫🚀
		Removed dingbats:
			♻✪💡📡🔋🔧🔭

	Xolonium 2.1  2013-10-20  Severin Meyer  <sev.ch@web.de>
		Added dingbats:
			←↑→↓❰❱❲❳■▬▮▰▲▶▼◀◢◣◤◥
			☄★☠☢☣♻⚙⚛⚠⚡✪❇❈❤
			🌌🌍🌎🌏👽💡📡🔋🔧🔫🔭🚀
			😁😃😄😆😇😈😉😊😎😐😒😕
			😘😛😝😞😟😠😣😭😮😲😴😵

	Xolonium 2.0.1  2013-07-12  Severin Meyer  <sev.ch@web.de>
		Reorganised and simplified files

	Xolonium 2.0  2012-08-11  Severin Meyer  <sev.ch@web.de>
		Revised bends
		Revised thickness of uppercase diagonal stems
		Revised diacritical marks
		Revised hints and PS Private Dictionary
		Revised glyphs:
			*1469@DPRly{}§©®¶ÐÞƒΘΞαεζνξνυЄЉЊ
			ЏБЗЛУЧЪЫЬЭЯбзлчъыьэяєљњџ•€∂∙√∞∫≠
		Completed glyph sets:
			Adobe Latin 3
			OpenType World Glyph Set 1 (W1G)
			Ghostscript Standard (ghostscript-fonts-std-8.11)
		Added OpenType kern feature
		Added Xolonium-Bold

	Xolonium 1.2  2011-02-12  Severin Meyer  <sev.ch@web.de>
		Revised glyphs:
			D·Ðı
		Completed glyph sets:
			ISO 8859-7 (Greek)
			Unicode Latin Extended-A block
		Added glyphs:
			†‡•…‰⁄™∂∑−√∞≠≤≥

	Xolonium 1.1  2011-01-17  Severin Meyer  <sev.ch@web.de>
		Revised placement of cedilla and ogonek in accented glyphs
		Revised glyphs:
			,;DKTjkvwxy¥§Ð˛€
		Completed glyph sets:
			ISO 8859-2  (Central European)
			ISO 8859-3  (South European, Esperanto)
			ISO 8859-4  (North European)
			ISO 8859-5  (Cyrillic)
			ISO 8859-9  (Turkish)
			ISO 8859-10 (Nordic)
			ISO 8859-13 (Baltic Rim)
			ISO 8859-14 (Celtic)
			ISO 8859-16 (South-Eastern European)
		Added glyphs:
			ȷʼ̒ ЀЍѐѝ‒–—‘’‚‛“”„‟‹›

	Xolonium 1.0  2011-01-04  Severin Meyer  <sev.ch@web.de>
		Completed glyph sets:
			ISO 8859-1  (Western European)
			ISO 8859-15 (Western European)
		Added glyphs:
			ĄĆĘŁŃŚŹŻąćęłńśźżıˆˇ˙˚˛˜

# fonts/LICENSE.txt
Copyright 2011-2016 Severin Meyer <sev.ch@web.de>,
with Reserved Font Name Xolonium.

This Font Software is licensed under the SIL Open Font License,
Version 1.1. This license is copied below, and is also available
with a FAQ at <http://scripts.sil.org/OFL>


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

# fonts/Xolonium-Regular.ttf.import
[remap]

importer="font_data_dynamic"
type="FontFile"
uid="uid://bgv586r20ps8e"
path="res://.godot/imported/Xolonium-Regular.ttf-bc2981e3069cff4c34dd7c8e2bb73fba.fontdata"

[deps]

source_file="res://fonts/Xolonium-Regular.ttf"
dest_files=["res://.godot/imported/Xolonium-Regular.ttf-bc2981e3069cff4c34dd7c8e2bb73fba.fontdata"]

[params]

Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=false
hinting=1
subpixel_positioning=4
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

# hud.gd
extends CanvasLayer

signal start_game

func show_message(text):
	$MessageLabel.text = text
	$MessageLabel.show()
	$MessageTimer.start()


func show_game_over():
	show_message("Game Over")
	await $MessageTimer.timeout
	$MessageLabel.text = "Dodge the\nCreeps"
	$MessageLabel.show()
	await get_tree().create_timer(1).timeout
	$StartButton.show()


func update_score(score):
	$ScoreLabel.text = str(score)


func _on_StartButton_pressed():
	$StartButton.hide()
	start_game.emit()


func _on_MessageTimer_timeout():
	$MessageLabel.hide()

# hud.gd.uid
uid://c1g57034r2c0

# hud.tscn
[gd_scene format=3 uid="uid://b0efehuavobda"]

[ext_resource type="Script" uid="uid://c1g57034r2c0" path="res://hud.gd" id="1"]
[ext_resource type="FontFile" uid="uid://bgv586r20ps8e" path="res://fonts/Xolonium-Regular.ttf" id="2_2jm3i"]

[sub_resource type="InputEventAction" id="InputEventAction_fopy7"]
action = &"start_game"

[sub_resource type="Shortcut" id="4"]
events = [SubResource("InputEventAction_fopy7")]

[node name="HUD" type="CanvasLayer" unique_id=126421993]
script = ExtResource("1")

[node name="ScoreLabel" type="Label" parent="." unique_id=1314826100]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 78.0
grow_horizontal = 2
theme_override_fonts/font = ExtResource("2_2jm3i")
theme_override_font_sizes/font_size = 60
text = "0"
horizontal_alignment = 1

[node name="MessageLabel" type="Label" parent="." unique_id=1611528703]
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = -79.5
offset_bottom = 79.5
grow_horizontal = 2
grow_vertical = 2
theme_override_fonts/font = ExtResource("2_2jm3i")
theme_override_font_sizes/font_size = 60
text = "Dodge the
Creeps"
horizontal_alignment = 1

[node name="StartButton" type="Button" parent="." unique_id=1561548516]
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -90.0
offset_top = -200.0
offset_right = 90.0
offset_bottom = -100.0
grow_horizontal = 2
grow_vertical = 0
theme_override_fonts/font = ExtResource("2_2jm3i")
theme_override_font_sizes/font_size = 60
shortcut = SubResource("4")
text = "Start"

[node name="MessageTimer" type="Timer" parent="." unique_id=1675980570]
one_shot = true

[connection signal="pressed" from="StartButton" to="." method="_on_StartButton_pressed"]
[connection signal="timeout" from="MessageTimer" to="." method="_on_MessageTimer_timeout"]

# icon.webp.import
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dfklrdtaun0xt"
path="res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://icon.webp"
dest_files=["res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

# main.gd
extends Node

@export var mob_scene: PackedScene
var score

func game_over():
	$ScoreTimer.stop()
	$MobTimer.stop()
	$HUD.show_game_over()
	$Music.stop()
	$DeathSound.play()


func new_game():
	get_tree().call_group(&"mobs", &"queue_free")
	score = 0
	$Player.start($StartPosition.position)
	$StartTimer.start()
	$HUD.update_score(score)
	$HUD.show_message("Get Ready")
	$Music.play()


func _on_MobTimer_timeout():
	# Create a new instance of the Mob scene.
	var mob = mob_scene.instantiate()

	# Choose a random location on Path2D.
	var mob_spawn_location = get_node(^"MobPath/MobSpawnLocation")
	mob_spawn_location.progress_ratio = randf()

	# Set the mob's position to a random location.
	mob.position = mob_spawn_location.position

	# Set the mob's direction perpendicular to the path direction.
	var direction = mob_spawn_location.rotation + PI / 2

	# Add some randomness to the direction.
	direction += randf_range(-PI / 4, PI / 4)
	mob.rotation = direction

	# Choose the velocity for the mob.
	var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
	mob.linear_velocity = velocity.rotated(direction)

	# Spawn the mob by adding it to the Main scene.
	add_child(mob)


func _on_ScoreTimer_timeout():
	score += 1
	$HUD.update_score(score)


func _on_StartTimer_timeout():
	$MobTimer.start()
	$ScoreTimer.start()

# main.gd.uid
uid://c4wt6ace7hycd

# main.tscn
[gd_scene format=3 uid="uid://bggkaprn62fwm"]

[ext_resource type="Script" uid="uid://c4wt6ace7hycd" path="res://main.gd" id="1_0r6n5"]
[ext_resource type="PackedScene" uid="uid://cao351pllxqpa" path="res://mob.tscn" id="2_50pww"]
[ext_resource type="PackedScene" uid="uid://bwhlkliwp13p4" path="res://player.tscn" id="3_veqnc"]
[ext_resource type="PackedScene" uid="uid://b0efehuavobda" path="res://hud.tscn" id="4_0qnje"]
[ext_resource type="AudioStream" uid="uid://sgfduhhw4pno" path="res://art/House In a Forest Loop.ogg" id="5_55d8h"]
[ext_resource type="AudioStream" uid="uid://td2mgko63p61" path="res://art/gameover.wav" id="6_hp1r0"]

[sub_resource type="Curve2D" id="1"]
_data = {
"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480, 0, 0, 0, 0, 0, 480, 720, 0, 0, 0, 0, 0, 720, 0, 0, 0, 0, 0, 0)
}
point_count = 5

[node name="Main" type="Node" unique_id=1975992027]
script = ExtResource("1_0r6n5")
mob_scene = ExtResource("2_50pww")

[node name="ColorRect" type="ColorRect" parent="." unique_id=569320965]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.219608, 0.372549, 0.380392, 1)

[node name="Player" parent="." unique_id=927660131 instance=ExtResource("3_veqnc")]

[node name="MobTimer" type="Timer" parent="." unique_id=228987391]
wait_time = 0.5

[node name="ScoreTimer" type="Timer" parent="." unique_id=451982858]

[node name="StartTimer" type="Timer" parent="." unique_id=238316384]
wait_time = 2.0
one_shot = true

[node name="StartPosition" type="Marker2D" parent="." unique_id=568887530]
position = Vector2(240, 450)

[node name="MobPath" type="Path2D" parent="." unique_id=694323229]
curve = SubResource("1")

[node name="MobSpawnLocation" type="PathFollow2D" parent="MobPath" unique_id=1080274542]

[node name="HUD" parent="." unique_id=1879130737 instance=ExtResource("4_0qnje")]

[node name="Music" type="AudioStreamPlayer" parent="." unique_id=267371681]
stream = ExtResource("5_55d8h")

[node name="DeathSound" type="AudioStreamPlayer" parent="." unique_id=1715684712]
stream = ExtResource("6_hp1r0")

[connection signal="hit" from="Player" to="." method="game_over"]
[connection signal="timeout" from="MobTimer" to="." method="_on_MobTimer_timeout"]
[connection signal="timeout" from="ScoreTimer" to="." method="_on_ScoreTimer_timeout"]
[connection signal="timeout" from="StartTimer" to="." method="_on_StartTimer_timeout"]
[connection signal="start_game" from="HUD" to="." method="new_game"]

# mob.gd
extends RigidBody2D

func _ready():
	var mob_types = Array($AnimatedSprite2D.sprite_frames.get_animation_names())
	$AnimatedSprite2D.animation = mob_types.pick_random()
	$AnimatedSprite2D.play()


func _on_VisibilityNotifier2D_screen_exited():
	queue_free()

# mob.gd.uid
uid://cypxpb8arjrqt

# mob.tscn
[gd_scene format=3 uid="uid://cao351pllxqpa"]

[ext_resource type="Script" uid="uid://cypxpb8arjrqt" path="res://mob.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://dun74wipekpfq" path="res://art/enemyFlyingAlt_1.png" id="2"]
[ext_resource type="Texture2D" uid="uid://vusf51hepduk" path="res://art/enemyFlyingAlt_2.png" id="3"]
[ext_resource type="Texture2D" uid="uid://dgwhuvn7qb4iy" path="res://art/enemyWalking_1.png" id="4"]
[ext_resource type="Texture2D" uid="uid://dyw702efe6meu" path="res://art/enemyWalking_2.png" id="5"]
[ext_resource type="Texture2D" uid="uid://d182mv7y80xqy" path="res://art/enemySwimming_1.png" id="6"]
[ext_resource type="Texture2D" uid="uid://dmgglhdyowipd" path="res://art/enemySwimming_2.png" id="7"]

[sub_resource type="SpriteFrames" id="1"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2")
}, {
"duration": 1.0,
"texture": ExtResource("3")
}],
"loop": true,
"name": &"fly",
"speed": 3.0
}, {
"frames": [{
"duration": 1.0,
"texture": ExtResource("6")
}, {
"duration": 1.0,
"texture": ExtResource("7")
}],
"loop": true,
"name": &"swim",
"speed": 4.0
}, {
"frames": [{
"duration": 1.0,
"texture": ExtResource("4")
}, {
"duration": 1.0,
"texture": ExtResource("5")
}],
"loop": true,
"name": &"walk",
"speed": 4.0
}]

[sub_resource type="CapsuleShape2D" id="2"]
radius = 37.0
height = 100.0

[node name="Mob" type="RigidBody2D" unique_id=371809901 groups=["mobs"]]
collision_mask = 0
gravity_scale = 0.0
script = ExtResource("1")

[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1998522389]
scale = Vector2(0.75, 0.75)
sprite_frames = SubResource("1")
animation = &"walk"

[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1880423722]
rotation = 1.5708
shape = SubResource("2")

[node name="VisibleOnScreenNotifier2D" type="VisibleOnScreenNotifier2D" parent="." unique_id=959995349]

[connection signal="screen_exited" from="VisibleOnScreenNotifier2D" to="." method="_on_VisibilityNotifier2D_screen_exited"]

# player.gd
extends Area2D

signal hit

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

func _ready():
	screen_size = get_viewport_rect().size
	hide()


func _process(delta):
	var velocity = Vector2.ZERO # The player's movement vector.
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1
	if Input.is_action_pressed(&"move_down"):
		velocity.y += 1
	if Input.is_action_pressed(&"move_up"):
		velocity.y -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

	if velocity.x != 0:
		$AnimatedSprite2D.animation = &"right"
		$AnimatedSprite2D.flip_v = false
		$Trail.rotation = 0
		$AnimatedSprite2D.flip_h = velocity.x < 0
	elif velocity.y != 0:
		$AnimatedSprite2D.animation = &"up"
		rotation = PI if velocity.y > 0 else 0


func start(pos):
	position = pos
	rotation = 0
	show()
	$CollisionShape2D.disabled = false


func _on_body_entered(_body):
	hide() # Player disappears after being hit.
	hit.emit()
	# Must be deferred as we can't change physics properties on a physics callback.
	$CollisionShape2D.set_deferred(&"disabled", true)

# player.gd.uid
uid://6s0lxctks3qn

# player.tscn
[gd_scene format=3 uid="uid://bwhlkliwp13p4"]

[ext_resource type="Script" uid="uid://6s0lxctks3qn" path="res://player.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://b2aofu01vxvea" path="res://art/playerGrey_walk1.png" id="2"]
[ext_resource type="Texture2D" uid="uid://ddjou2q6gxlfr" path="res://art/playerGrey_walk2.png" id="3"]
[ext_resource type="Texture2D" uid="uid://bcow5c46vixno" path="res://art/playerGrey_up1.png" id="4"]
[ext_resource type="Texture2D" uid="uid://dw3lwgwhpbfx8" path="res://art/playerGrey_up2.png" id="5"]

[sub_resource type="SpriteFrames" id="1"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": ExtResource("2")
}, {
"duration": 1.0,
"texture": ExtResource("3")
}],
"loop": true,
"name": &"right",
"speed": 5.0
}, {
"frames": [{
"duration": 1.0,
"texture": ExtResource("4")
}, {
"duration": 1.0,
"texture": ExtResource("5")
}],
"loop": true,
"name": &"up",
"speed": 5.0
}]

[sub_resource type="CapsuleShape2D" id="2"]
radius = 27.0
height = 68.0

[sub_resource type="Gradient" id="3"]
colors = PackedColorArray(1, 1, 1, 0.501961, 1, 1, 1, 0)

[sub_resource type="GradientTexture1D" id="4"]
gradient = SubResource("3")

[sub_resource type="Curve" id="5"]
_data = [Vector2(0.00501098, 0.5), 0.0, 0.0, 0, 0, Vector2(0.994989, 0.324), 0.0, 0.0, 0, 0]
point_count = 2

[sub_resource type="CurveTexture" id="6"]
curve = SubResource("5")

[sub_resource type="ParticleProcessMaterial" id="7"]
gravity = Vector3(0, 0, 0)
scale_curve = SubResource("6")
color_ramp = SubResource("4")

[node name="Player" type="Area2D" unique_id=2141725708]
z_index = 10
script = ExtResource("1")

[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1437394421]
scale = Vector2(0.5, 0.5)
sprite_frames = SubResource("1")
animation = &"right"

[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1954506745]
shape = SubResource("2")

[node name="Trail" type="GPUParticles2D" parent="." unique_id=1747300857]
z_index = -1
amount = 10
texture = ExtResource("2")
speed_scale = 2.0
process_material = SubResource("7")

[connection signal="body_entered" from="." to="." method="_on_body_entered"]

# project.godot
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
;   [section] ; section goes between []
;   param=value ; assign values to parameters

config_version=5

[application]

config/name="Dodge the Creeps"
config/description="This is a simple game where your character must move
and avoid the enemies for as long as possible.

This is a finished version of the game featured in the 'Your first 2D game'
tutorial in the documentation. For more details, consider
following the tutorial in the documentation."
config/tags=PackedStringArray("2d", "demo", "official")
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.6")
config/icon="res://icon.webp"

[display]

window/size/viewport_width=480
window/size/viewport_height=720
window/size/window_width_override=480
window/size/window_height_override=720
window/stretch/mode="canvas_items"

[input]

move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
]
}
move_up={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_down={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
]
}
start_game={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}

[rendering]

renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"

# screenshots/.gdignore

Les actifs binaires ne contiennent pas les octets originaux dans le texte en clair ci‑dessus, mais ils doivent absolument rester dans le repository path order ainsi que dans les listes d’exclusion/référence. Par exemple, si main.tscn pointe vers res://art/gameover.wav, le chunk issu de main.tscn, les métadonnées du chemin référencé art/gameover.wav, les résultats de recherche du Retriever et l’invite de l’utilisateur seront tous inclus dans le même objet de requête. Les octets binaires originaux eux‑mêmes ne sont pas envoyés au Retriever ou au LLM.

Exemple de transmission par fichier

Si l’on extrait le bloc # player.gd du groupe de texte en clair, cela devient une seule analysis_request.

extends Area2D

signal hit

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

func _ready():
	screen_size = get_viewport_rect().size
	hide()


func _process(delta):
	var velocity = Vector2.ZERO # The player's movement vector.
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1
	if Input.is_action_pressed(&"move_down"):
		velocity.y += 1
	if Input.is_action_pressed(&"move_up"):
		velocity.y -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

	if velocity.x != 0:
		$AnimatedSprite2D.animation = &"right"
		$AnimatedSprite2D.flip_v = false
		$Trail.rotation = 0
		$AnimatedSprite2D.flip_h = velocity.x < 0
	elif velocity.y != 0:
		$AnimatedSprite2D.animation = &"up"
		rotation = PI if velocity.y > 0 else 0


func start(pos):
	position = pos
	rotation = 0
	show()
	$CollisionShape2D.disabled = false


func _on_body_entered(_body):
	hide() # Player disappears after being hit.
	hit.emit()
	# Must be deferred as we can't change physics properties on a physics callback.
	$CollisionShape2D.set_deferred(&"disabled", true)

Dans l'AST Parser, on ne transmet pas la requête complète, mais uniquement les champs nécessaires à l'analyse du code source.

ast_parse_input
  project_id: "github:godotengine/godot-demo-projects/2d/dodge_the_creeps"
  source_file: "player.gd"
  source_kind: "gdscript"
  source_text: |
    extends Area2D
    
    signal hit
    
    @export var speed = 400 # How fast the player will move (pixels/sec).
    var screen_size # Size of the game window.
    
    func _ready():
    	screen_size = get_viewport_rect().size
    	hide()
    
    
    func _process(delta):
    	var velocity = Vector2.ZERO # The player's movement vector.
    	if Input.is_action_pressed(&"move_right"):
    		velocity.x += 1
    	if Input.is_action_pressed(&"move_left"):
    		velocity.x -= 1
    	if Input.is_action_pressed(&"move_down"):
    		velocity.y += 1
    	if Input.is_action_pressed(&"move_up"):
    		velocity.y -= 1
    
    	if velocity.length() > 0:
    		velocity = velocity.normalized() * speed
    		$AnimatedSprite2D.play()
    	else:
    		$AnimatedSprite2D.stop()
    
    	position += velocity * delta
    	position = position.clamp(Vector2.ZERO, screen_size)
    
    	if velocity.x != 0:
    		$AnimatedSprite2D.animation = &"right"
    		$AnimatedSprite2D.flip_v = false
    		$Trail.rotation = 0
    		$AnimatedSprite2D.flip_h = velocity.x < 0
    	elif velocity.y != 0:
    		$AnimatedSprite2D.animation = &"up"
    		rotation = PI if velocity.y > 0 else 0
    
    
    func start(pos):
    	position = pos
    	rotation = 0
    	show()
    	$CollisionShape2D.disabled = false
    
    
    func _on_body_entered(_body):
    	hide() # Player disappears after being hit.
    	hit.emit()
    	# Must be deferred as we can't change physics properties on a physics callback.
    	$CollisionShape2D.set_deferred(&"disabled", true)
  source_sha256: "87f4fcf7481dba031f74a363475cee75b81c3d42eb1347b318f6b824e37329a6"

Parser divise le fichier en fragments. Jusqu'à ce que l'implémentation réelle soit confirmée, on s'attend à des fragments de niveau suivant.

ast_parse_output
  source_file: "player.gd"
  chunks:
    - node_kind: "class_extends"
      code_text: "extends Area2D"
      symbol_candidates: ["Area2D"]
    - node_kind: "signal"
      code_text: "signal hit"
      symbol_candidates: ["hit"]
    - node_kind: "export_variable"
      code_text: "@export var speed = 400"
      symbol_candidates: ["@export", "speed"]
    - node_kind: "function"
      code_text: |
        func _process(delta):
        	var velocity = Vector2.ZERO # The player's movement vector.
        	if Input.is_action_pressed(&"move_right"):
        		velocity.x += 1
        	if Input.is_action_pressed(&"move_left"):
        		velocity.x -= 1
        	if Input.is_action_pressed(&"move_down"):
        		velocity.y += 1
        	if Input.is_action_pressed(&"move_up"):
        		velocity.y -= 1

        	if velocity.length() > 0:
        		velocity = velocity.normalized() * speed
        		$AnimatedSprite2D.play()
        	else:
        		$AnimatedSprite2D.stop()

        	position += velocity * delta
        	position = position.clamp(Vector2.ZERO, screen_size)

        	if velocity.x != 0:
        		$AnimatedSprite2D.animation = &"right"
        		$AnimatedSprite2D.flip_v = false
        		$Trail.rotation = 0
        		$AnimatedSprite2D.flip_h = velocity.x < 0
        	elif velocity.y != 0:
        		$AnimatedSprite2D.animation = &"up"
        		rotation = PI if velocity.y > 0 else 0
      api_call_candidates: ["Input.is_action_pressed", "Vector2.ZERO", "position.clamp"]
    - node_kind: "function"
      code_text: |
        func _on_body_entered(_body):
        	hide() # Player disappears after being hit.
        	hit.emit()
        	# Must be deferred as we can't change physics properties on a physics callback.
        	$CollisionShape2D.set_deferred(&"disabled", true)
      api_call_candidates: ["hit.emit", "set_deferred"]

Chaque morceau est à nouveau envoyé à la requête Retriever.

retrieval_request
  project_id: "github:godotengine/godot-demo-projects/2d/dodge_the_creeps"
  source_file: "player.gd"
  chunk_id: "player.gd:function:_process"
  prompt_intent: "version_check_and_explain"
  query_terms:
    - "Input.is_action_pressed"
    - "Vector2.ZERO"
    - "position.clamp"
    - "@export"
  candidate_tables:
    - "docs_chunks"
    - "api_mapping"
    - "label_prototypes"

Ce flux n’est pas un traitement spécial propre à player.gd. Cependant, l’étape de création des fragments varie selon le type de fichier. main.gd, mob.gd, hud.gd répètent analysis_request → ast_parse_input → ast_parse_output.chunks[] → retrieval_request. Les fichiers qui ne sont pas ciblés par l’AST Parser, comme project.godot ou main.tscn, répètent analysis_request → direct_chunk_output.chunks[] → retrieval_request.

Dans le projet d’exemple, le flux d’envoi répété à vérifier est le suivant :

  1. Le répertoire racine du projet est considéré comme un seul project_id.
  2. Les fichiers texte liés à Godot sont collectés en fonction de chemins relatifs.
  3. Chaque fichier est développé en un bloc de texte brut précédé d’un en‑tête # <relative/path>.
  4. Chaque bloc de texte brut est à nouveau transformé en analysis_request au niveau du fichier.
  5. Pour chaque analysis_request, le source_sha256 est calculé.
  6. Les fichiers .gd sont traités par l’AST Parser qui génère ast_parse_output.chunks[].
  7. Les fichiers inclus qui ne sont pas ciblés par l’AST Parser sont fragmentés directement par le fragmentateur qui crée direct_chunk_output.chunks[].
  8. Un requête Retriever est créée pour chaque chunk.
  9. Le résultat du Retriever et le chunk sont combinés pour une requête de jugement LLM, qui est répétée.
  10. Toutes les requêtes de fichiers/chunks nécessaires doivent recevoir une réponse correcte avant que le projet ne soit présenté comme candidat à une évaluation globale.

En d’autres termes, un projet est découpé en plusieurs requêtes comme suit.

dodge_the_creeps/project.godot
  -> analysis_request
  -> direct_chunk_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/main.gd
  -> analysis_request
  -> ast_parse_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/player.gd
  -> analysis_request
  -> ast_parse_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/player.tscn
  -> analysis_request
  -> direct_chunk_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/mob.gd
  -> analysis_request
  -> ast_parse_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/mob.tscn
  -> analysis_request
  -> direct_chunk_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/hud.gd
  -> analysis_request
  -> ast_parse_output.chunks[]
  -> retrieval_request per chunk

dodge_the_creeps/hud.tscn
  -> analysis_request
  -> direct_chunk_output.chunks[]
  -> retrieval_request per chunk

À ce moment, si un fichier ou un morceau échoue, on ne confirme pas immédiatement l’ensemble du projet. Les requêtes échouées restent en attente ou en mode de réessai, et seuls les morceaux ayant reçu une réponse correcte sont accumulés. Le jugement au niveau du projet n’est effectué qu’après que les requêtes répétées nécessaires ont reçu suffisamment de réponses.

Flux de requêtes répétées

Un projet n’est pas évalué avec une seule requête LLM.

L’entrée du projet est divisée en plusieurs requêtes comme suit.

project
  -> file requests
  -> AST chunks or direct chunks
  -> retrieval requests
  -> LLM judgment requests
  -> project-level aggregation

Chaque étape possède une unité de répétition comme suit.

Étape Unité de répétition
Scan de fichiers Tous les fichiers du projet
Analyseur AST Fichiers .gd inclus
Fragmentation directe Fichiers inclus qui ne sont pas ciblés par l’analyseur AST
Enregistrement des exclusions Fichiers à exclure comme .md, binaires, etc.
Retriever Chunk AST ou chunk direct
Jugement Qwen 3.6 Chunk AST/direct + preuve de recherche
Classification du projet Résultat global du jugement des chunks

Ainsi, le projet ne se conclut pas simplement par un vrai/faux ; plusieurs fichiers et fragments du projet doivent chacun être traités correctement. Le jugement final du projet ne peut être effectué qu’après que tous les résultats du Retriever/LLM/Validator des fragments nécessaires soient rassemblés.

Qwen 3.6 fonctionne à la demande, il est donc préférable d’appeler plusieurs fois au niveau des chunks AST ou des chunks directs plutôt que d’essayer de tout faire en une seule grosse requête. Dans ce cas, les chunks déjà complétés sont réutilisés, et seules les chunks échouées ou interrompues sont redemandées.

Données transmises à l’analyseur AST

Aucun prompt n’est directement injecté dans l’analyseur AST.

L’entrée du parser est minimisée comme suit.

ast_parse_input
  project_id
  source_file
  source_kind
  source_text
  source_sha256

Ainsi, les raisons de cette séparation sont les suivantes :

  • Le même code source peut être réanalysé avec plusieurs invites.
  • Même si l’invite change, le résultat de l’analyse AST peut être réutilisé.
  • Le parseur empêche que la « intention de la question » modifie l’interprétation de la structure du code.
  • Ce n’est qu’au stade Retriever/LLM que l’on utilise l’invite pour orienter le jugement.

Sortie du parseur AST

Le parseur crée, au lieu de transmettre l’ensemble du code source directement au LLM, une liste de fragments traçables.

ast_parse_output
  project_id
  source_file
  source_sha256
  parse_status
  chunks[]

Chaque chunk possède les informations suivantes.

chunk
  chunk_id
  source_file
  span
  node_kind
  symbol_candidates
  api_call_candidates
  code_text
  surrounding_context
Élément Description
chunk_id ID déterministe basé sur le projet/fichier/périmètre
span plage de ligne/colonne ou d'offset en octets
node_kind function, class, call, signal, property, scene_node, etc.
symbol_candidates candidats de noms de classe, de type, de méthode
api_call_candidates candidats d'appels à l'API Godot
code_text code source de ce fragment
surrounding_context code environnant nécessaire au jugement. Au lieu du fichier complet, on ne garde que la plage nécessaire.

Sortie de découpage direct

Les fichiers inclus qui ne sont pas ciblés par l'AST Parser créent direct_chunk_output. Cette sortie n'est pas un AST, mais le résultat de la division de la structure texte/config/scene dans l'ordre original.

direct_chunk_output
  project_id
  source_file
  source_sha256
  chunk_status
  chunks[]

Chaque chunk possède les informations suivantes.

direct_chunk
  chunk_id
  source_file
  chunk_order
  span
  chunk_kind
  chunk_text
  reference_candidates

Exemple :

direct_chunk
  source_file: "main.tscn"
  chunk_order: 3
  chunk_kind: "ext_resource"
  chunk_text: "[ext_resource type=\"Script\" uid=\"uid://c4wt6ace7hycd\" path=\"res://main.gd\" id=\"1_0r6n5\"]"
  reference_candidates:
    - "res://main.gd"

Les fichiers que l’on a décidé d’exclure, comme les .md, ne sont pas placés dans direct_chunk_output mais restent dans excluded_files.

Combinaison du prompt et des fragments

Le prompt est recombiné après le Parser.

analysis_request.prompt
+ ast chunk ou direct chunk
+ project/file metadata
-> retrieval_request
Intention du prompt Priorité Retriever
“Que signifie ce code ?” docs_chunks
“Faut‑il convertir en Godot 4 ?” api_mapping, label_prototypes
“Ce code est‑il pour Godot 3 ou 4 ?” docs_chunks, api_mapping, label_prototypes tous candidats

À cette étape, aucune table spécifique n’est traitée de manière particulière. Selon la nature du prompt et du fragment AST, on recherche les références JSONL de la documentation officielle nécessaires.

Unité transmise au LLM

Avec Qwen 3.6, on ne transmet pas l’ensemble complet du projet original en une seule fois.

L’unité d’appel du LLM est le groupe suivant.

llm_judgment_request
  prompt
  project_id
  source_file
  chunk_id
  chunk_order
  chunk_kind
  chunk_text
  surrounding_context
  retrieved_evidence
  judgment_contract

Ici, judgment_contract n’est pas encore le schéma JSONL final. À ce stade, il s’agit d’un contrat de requête qui indique à l’LLM ce qu’il doit juger.

retrieved_evidence n’est pas encore le schéma de stockage final, mais un champ abstrait qui désigne un ensemble de résultats de recherche. Lors de l’étape de stockage ultérieure, il pourra être séparé selon le type de preuve, comme les ID de preuve docs_chunks, les ID de preuve api_mapping, les ID de preuve label_prototypes. Dans ce document, il est utilisé uniquement avec le sens de « ensemble de preuves JSONL provenant de la documentation officielle recherchée ».

Exemple :

À juger :
- Si ce fragment de code utilise l’API Godot 3
- S’il est valide selon les critères de Godot 4
- S’il existe une base de migration
- Si les références de la documentation officielle trouvées sont réellement liées au code

Limite de réutilisation

Même si le code source est identique, le jugement du Retriever/LLM peut différer selon le prompt.

Inversement, si le code source est le même, le résultat de l'analyseur AST peut être réutilisé.

Étape Critère de réutilisation
Analyse AST source_sha256, source_kind, version du parseur
Génération de chunk source_sha256, version du découpage
Recherche Retriever intention du prompt, symboles du chunk, version de la récupération
Jugement LLM prompt, chunk, preuve, modèle, version du prompt

En respectant cette limite, l’« analyse de la structure du code » et le « jugement selon l’intention de la question » ne se mélangent pas.

Point de vue interruption/reprise

Comme la demande d’analyse peut être longue, chaque étape doit pouvoir être reprise.

Unité de stockage minimale :

  • État d’achèvement du scan de fichier
  • État d’analyse AST par fichier
  • État de génération de chunk
  • État de recherche Retriever par chunk
  • État de jugement LLM par chunk
  • État de validation réussie

Si RunPod ou l’application locale s’arrête, seul le chunk en cours de traitement est remis en attente, tandis que les fichiers et chunks terminés sont réutilisés.

Points encore non confirmés

Ce document ne confirme pas les éléments suivants.

L’ordre de dépendance est toutefois consigné.

Ordre Élément Raison Position de suivi actuelle
1 Implémentation du parseur AST Selon le parseur utilisé, la structure du chunk et la méthode de récupération après échec varient. AST Parser에 넘기는 데이터, AST Parser 출력 de ce document
2 Schéma de réponse JSONL final Il faut définir quel JSONL valider les résultats du jugement LLM pour pouvoir y ajouter un validateur. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md
3 Colonnes de la base de données des scores Après avoir organisé les réponses JSONL et les ID de preuve du Retriever, on pourra définir les colonnes de stockage. docs/retrospectives/2026-06-25-source-analysis-scoring.md
4 Étiquette de classification du système de fichiers Après avoir observé quels résultats de jugement s’accumulent dans la base de scores, on définira les étiquettes du projet. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md
5 Règles de génération SFT/DPO Une fois le système de fichiers classifié accumulé, cela sera conçu séparément. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md

Actuellement, un seul point est confirmé.

Le prompt et le code source sont regroupés en une seule demande d'analyse, mais l'AST Parser ne reçoit que le code source et les métadonnées du fichier, tandis que le prompt est utilisé lors de l'étape de jugement du Retriever/LLM après le Parser.