idea_world_labDEV JOURNAL
viernes, 26 de junio de 2026

Flujo de pasar la entrada de código fuente al análisis AST

Fecha de creación: 26 de junio de 2026

Propósito

Este documento refuerza la sección inicial de docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md.

El roadmap de referencia ya describió el siguiente flujo.

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

Aquí se describe cómo estructurar la información que el usuario proporciona (prompt y código fuente) para pasarla al AST Parser.

Principio fundamental

El prompt y el código fuente forman parte del mismo conjunto de entrada, pero cumplen funciones diferentes.

Entrada Función
Prompt del usuario Intención de análisis, tipo de pregunta, dirección esperada del resultado
Código fuente Objeto que el AST Parser debe analizar realmente
Ruta de archivo / información del proyecto Metadatos para identificar la unidad de análisis y clasificar por proyecto

El AST Parser analiza el código fuente. El prompt no es objeto de análisis sintáctico por el AST.

Sin embargo, el prompt se utiliza en etapas posteriores para decidir qué fragmento priorizar, qué Retriever emplear y qué solicitud de verificación generar para Qwen 3.6.

Paquete de entrada

La entrada del usuario se agrupa primero en una única solicitud de análisis.

analysis_request
  prompt
  source
  source_kind
  project_id
  source_file
  source_sha256
  source_origin

Cada elemento tiene el siguiente significado.

Elemento Descripción
prompt Pregunta ingresada por el usuario. Ej.: “¿Qué significa este código?”, “¿Necesito convertir a Godot 4?”
source Texto del código fuente original. Conservarlo lo más fielmente posible.
source_kind Tipo de entrada, como gdscript, scene, resource, project_config, markdown, unknown, etc.
project_id ID para distinguir el repositorio de GitHub o el proyecto local
source_file Ruta del archivo dentro del proyecto. Si es un pegado de código único, se usa una ruta temporal.
source_sha256 Hash del texto fuente original. Se calcula al crear la solicitud, tanto para pegados como para archivos.
source_origin Origen, como github, local_directory, uploaded_file, pasted_snippet, etc.

En esta etapa no se define el esquema JSONL de salida ni las columnas de la base de datos de puntuaciones. El objetivo es que el Analizador AST y las etapas posteriores de Recuperador/LLM puedan rastrear la misma entrada bajo los mismos criterios.

Pegado de código fuente único

Cuando el usuario introduce directamente el prompt y el código fuente en la interfaz web o en la CLI.

Ejemplo:

¿Necesito convertir esto a Godot 4?
extends KinematicBody2D

func _physics_process(delta):
    move_and_slide()

Flujo de procesamiento:

  1. Agrupe la entrada como analysis_request.
  2. Establezca source_origin como pasted_snippet.
  3. Como no hay una ruta de archivo real, use un identificador temporal como pasted://snippet-<id>.gd para source_file.
  4. Calcule source_sha256 inmediatamente a partir del código original pegado.
  5. Estime source_kind basándose en la pista de extensión, el contenido del código y el lenguaje especificado por el usuario.
  6. Pase source, source_kind, source_file y source_sha256 al Analizador AST.
  7. Guarde el prompt en analysis_request.prompt sin mezclarlo con el Analizador.

Entrada del directorio del proyecto

Se trata de analizar un repositorio de GitHub o una carpeta local.

Flujo de procesamiento:

  1. Agrupe la raíz del proyecto como project_id.
  2. Recorra el sistema de archivos usando rutas relativas.
  3. Registre primero la inclusión/exclusión de cada archivo.
  4. Deje los archivos excluidos en excluded_files para que puedan verse en la UI web o en los registros.
  5. Expanda los archivos incluidos como bloques de texto plano precedidos por un encabezado # <relative/path>.
  6. Envíe los archivos .gd al Analizador AST para crear fragmentos de funciones/código en el orden original.
  7. Aplique reglas de fragmentación directas apropiadas al tipo de archivo para los archivos que no son objetivo del Analizador AST.
  8. Cada fragmento se envía como una solicitud al Recuperador con la ruta original, el orden original y su hash.

Los archivos objetivo iniciales son los mismos que el mapa de referencia.

Archivo Dirección de AST/fragmentación
.gd AST de GDScript, función, clase, señal, variable, candidato a llamada de API
.tscn tipo de nodo de escena, referencia a script, referencia a recurso, propiedad exportada
.tres tipo de recurso, clase de script, pista de material/shader/recurso
project.godot pista de versión de Godot, autoload, renderizador, característica, mapa de entradas
README/documentación excluido por defecto en el análisis AST del código fuente. Registre la exclusión y la razón en la UI/registros.

