idea_world_labDEV JOURNAL
sexta-feira, 26 de junho de 2026

Fluxo de envio de código-fonte para análise AST

Data de criação: 26 de junho de 2026

Objetivo

Este documento complementa a parte inicial de docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md.

O roadmap de referência já descreveu o seguinte fluxo.

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

Aqui, organizamos como o usuário deve passar o prompt e o código‑fonte para o AST Parser.

Princípio Fundamental

Prompt e código‑fonte chegam no mesmo conjunto de entrada, mas têm papéis diferentes.

Entrada Papel
Prompt do usuário Intenção de análise, tipo de pergunta, direção esperada da saída
Código‑fonte Alvo que o AST Parser realmente analisará
Caminho do arquivo / informações do projeto Metadados para identificar a unidade de análise e classificar por projeto

O AST Parser analisa o código‑fonte. O prompt não é alvo da análise sintática do AST.

Entretanto, o prompt é usado nas etapas subsequentes para decidir quais trechos priorizar, qual Retriever utilizar e que tipo de solicitação de validação criar para o Qwen 3.6.

Pacote de Entrada

A entrada do usuário é primeiro agrupada como uma única solicitação de análise.

analysis_request
  prompt
  source
  source_kind
  project_id
  source_file
  source_sha256
  source_origin

Cada item tem o seguinte significado.

Item Descrição
prompt Pergunta inserida pelo usuário. Ex.: “O que significa este código?”, “Preciso converter para Godot 4?”
source Texto do código-fonte original. Deve ser preservado o máximo possível.
source_kind Tipo de entrada, como gdscript, scene, resource, project_config, markdown, unknown, etc.
project_id ID para distinguir o repositório GitHub ou projeto local
source_file Caminho do arquivo dentro do projeto. Se for apenas um trecho de código colado, use um caminho temporário.
source_sha256 Hash SHA‑256 do texto-fonte original. Calculado no momento da criação da solicitação, tanto para colagens quanto para arquivos.
source_origin Origem, como github, local_directory, uploaded_file, pasted_snippet, etc.

Nesta etapa, não se define o esquema JSONL de saída nem as colunas do banco de dados de pontuação. O objetivo é permitir que o AST Parser e as fases subsequentes de Retriever/LLM rastreiem a mesma entrada pelos mesmos critérios.

Colagem de código-fonte único

Quando o usuário insere diretamente o prompt e o código-fonte na interface web ou na CLI.

Exemplo:

Preciso converter isso para Godot 4?

extends KinematicBody2D

func _physics_process(delta):
    move_and_slide()

Processamento de fluxo:

  1. Agrupe a entrada como analysis_request.
  2. Defina source_origin como pasted_snippet.
  3. Como não há caminho de arquivo real, use um identificador temporário como pasted://snippet-<id>.gd para source_file.
  4. Calcule source_sha256 imediatamente com base no código original colado.
  5. Estime source_kind a partir da extensão, conteúdo do código e linguagem especificada pelo usuário.
  6. Passe source, source_kind, source_file e source_sha256 para o AST Parser.
  7. Armazene o prompt em analysis_request.prompt sem misturá‑lo ao Parser.

Entrada do diretório do projeto

Aplica‑se ao analisar um repositório do GitHub ou uma pasta local.

Processamento de fluxo:

  1. Agrupe a raiz do projeto como project_id.
  2. Percorra o sistema de arquivos a partir do caminho relativo.
  3. Registre primeiro a inclusão/exclusão de cada arquivo.
  4. Mantenha os arquivos excluídos em excluded_files para visualização na UI web ou nos logs.
  5. Expanda os arquivos incluídos como blocos de texto simples precedidos por um cabeçalho # <relative/path>.
  6. Envie arquivos .gd ao AST Parser para gerar funções/trechos de código na ordem original.
  7. Para arquivos incluídos que não são alvos do AST Parser, aplique regras de fragmentação adequadas ao tipo de arquivo.
  8. Cada fragmento é encaminhado à solicitação do Retriever com caminho original, ordem original e hash.

Os arquivos iniciais alvo são os mesmos do roadmap de referência.

Arquivo Direção AST/Fragmentação
.gd AST GDScript, função, classe, signal, variável, candidato a chamada de API
.tscn tipo de nó de cena, referência de script, referência de recurso, propriedade exportada
.tres tipo de recurso, classe de script, pista de material/shader/recurso
project.godot dica da versão Godot, autoload, renderizador, recurso, mapa de entrada
README/documentação normalmente excluído da análise AST de código‑fonte. Registre a exclusão e o motivo na UI/log.

Expansão do texto bruto do código‑fonte do projeto

A entrada por projeto primeiro expande cada arquivo como um bloco de texto bruto. Esse texto não é o prompt final a ser inserido de uma só vez no LLM, mas uma representação intermediária que preserva os limites e caminhos relativos dos arquivos.

Formato básico:

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

Regra:

  • O cabeçalho do arquivo deve estar no formato # <caminho/relativo>.
  • O caminho relativo é baseado na raiz do projeto.
  • O corpo do arquivo deve permanecer o mais próximo possível do original.
  • Este bloco de texto simples é usado para verificação de limites de arquivos e depuração.
  • No processamento real, todo esse texto não é enviado de uma só vez para um LLM ou analisador AST.
  • Cada arquivo separado por cabeçalhos recebe primeiro uma avaliação de inclusão/exclusão.
  • Arquivos .gd incluídos são encaminhados para analysis_request antes de irem ao analisador AST.
  • Arquivos de texto/configuração não .gd incluídos são enviados diretamente ao fragmentador.

Dessa forma, o “código-fonte completo” fornecido pelo usuário permanece em um formato legível por humanos, enquanto nas etapas subsequentes é possível rastrear arquivos/fragments excluídos, fragmentos AST, fragmentos diretos e payloads do Retriever por arquivo ou fragmento.

Plano de verificação baseado no projeto clonado

É difícil confirmar o fluxo do Analyzer Parser para o Retriever apenas com um exemplo abstrato. Portanto, clonaremos um projeto Godot real e usaremos a árvore de arquivos, a expansão de texto, a separação de fragmentos AST/diretos e o fluxo de solicitações do Retriever como referência.

O projeto de referência será definido da seguinte forma.

Item Valor
repositório godotengine/godot-demo-projects
caminho do projeto 2d/dodge_the_creeps
motivo da escolha É um demo oficial da Godot, contendo .gd, .tscn, project.godot, README e referências a assets, facilitando a verificação do fluxo de entrada do projeto.
critério de armazenamento O código clonado não será commitado no repositório. O documento conterá apenas a árvore e o plano de tratamento.

Critérios para clonagem:

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

Esta clonagem é apenas para verificação de fluxo. Não copie o código-fonte ou os assets do projeto clonado dentro do repositório.

O alvo da verificação são os caminhos completos dos arquivos do projeto clonado. Primeiro, expanda todos os caminhos em ordem ordenada e divida cada arquivo em fragmentos que o LLM possa ler. Não será criado nenhum banco de dados intermediário. O ponto crucial não é “ler todo o repositório de uma vez”, mas “criar fragmentos na ordem dos caminhos e chamar múltiplas vezes para 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

Arquivo de critério de fragmentação por tipo:

Tipo de arquivo Modo de fragmentação Forma de chamada subsequente
.gd O AST Parser cria fragmentos de funções, signals, variáveis, extensões de classe e chamadas de API na ordem original. Para cada fragmento de AST, chama o Retriever e envia ao LLM prompt + fragmento de AST + resultados da pesquisa.
project.godot Não é inserido no AST Parser. Se for incluído, cria fragmentos de seção/valor‑chave, cena principal, recurso e mapa de entrada. Envia o fragmento diretamente ao Retriever ou o anexa como contexto ao redor da avaliação de AST de .gd.
.tscn, .tres Não é inserido no AST Parser. Se for incluído, cria fragmentos de bloco de nó, ext_resource, sub_resource e conexão. Envia o fragmento diretamente ao Retriever ou o anexa como contexto ao redor da avaliação de AST do arquivo .gd conectado.
.md, .txt, LICENSE É tratado como candidato básico de exclusão na análise de AST de código‑fonte. Registra a exclusão e o motivo na UI/log. Se não houver modo de análise de documento separado, não é enviado ao Retriever.
.import, .uid, .gitignore, .gdignore Não é inserido no AST Parser. Se for incluído, cria fragmentos de linha ou valor‑chave. Envia o fragmento diretamente ao Retriever ou o usa apenas como contexto ao verificar relações de caminho/recursos.
Imagens, áudio, fontes e outros binários Não são inseridos no AST Parser, nem os bytes originais são enviados ao LLM. Apenas o caminho e o motivo da exclusão são registrados. Quando o caminho aparece em fragmentos de texto como .tscn ou .import, é usado apenas como informação de relação.