Expansión del texto plano del código fuente del proyecto

La entrada a nivel de proyecto se despliega primero en bloques de texto plano por archivo. Este texto plano no es el prompt final que se enviará al LLM de una sola vez, sino una representación intermedia que preserva los límites de archivo y las rutas relativas.

# 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"]

Reglas:

  • El encabezado del archivo se escribe en forma # <ruta/relativa>.
  • La ruta relativa es con respecto a la raíz del proyecto.
  • El cuerpo del archivo debe mantenerse lo más fiel posible al original.
  • Este bloque de texto plano se utiliza para verificar los límites de los archivos y para depuración.
  • En el procesamiento real, este texto completo no se envía de una sola vez a un LLM o al Analizador AST.
  • Cada archivo separado por encabezados recibe primero una evaluación de inclusión/exclusión.
  • Los archivos .gd incluidos se envían a través de analysis_request antes de pasar al Analizador AST.
  • Los archivos de texto/configuración que no son .gd incluidos se envían directamente al fragmentador.

De esta manera, se conserva el “código fuente completo del proyecto” proporcionado por el usuario en un formato legible por humanos, y en etapas posteriores se pueden rastrear los archivos/fragments excluidos, los fragmentos AST, los fragmentos directos y la carga del Retriever por archivo o fragmento.

Plan de verificación basado en el proyecto clonado

El flujo desde el Analizador AST hacia el Retriever es difícil de confirmar solo con ejemplos abstractos. Por lo tanto, se clonará un proyecto real de Godot y se verificará basándose en el árbol de archivos del proyecto, la expansión del texto plano, la separación de fragmentos AST/directos y el flujo repetido de solicitudes al Retriever.

El proyecto de referencia se define de la siguiente manera.

Ítem Valor
repositorio godotengine/godot-demo-projects
ruta del proyecto 2d/dodge_the_creeps
razón de la selección Es una demo oficial de Godot y contiene .gd, .tscn, project.godot, README y referencias a assets, lo que facilita la verificación del flujo de entrada del proyecto en un tamaño reducido.
criterio de guardado El código fuente clonado no se compromete al repositorio. En la documentación solo se dejan el árbol y el plan de procesamiento.

Criterio de clonación:

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

Este clon es solo para verificar el flujo. No se copian los fuentes o activos del proyecto clonado dentro del repositorio.

El objetivo de la verificación son las rutas completas de los archivos del proyecto clonado. Primero se despliegan todas las rutas en orden ordenado y cada archivo se divide en fragmentos que el LLM pueda leer. No se crea una base de datos intermedia separada. Lo esencial no es “leer todo el repositorio de una vez”, sino “crear fragmentos según el orden de las rutas y llamar varias veces a cada fragmento”.

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

Criterio de fragmentación por archivo:

Tipo de archivo Método de fragmentación Forma de llamada posterior
.gd El AST Parser crea fragmentos de funciones, señales, variables, extensiones de clase y llamadas a API en el orden original. Para cada fragmento del AST se llama al Retriever y se envía a LLM prompt + fragmento del AST + resultados de búsqueda.
project.godot No se incluye en el AST Parser. Si está dentro del alcance, se crean fragmentos de sección/clave‑valor, escena principal, característica y mapa de entrada. El fragmento se envía directamente al Retriever o se adjunta como contexto circundante al juicio del AST de .gd.
.tscn, .tres No se incluye en el AST Parser. Si está dentro del alcance, se crean fragmentos de bloque de nodo, ext_resource, sub_resource y conexión. El fragmento se envía directamente al Retriever o se adjunta como contexto circundante al juicio del AST del archivo .gd conectado.
.md, .txt, LICENSE Se excluyen por defecto del análisis AST del código fuente. Se registra la exclusión y la razón en la UI/log. No se envía al Retriever a menos que sea un modo de análisis de documento separado.
.import, .uid, .gitignore, .gdignore No se incluye en el AST Parser. Si está dentro del alcance, se crean fragmentos de línea o clave‑valor. El fragmento se envía directamente al Retriever o se usa solo como contexto circundante para verificar la relación de ruta/recursos del archivo.
Imágenes, audio, fuentes y otros binarios No se incluyen en el AST Parser y tampoco se envían los bytes originales al LLM. Solo se guardan la ruta y la razón de exclusión. Cuando la ruta aparece en fragmentos de texto como .tscn o .import, se usa únicamente como información de relación.