Em resumo, o único alvo que entra no AST Parser são os arquivos .gd. O repositório inteiro é percorrido na ordem dos caminhos, mas o resultado não é um “grande corpo enviado ao LLM de uma só vez”. O cabeçalho # <caminho/relativo> serve para indicar de onde o arquivo veio e como ele foi dividido em fragmentos de funções/código/configurações que foram enviados ao Retriever. Arquivos como .md que foram decididos ser excluídos permanecem na lista de exclusão, enquanto os arquivos .gd incluídos são divididos pelo AST Parser em fragmentos de funções ou trechos de código na ordem. As chamadas ao LLM são sempre realizadas várias vezes como prompt + fragmento atual + resultados da pesquisa do Retriever.

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

A unidade de entrada final do LLM é sempre da seguinte forma.

llm_judgment_request
  user_prompt
  project_id
  source_file
  chunk_id
  chunk_order
  chunk_kind
  chunk_text
  retrieved_evidence

Entrada de pesquisa do Retriever

O Retriever tem a função apenas de buscar o fragmento de código que forneci no banco de dados. O caminho do arquivo, o ID do fragmento (chunk id) e a ordem do fragmento (chunk order) não são condições de busca, mas informações de rastreamento. Portanto, na implementação, esses dois objetos são separados.

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 é usado apenas para UI, logs, reinicialização, depuração e conexão de respostas LLM. Na função de busca do Retriever, apenas retriever_query é passado.

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

Ou seja, a pesquisa será feita com os seguintes valores.

Usado na pesquisa Não usado na pesquisa
chunk_text source_file
symbol_candidates chunk_id
api_call_candidates chunk_order
reference_candidates source_sha256
prompt_terms project_id

O motivo de precisar do caminho do arquivo não é busca, mas rastreamento. Por exemplo, deve existir um trace player.gd:function:_process para que a interface web possa mostrar “Esta resposta veio do quarto fragmento de player.gd”. No entanto, quando o Retriever procura evidências no banco de dados, ele deve pesquisar não pelo caminho player.gd, mas pelo código dentro da função _process e pelos candidatos de API.

Por exemplo, o trecho da função _process de player.gd primeiro separa as informações de rastreamento e de busca.

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

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

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

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

Critério de código de pesquisa do Retriever

O código abaixo serve como referência para fixar a direção da implementação. Apenas RetrieverQuery é passado para o Retriever. O ChunkTrace é mantido pelo orquestrador fora do Retriever e é reunido novamente com os resultados da pesquisa.

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

O ponto importante no código acima é o seguinte:

  • A função de busca do Retriever não recebe source_file, chunk_id e chunk_order como condições de busca.
  • O Retriever realiza a busca vetorial com o query_embedding criado a partir do trecho de código, enquanto query.query_text é usado para a busca auxiliar por palavra‑chave/trigrama.
  • query_embedding é formado combinando raw_chunk_text, candidatos de API, candidatos de símbolo e a intenção do prompt.
  • query.query_text é criado a partir de API, símbolo, identificador e a intenção do prompt extraídos do trecho de código interno do arquivo.
  • O caminho do arquivo e a ordem do chunk são armazenados apenas em ChunkTrace.
  • retrieve_with_trace é a função orquestradora que, após a busca, combina o resultado com o trace. A própria busca é feita por retrieve_for_query.
  • docs_chunks, api_mapping e label_prototypes são pesquisados com o mesmo RetrieverQuery.

godot_rag.dynamic_retrieval_view não é uma nova lógica de decisão, mas sim uma view de nivelamento para busca. Mesmo que a estrutura de payload das três tabelas seja diferente, o Retriever vê as mesmas colunas.

dynamic_retrieval_view
  table_name
  id
  title
  content
  embedding
  search_text
  search_tsv
  metadata

Os alvos de pesquisa são expandidos da seguinte forma. Mesmo assim, campos de origem como source_file e source_url devem ser mantidos como metadados, mas não incluídos na string de pesquisa padrão. A pesquisa deve ser feita por fragmentos de código, API/símbolo/padrão.

Tabela title content Valor inserido em search_text
docs_chunks Título do documento, caminho da seção, símbolo Trecho do corpo do documento doc_type, symbol, section_path, content, api_symbols
api_mapping API alterada ou nome de API anterior Descrição da migração, motivo da mudança, exemplo old_symbol, new_symbol, change_type, description, before_code, after_code
label_prototypes Nome do protótipo ou rótulo Forma de uso da função, composição de argumentos, exemplo de mudança de padrão de chamada label, input_pattern, prompt_pattern, before_code, after_code, expected_response, explanation

Exemplo de visualização 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;

No view acima, o valor que o Retriever realmente pesquisa é query_text. Por exemplo, se for o chunk _process, ele pesquisa da seguinte forma.

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

Os resultados da pesquisa não são respostas definitivas que devem ser inseridas diretamente no LLM, mas sim evidências candidatas. Ao solicitar um julgamento do LLM, inclua o chunk e as evidências candidatas, e o Qwen 3.6 verificará novamente “se essa evidência está relacionada ao chunk atual”.

Critérios de depuração de transmissão

Para impedir que a IA selecione arbitrariamente “apenas as funções principais” de um arquivo .gd e as envie, ou, ao contrário, envie o arquivo inteiro como uma única solicitação ao LLM, é necessário poder verificar o diff antes e depois da transmissão. O que deve ser verificado não é se “todo o arquivo original foi enviado ao LLM”, mas se “os trechos de funções/código gerados a partir do arquivo original foram rastreados completamente na ordem original e cada trecho foi encaminhado como solicitação ao Retriever”.

Na fase de expansão do arquivo, primeiro cria‑se blocos de texto original organizados por caminho.

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

Em seguida, deixa a lista de fragmentos criada pelo 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)

O payload também é deixado em unidades de chunk imediatamente antes do envio do Retriever.

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

Regra de validação:

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

Se essa validação falhar, não será enviado. Essa regra é especialmente importante em arquivos .gd. A intenção não é inserir o arquivo .gd inteiro no LLM, mas sim garantir que trechos de funções/código extraídos de .gd cheguem ao Retriever sem perder o caminho original e a ordem original.

Ao receber este projeto como entrada, ele primeiro o expande em blocos de texto legíveis por humanos. O bloco abaixo exibe o conteúdo completo dos arquivos de texto, excluindo ativos binários, organizado por unidades # <caminho/relativo>. Isso inclui arquivos que normalmente seriam excluídos da análise de código‑fonte, como .md e LICENSE. No exemplo de documentação, o conteúdo dentro do caminho relativo é mantido integralmente, sem omissões, para que os critérios de rastreamento possam ser verificados. Contudo, se a política de exclusão for aplicada, os arquivos excluídos não são enviados ao Retriever e são listados em excluded_files com a respectiva justificativa.


# .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/pt-BR/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

Os ativos binários não são incluídos no corpo de texto plano acima, mas devem permanecer na repository path order e nas listas de exclusão/referência. Por exemplo, se main.tscn aponta para res://art/gameover.wav, a entrada de julgamento do LLM contém o chunk proveniente de main.tscn, os metadados de caminho que referenciam art/gameover.wav, os resultados da pesquisa do Retriever e o prompt do usuário, tudo dentro do mesmo objeto de solicitação. Os bytes binários originais não são enviados ao Retriever ou ao LLM.

Exemplo de transmissão por arquivo

Se remover o bloco # player.gd do conjunto de texto plano, obterá um único analysis_request como a seguir.

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)

No AST Parser, passa‑se apenas os campos necessários para a análise de código, não toda a solicitação acima.

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"

O parser divide o arquivo em fragmentos. Até que a implementação real seja confirmada, espera‑se fragmentos de nível seguinte.

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 chunk é passado novamente para a solicitação do Retriever.

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

Este fluxo não é um tratamento especial apenas do player.gd. Apenas o estágio de criação de fragmentos varia de acordo com o tipo de arquivo. main.gd, mob.gd e hud.gd repetem analysis_request → ast_parse_input → ast_parse_output.chunks[] → retrieval_request. Arquivos que não são alvo do AST Parser, como project.godot e main.tscn, repetem analysis_request → direct_chunk_output.chunks[] → retrieval_request.

O fluxo de envios repetidos a ser verificado neste projeto de exemplo é o seguinte:

  1. Defina a raiz do projeto como um único project_id.
  2. Coleta os arquivos de texto relacionados ao Godot usando caminhos relativos.
  3. Expanda cada arquivo em um bloco de texto simples precedido por um cabeçalho # <relative/path>.
  4. Converta cada bloco de texto simples novamente em um analysis_request por arquivo.
  5. Calcule o source_sha256 em cada analysis_request.
  6. Arquivos .gd são processados pelo AST Parser, que gera ast_parse_output.chunks[].
  7. Arquivos incluídos que não são alvo do AST Parser são fragmentados diretamente pelo fragmentador, que gera direct_chunk_output.chunks[].
  8. Crie solicitações ao Retriever com base em cada chunk.
  9. Combine o resultado do Retriever com o chunk e repita a solicitação de julgamento do LLM.
  10. Todas as solicitações necessárias de arquivos/chunks devem receber respostas corretas antes de serem elevadas como candidatos de julgamento em nível de projeto.