En resumen, los únicos archivos que ingresan al AST Parser son los archivos .gd. El repositorio se despliega completo en orden de rutas, pero el resultado no es un “gran cuerpo enviado a LLM de una sola vez”. El encabezado # <ruta/relativa> sirve para indicar de dónde proviene cada archivo y cómo se divide posteriormente en fragmentos de funciones/código/configuración que ingresan al Retriever. Los archivos excluidos, como .md, se anotan en la lista de exclusión, mientras que los archivos .gd incluidos son fragmentados por el AST Parser en unidades de función o código en orden. Luego, cada llamada a LLM se realiza siempre como prompt + fragmento actual + resultados de búsqueda del Retriever, repitiéndose varias veces.

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)

La unidad de entrada final del LLM siempre tiene la siguiente forma.

llm_judgment_request
  user_prompt
  project_id
  source_file
  chunk_id
  chunk_order
  chunk_kind
  chunk_text
  retrieved_evidence

Entrada de búsqueda del Retriever

El Retriever solo tiene la función de buscar los fragmentos de código que proporciono en la base de datos. La ruta del archivo, el id del fragmento y el orden del fragmento no son criterios de búsqueda, sino información de seguimiento. Por eso, en la implementación se separan dos objetos.

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 se usa solo para la UI, los registros, el reinicio, la depuración y la conexión de respuestas de LLM. En la función de búsqueda del recuperador, solo se pasa 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

Es decir, la búsqueda se realiza con los siguientes valores.

Usado en la búsqueda No usado en la búsqueda
chunk_text source_file
symbol_candidates chunk_id
api_call_candidates chunk_order
reference_candidates source_sha256
prompt_terms project_id

Los archivos de ruta son necesarios no para la búsqueda sino para el rastreo. Por ejemplo, debe existir un rastro como player.gd:function:_process para que la interfaz web pueda mostrar “Esta respuesta proviene del cuarto fragmento de player.gd”. Sin embargo, cuando el Retriever busca evidencia en la base de datos, debe buscar dentro del código de la función _process y los candidatos de API, no por la ruta player.gd.

Por ejemplo, el fragmento de la función _process de player.gd separa primero la información de seguimiento y la información de búsqueda.

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"

Criterios de código de búsqueda del Retriever

El código a continuación sirve como criterio para fijar la dirección de implementación. Sólo se pasa RetrieverQuery al Retriever. ChunkTrace lo mantiene el orquestador fuera del Retriever y lo vuelve a combinar con los resultados de la búsqueda.

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,
    }

En el código anterior, los puntos importantes son los siguientes:

  • La función de búsqueda del Retriever no recibe source_file, chunk_id ni chunk_order como condiciones de búsqueda.
  • El Retriever realiza una búsqueda vectorial con el query_embedding creado a partir del fragmento de código, y query.query_text se utiliza para la búsqueda auxiliar de palabras clave/trigramas.
  • query_embedding se genera combinando raw_chunk_text, candidatos de API, candidatos de símbolos y palabras clave del prompt.
  • query.query_text se construye a partir de API, símbolos, identificadores y palabras clave del prompt extraídos del fragmento de código interno del archivo.
  • La ruta del archivo y el orden del chunk solo se guardan en ChunkTrace.
  • retrieve_with_trace es la función orquestadora que, después de la búsqueda, combina los resultados con el rastro; la búsqueda propiamente dicha la realiza retrieve_for_query.
  • docs_chunks, api_mapping y label_prototypes se buscan con el mismo RetrieverQuery.

godot_rag.dynamic_retrieval_view no es una nueva lógica de decisión, sino una vista aplanada para la búsqueda. Aunque la estructura de la carga útil de las tres tablas sea diferente, el Retriever ve las mismas columnas.

dynamic_retrieval_view
  table_name
  id
  title
  content
  embedding
  search_text
  search_tsv
  metadata

El objetivo de la búsqueda se despliega de la siguiente manera. Aquí también se conservan los campos de origen como source_file, source_url en los metadatos, pero no se incluyen en la cadena de búsqueda básica. La búsqueda debe realizarse mediante fragmentos de código API/símbolo/patrón.

Tabla title content Valor que entra en search_text
docs_chunks Título del documento, ruta de la sección, símbolo Fragmento del cuerpo del documento doc_type, symbol, section_path, content, api_symbols
api_mapping API modificada o nombre anterior de la API Descripción de la migración, razón del cambio, ejemplo old_symbol, new_symbol, change_type, description, before_code, after_code
label_prototypes Nombre del prototipo o etiqueta Forma de uso de la función, composición de argumentos, ejemplo de cambio de patrón de llamada label, input_pattern, prompt_pattern, before_code, after_code, expected_response, explanation

Ejemplo de vista 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;

En esta vista, el valor que realmente busca el Retriever es query_text. Por ejemplo, si se trata del fragmento _process, se busca de la siguiente manera.

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

Los resultados de búsqueda no son respuestas definitivas que se insertan directamente en el LLM, sino evidencias candidatas. En la solicitud de juicio del LLM se deben incluir tanto el fragmento como las candidatas de búsqueda, y Qwen 3.6 verifica nuevamente “si esta evidencia está relacionada con el fragmento actual”.

Criterios de depuración de transmisión

Para evitar que la IA seleccione arbitrariamente “solo las funciones clave” de un archivo .gd y lo envíe, o que envíe todo el archivo como una única solicitud al LLM, es necesario poder comprobar antes y después la diferencia (diff). El objetivo de la verificación no es “si todo el archivo original se envió al LLM”, sino “si los fragmentos de funciones/código creados a partir del archivo original se rastrean completamente en el orden original y cada fragmento se pasa a la solicitud del Retriever”.

En la fase de expansión del archivo, primero se crean bloques de texto originales por ruta.

# 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)

Luego deja la lista de fragmentos creada por el 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)

También se deja el payload justo antes del envío del Retriever en unidades de chunk.

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

Reglas de validación:

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 esta validación falla, no se envía. Esta regla es especialmente importante en los archivos .gd. La intención no es introducir todo el archivo .gd en el LLM, sino verificar que los fragmentos de funciones/código extraídos de .gd lleguen al Retriever sin perder la ruta original ni el orden original.

Cuando se recibe este proyecto como entrada, primero se despliega en bloques de texto plano que pueden ser verificados por una persona. El bloque a continuación muestra el contenido completo del archivo de texto, excluyendo los recursos binarios, organizado por unidades # <ruta/relativa>. Esto incluye archivos que normalmente se excluirían del análisis de código fuente, como .md o LICENSE. En el ejemplo de documentación se conserva todo el contenido dentro de la ruta relativa sin omitir partes, para confirmar los criterios de seguimiento. Sin embargo, si se aplica una política de exclusión en la implementación, los archivos excluidos no se envían al Retriever y se anotan en excluded_files con la razón correspondiente.


# .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/es/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

Los activos binarios no se incluyen en el cuerpo del texto plano, pero deben permanecer en el repository path order y en la lista de exclusión/referencia. Por ejemplo, si main.tscn apunta a res://art/gameover.wav, la entrada de juicio del LLM incluye el fragmento extraído de main.tscn, los metadatos de ruta que hacen referencia a art/gameover.wav, los resultados de búsqueda del Retriever y el prompt del usuario, todo dentro del mismo objeto de solicitud. Los bytes binarios originales no se envían al Retriever ni al LLM.

Ejemplo de transmisión por archivo

Si se extrae el bloque # player.gd del conjunto de texto plano, se convierte en una única analysis_request como se muestra a continuación.

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)

Al parser AST solo se le pasan los campos necesarios para el análisis de código, no toda la solicitud.

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"

El parser divide el archivo en fragmentos. Hasta que se confirme la implementación real, se espera el siguiente nivel de fragmentos.

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"]

Cada fragmento se envía nuevamente como una solicitud al Recuperador.

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"

Este flujo no es un tratamiento especial solo de player.gd. Simplemente, la etapa de generación de fragmentos varía según el tipo de archivo. main.gd, mob.gd, hud.gd repiten analysis_request -> ast_parse_input -> ast_parse_output.chunks[] -> retrieval_request. Los archivos que no son objetivo del AST Parser, como project.godot o main.tscn, repiten analysis_request -> direct_chunk_output.chunks[] -> retrieval_request.

El flujo de envío repetido que se verifica en este proyecto de ejemplo es el siguiente:

  1. Se toma la raíz del proyecto como un único project_id.
  2. Se recopilan los archivos de texto relacionados con Godot usando rutas relativas.
  3. Cada archivo se despliega como un bloque de texto plano con el encabezado # <relative/path>.
  4. Cada bloque de texto plano se convierte nuevamente en un analysis_request por archivo.
  5. En cada analysis_request se calcula el source_sha256.
  6. Los archivos .gd hacen que el AST Parser genere ast_parse_output.chunks[].
  7. Los archivos incluidos que no son objetivo del AST Parser son fragmentados directamente por el fragmentador, creando direct_chunk_output.chunks[].
  8. Se crea una solicitud al Retriever basada en cada chunk.
  9. Se combina el resultado del Retriever con el chunk y se repite la solicitud de juicio al LLM.
  10. Todas las solicitudes de archivo/chunk necesarias deben recibir respuestas correctas para que el proyecto se eleve como candidato a juicio a nivel de proyecto.