Em resumo, um único projeto é dividido em várias solicitações como descrito acima.

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

Nesse caso, se um arquivo ou um chunk falhar, o projeto inteiro não é concluído imediatamente. As solicitações que falharam permanecem como pendentes ou para retry, e apenas os chunks que responderam corretamente são acumulados. A avaliação em nível de projeto só é feita depois que as solicitações de repetição necessárias tenham sido suficientemente respondidas.

Fluxo de Solicitações Repetidas

Um projeto não é avaliado com apenas uma solicitação ao LLM.

A entrada do projeto é dividida em várias solicitações, como a seguir.

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

Cada unidade de repetição em cada etapa é a seguinte.

Etapa Unidade de repetição
Varredura de arquivos Arquivos dentro do projeto
Analisador AST Arquivos .gd incluídos
Fragmentação direta Arquivos incluídos que não são alvo do analisador AST
Registro de exclusão Arquivos excluídos como .md, binários etc.
Recuperador Chunk AST ou chunk direto
Julgamento Qwen 3.6 Chunk AST/direto + evidência de busca
Classificação do projeto Resultado do julgamento de todos os chunks

Portanto, um projeto não termina simplesmente como verdadeiro/falso; vários arquivos e fragmentos dentro do projeto precisam ser processados corretamente individualmente. O julgamento final do projeto só pode ser feito depois que os resultados do Recuperador/LLM/Validador de todos os fragmentos necessários forem reunidos.

Qwen 3.6 opera sob demanda, portanto, em vez de concluir tudo em uma única grande solicitação, é mais adequado chamar várias vezes em unidades de chunk AST ou chunk direto. Nesse caso, os chunks já concluídos são reutilizados, e apenas os chunks que falharam ou foram interrompidos são solicitados novamente.

Dados enviados ao analisador AST

Não insira o prompt diretamente no analisador AST.

A entrada para o analisador deve ser minimizada da seguinte forma.

ast_parse_input
  project_id
  source_file
  source_kind
  source_text
  source_sha256

Assim a separação é feita pelos seguintes motivos.

  • O mesmo código‑fonte pode ser analisado novamente em vários prompts.
  • Mesmo que o prompt mude, o resultado da análise AST pode ser reutilizado.
  • O parser evita que, por causa da “intenção da pergunta”, a estrutura do código seja interpretada de forma diferente.
  • Só nas etapas Retriever/LLM é que o prompt pode ser usado para mudar a direção do julgamento.

Saída do AST Parser

Em vez de enviar todo o código‑fonte diretamente ao LLM, o parser cria uma lista de trechos rastreáveis.

ast_parse_output
  project_id
  source_file
  source_sha256
  parse_status
  chunks[]

Cada chunk possui as seguintes informações.

chunk
  chunk_id
  source_file
  span
  node_kind
  symbol_candidates
  api_call_candidates
  code_text
  surrounding_context
Item Descrição
chunk_id ID determinístico baseado em projeto/arquivo/escopo
span intervalo de linha/coluna ou deslocamento em bytes
node_kind function, class, call, signal, property, scene_node etc.
symbol_candidates candidatos a nomes de classe, tipo, método
api_call_candidates candidatos a chamadas da API do Godot
code_text código original desse fragmento
surrounding_context código ao redor necessário para julgamento. Não o arquivo inteiro, apenas o trecho necessário.

Saída de fragmentação direta

Arquivos incluídos que não são alvo do AST Parser criam direct_chunk_output. Essa saída não é um AST, mas o resultado da divisão da estrutura de texto/configuração/scene na ordem original.

direct_chunk_output
  project_id
  source_file
  source_sha256
  chunk_status
  chunks[]

Cada chunk possui as seguintes informações.

direct_chunk
  chunk_id
  source_file
  chunk_order
  span
  chunk_kind
  chunk_text
  reference_candidates

Exemplo:

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"

Arquivos que foram excluídos como .md não são criados em direct_chunk_output, permanecendo em excluded_files.

Combinação de Prompt e Fragmentos

O prompt é recombinado após o Parser.

+ ast chunk ou direct chunk
+ project/file metadata
-> retrieval_request
Intenção do Prompt Prioridade Retriever
“O que significa este código?” docs_chunks
“Preciso converter para Godot 4?” api_mapping, label_prototypes
“Este código é Godot 3 ou 4?” docs_chunks, api_mapping, label_prototypes todos são candidatos

Mesmo nesta etapa, nenhuma tabela específica recebe tratamento especial. Dependendo da natureza do prompt e dos fragmentos de AST, busca‑se a evidência necessária nos documentos oficiais em formato JSONL.

Unidade passada ao LLM

No Qwen 3.6, não se envia todo o projeto original de uma só vez.

A unidade de chamada ao LLM é o seguinte agrupamento:

llm_judgment_request
  prompt
  project_id
  source_file
  chunk_id
  chunk_order
  chunk_kind
  chunk_text
  surrounding_context
  retrieved_evidence
  judgment_contract

Aqui, judgment_contract ainda não é o esquema JSONL final. Nesta fase, ele funciona como um contrato de solicitação que informa ao LLM o que deve ser julgado.

retrieved_evidence ainda não é o esquema de armazenamento final, mas um campo abstrato que representa um conjunto de resultados de busca. Na etapa de armazenamento posterior, ele pode ser dividido conforme o tipo de evidência, como IDs de evidência docs_chunks, IDs de evidência api_mapping ou IDs de evidência label_prototypes. Neste documento, ele é usado apenas com o sentido de “conjunto de evidências JSONL de documentação oficial recuperada”.

Exemplo:

- Se este trecho de código usa a API do Godot 3
- Se é válido para o Godot 4
- Se há justificativa para migração
- Se a referência da documentação oficial encontrada está realmente relacionada ao código

Limite de reutilização

Mesmo que o código‑fonte seja o mesmo, se o prompt for diferente, a avaliação do Retriever/LLM pode mudar.

Por outro lado, se o código‑fonte for o mesmo, o resultado do analisador AST pode ser reutilizado.

Etapa Critério de reutilização
Análise AST source_sha256, source_kind, versão do Parser
Geração de chunk source_sha256, versão do chunking
Busca no Retriever intenção do prompt, símbolos do chunk, versão da recuperação
Avaliação do LLM prompt, chunk, evidência, modelo, versão do prompt

Ao respeitar esse limite, “análise da estrutura do código” e “avaliação baseada na intenção da pergunta” não se misturam.

Perspectiva de interrupção/reinício

Como a solicitação de análise pode se estender, cada etapa deve ser reiniciável.

Unidade mínima de armazenamento:

  • Status de conclusão da varredura de arquivos
  • Estado da análise AST por arquivo
  • Estado da geração de chunk
  • Estado da busca no Retriever por chunk
  • Estado da avaliação do LLM por chunk
  • Status de aprovação do Validador

Se o RunPod ou o aplicativo local parar no meio, apenas o chunk que estava em processamento volta ao estado pendente, enquanto arquivos e chunks concluídos são reutilizados.

Itens ainda não definidos

Este documento não define os seguintes itens.

Apenas a ordem de dependência é registrada.

Ordem Item Motivo Localização atual de rastreamento
1 Implementação do Parser AST Dependendo do parser usado, a estrutura do chunk e o método de recuperação de falhas mudam. AST Parser에 넘기는 데이터, AST Parser 출력 neste documento
2 Esquema final de resposta JSONL É preciso definir qual JSONL será usado para validar o resultado da avaliação do LLM para poder anexar o Validador. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md
3 Colunas do banco de dados de pontuação Após organizar as respostas JSONL e os IDs de evidência do Retriever, será possível definir as colunas de armazenamento. docs/retrospectives/2026-06-25-source-analysis-scoring.md
4 Rótulo de classificação do sistema de arquivos Depois de observar quais resultados de avaliação são armazenados no banco de dados de pontuação, define‑se o rótulo do projeto. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md
5 Regras de geração SFT/DPO Após o sistema de arquivos classificado ser acumulado, será projetado separadamente. docs/roadmaps/2026-06-25-source-analysis-scoring-architecture.md

A única coisa que está definida atualmente é uma.

Prompt e código‑fonte são agrupados em uma única solicitação de análise,  
apenas o código‑fonte e os metadados do arquivo são passados ao AST Parser,  
e o prompt é usado na fase de julgamento do Retriever/LLM após o Parser.