En otras palabras, un proyecto se divide en múltiples solicitudes como se muestra a continuación.

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

En este caso, si un archivo o un fragmento falla, no se confirma todo el proyecto de inmediato. Las solicitudes fallidas se dejan como pendientes o para reintento, y solo se acumulan los fragmentos que respondieron correctamente. La evaluación a nivel de proyecto se realiza solo después de que las solicitudes repetidas necesarias hayan respondido suficientemente.

Flujo de solicitudes repetidas

Un proyecto no se juzga con una sola solicitud a LLM.

La entrada del proyecto se divide en varias solicitudes como se muestra a continuación.

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

Cada unidad de iteración en cada etapa es la siguiente.

Etapa Unidad de iteración
Escaneo de archivos Archivos dentro del proyecto
Analizador AST Archivos .gd incluidos
Fragmentación directa Archivos incluidos que no son objetivo del Analizador AST
Registro de exclusiones Archivos excluidos como .md, binarios, etc.
Recuperador Fragmento AST o fragmento directo
Evaluación Qwen 3.6 Fragmento AST/directo + evidencia de búsqueda
Clasificación del proyecto Resultado de la evaluación de todos los fragmentos

Por lo tanto, no se trata de que un proyecto termine como verdadero/falso, sino que varios archivos y fragmentos dentro del proyecto deben procesarse correctamente uno por uno. La evaluación final del proyecto solo puede realizarse después de que se hayan reunido los resultados del Recuperador/LLM/Validador de todos los fragmentos necesarios.

Qwen 3.6 se ejecuta bajo demanda, por lo que es más apropiado invocarlo varias veces en unidades de fragmento AST o fragmento directo en lugar de intentar completarlo con una sola solicitud grande. En ese caso, los fragmentos ya completados se reutilizan y solo se vuelven a solicitar los fragmentos que fallaron o se interrumpieron.

Datos que se pasan al Analizador AST

No se inserta el prompt directamente en el Analizador AST.

La entrada del analizador se minimiza de la siguiente manera.

ast_parse_input
  project_id
  source_file
  source_kind
  source_text
  source_sha256

Así se explica la razón de la separación:

  • El mismo código fuente puede volver a analizarse con varios prompts.
  • Incluso si el prompt cambia, el resultado del análisis AST puede reutilizarse.
  • El analizador evita que la “intención de la pregunta” haga que la estructura del código se interprete de manera diferente.
  • Sólo en las etapas de Retriever/LLM se puede usar el prompt para cambiar la dirección del juicio.

Salida del Analizador AST

En lugar de pasar todo el código fuente directamente al LLM, el analizador crea una lista de fragmentos rastreables.

ast_parse_output
  project_id
  source_file
  source_sha256
  parse_status
  chunks[]

Cada chunk tiene la siguiente información.

chunk
  chunk_id
  source_file
  span
  node_kind
  symbol_candidates
  api_call_candidates
  code_text
  surrounding_context
Elemento Descripción
chunk_id ID determinístico basado en proyecto/archivo/alcance
span rango de línea/columna o desplazamiento de bytes
node_kind function, class, call, signal, property, scene_node, etc.
symbol_candidates candidatos a nombre de clase, tipo, método
api_call_candidates candidatos a llamadas de la API de Godot
code_text código fuente original del fragmento
surrounding_context código circundante necesario para el juicio. No todo el archivo, solo el rango necesario.

Salida de fragmentación directa

Los archivos incluidos que no son objetivo del AST Parser generan direct_chunk_output. Esta salida no es un AST, sino el resultado de dividir la estructura de texto/configuración/escena en el orden original.

direct_chunk_output
  project_id
  source_file
  source_sha256
  chunk_status
  chunks[]

Cada chunk tiene la siguiente información.

direct_chunk
  chunk_id
  source_file
  chunk_order
  span
  chunk_kind
  chunk_text
  reference_candidates

Ejemplo:

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"

Los archivos que se excluyen como .md no se crean en direct_chunk_output sino que se dejan en excluded_files.

Prompt y fragmentos combinados

El prompt se vuelve a combinar después del Parser.

analysis_request.prompt
+ ast chunk o direct chunk
+ project/file metadata
-> retrieval_request

Ejemplo:

Intención del prompt Prioridad Retriever
“¿Qué significa este código?” docs_chunks
“¿Necesita conversión a Godot 4?” api_mapping, label_prototypes
“¿Este código es de Godot 3 o 4?” docs_chunks, api_mapping, label_prototypes todos son candidatos

En esta etapa tampoco se trata de forma especial ninguna tabla específica. Según la naturaleza del prompt y del fragmento AST, se busca la evidencia JSONL de la documentación oficial necesaria.

Unidad que se pasa al LLM

En Qwen 3.6 no se envía todo el proyecto original de una sola vez.

Las unidades de llamada al LLM son los siguientes grupos:

llm_judgment_request
  prompt
  project_id
  source_file
  chunk_id
  chunk_order
  chunk_kind
  chunk_text
  surrounding_context
  retrieved_evidence
  judgment_contract

Aquí judgment_contract aún no es el esquema JSONL final. En esta etapa es un contrato de solicitud que indica al LLM qué debe juzgar.

retrieved_evidence todavía no es el esquema de almacenamiento final, sino un campo abstracto que se refiere a un conjunto de resultados de búsqueda. En la fase de almacenamiento posterior, puede dividirse según el tipo de evidencia, como IDs de evidencia docs_chunks, IDs de evidencia api_mapping, IDs de evidencia label_prototypes. En este documento se usa únicamente con el significado de “conjunto de evidencias JSONL de documentación oficial buscada”.

Ejemplo:

A considerar:
- si este fragmento de código usa la API de Godot 3
- si es válido según Godot 4
- si hay fundamentos para la migración
- si la evidencia encontrada en la documentación oficial está realmente relacionada con el código

Límite de reutilización

Incluso con el mismo código fuente, si el prompt es diferente, el juicio del Retriever/LLM puede variar.

Por el contrario, si el código fuente es el mismo, el resultado del AST Parser puede reutilizarse.

Etapa Criterio de reutilización
Análisis AST source_sha256, source_kind, versión del parser
Generación de fragmentos source_sha256, versión de fragmentación
Búsqueda del Retriever intención del prompt, símbolos del fragmento, versión de recuperación
Juicio del LLM prompt, fragmento, evidencia, modelo, versión del prompt

Si se respeta este límite, el “análisis de la estructura del código” y el “juicio según la intención de la pregunta” no se mezclarán.

Perspectiva de interrupción/reanudación

Dado que la solicitud de análisis puede alargarse, cada etapa debe poder reanudarse.

Unidad mínima de almacenamiento:

  • Estado de finalización del escaneo de archivos
  • Estado del análisis AST por archivo
  • Estado de generación de fragmentos
  • Estado de búsqueda del Retriever por fragmento
  • Estado de juicio del LLM por fragmento
  • Estado de paso del Validador

Si RunPod o la aplicación local se detienen a mitad de camino, solo los fragmentos que estaban en proceso se devuelven a estado pendiente, y los archivos y fragmentos completados se reutilizan.

Cosas aún no confirmadas

Este documento no confirma lo siguiente.

Sin embargo, se registra el orden de dependencia.

Orden Ítem Razón Posición de seguimiento actual
1 Implementación del AST Parser Dependiendo del parser utilizado, cambian la estructura del fragmento y el método de recuperación de fallos. AST Parser에 넘기는 데이터, AST Parser 출력 de este documento
2 Esquema de respuesta JSONL final Se debe definir qué JSONL se usará para validar los resultados del juicio del LLM, para poder añadir el Validador. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md
3 Columnas de la base de datos de puntuaciones Después de organizar la respuesta JSONL y los IDs de evidencia del Retriever, se pueden definir las columnas de almacenamiento. docs/retrospectives/2026-06-25-source-analysis-scoring.md
4 Etiqueta de clasificación del sistema de archivos Después de observar qué resultados de juicio se acumulan en la base de datos de puntuaciones, se define la etiqueta del proyecto. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md
5 Reglas de generación SFT/DPO Se diseñarán por separado una vez que el sistema de archivos clasificado se haya acumulado. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md

Actualmente solo hay una cosa confirmada.

El prompt y el código fuente se agrupan como una sola solicitud de análisis, pero solo se pasa el código fuente y los metadatos del archivo al AST Parser, y el prompt se utiliza en la etapa de juicio del Retriever/LLM después del Parser.