Este documento é a fonte única do formato do pacote
de problema do MOJ. Explica o que é um pacote, o que faz cada arquivo
dentro dele, o que são os metadados (.moj-meta.json e
.moj-id), o que são orgs e
coleções, e como um problema sai de rascunho e chega ao
aluno.
Quem monta um pacote na prática (passo a passo, com os comandos) deve ler o
README.mddo mojtools, que tem o roteiro. Aqui está a referência: o que cada coisa é e por quê. As rotas da API que leem e escrevem o pacote estão em API.md.
⚠ O pacote não é fonte de leitura para rota de contest nem de treino. Quem serve prova ou treino usa o que já está materializado — índice de donos,
var/jsons{,-private}/<id>.json,run/tl/— e nunca abre a árvore de pacotes. Quem precisa de um dado que só existe aqui dentro materializa antes. A fronteira, os motivos e o inventário do que ainda falta:cdmoj/CLAUDE.mdebash server/test/sem-pacote.sh.
Doc atrasada = bug. Mudou o pacote (arquivo novo, campo novo, layout, de onde vem o título)? Atualize este documento no mesmo commit. Os outros lugares (o
CLAUDE.mddocdmoj, domojtoolse domoj-cli) apontam para cá em vez de repetir o formato.
.moj-meta.json:
os metadados do problema.moj-id: o
ponteiro local da CLIUm pacote é um diretório que descreve um problema por inteiro: o enunciado, os testes, as soluções de referência e os limites de execução. Não existe banco de dados de problemas: o pacote é o problema.
Três coisas valem saber desde já:
moj push, o servidor grava os arquivos e commita ali mesmo
(função problem_commit, em
server/api/v1/lib/problems.sh, com trava por
problema).<org>#<prob>, por exemplo
apc#fatorial. A parte antes do # é a
org (seção 7), a parte depois é o nome do diretório do
pacote.moj), e as duas falam com a mesma
API. Este documento descreve o que essas ferramentas gravam, para que
você entenda o que está acontecendo e possa conferir.moj-problems/<org>/<prob>/ # o pacote (raiz do repo git local daquele problema)
A raiz moj-problems/ é configurável pela variável
MOJ_PROBLEMS_DIR. No checkout de desenvolvimento ela fica
ao lado do cdmoj/.
Um pacote não contém o placar, o histórico de submissões, nem os tempos-limite calibrados. Isso tudo vive fora dele:
| Coisa | Onde fica | Quem escreve |
|---|---|---|
| Tempos-limite calibrados | run/tl/<id>.json |
os juízes, ao calibrar |
| Relatório de validação | run/validation/<id>.json |
validate-problem.sh |
| Índice servido ao aluno | contests/treino/var/jsons/<id>.json |
gen-problem-json.sh |
| Registro de orgs | contests/treino/var/orgs.json |
a API (lib/orgs.sh) |
| Registro de coleções | contests/treino/var/collections.json |
a API (lib/problems.sh) |
Árvore de um pacote completo. A coluna da direita mostra em quantos dos 453 pacotes do acervo atual cada item aparece, para dar noção do que é rotina e do que é exceção.
moj-problems/<org>/<prob>/
├── .git/ repo git local do problema 453 (sempre)
├── .moj-meta.json metadados (título, público, coleções, …) 453 (sempre)
├── author autor(es) do problema 453 (obrigatório)
├── tags assuntos, uma tag por linha 453
├── conf limites e ajustes de execução 453
├── docs/
│ ├── enunciado.md o enunciado em português (também .org e .tex) 453 (obrigatório)
│ ├── enunciado.en.md o enunciado em inglês (idem enunciado.es.md) opcional
│ ├── notes/sample1.md explicação de cada exemplo (markdown; 1/sample) opcional
│ ├── notes/sample1.en.md a explicação traduzida (cai na PT se faltar) opcional
│ ├── <figura>.png imagens do enunciado/notas (o render embute) opcional
│ ├── solucao.md editorial, só para o autor opcional
│ └── solucao.en.md o editorial traduzido (idem solucao.es.md) opcional
├── tests/
│ ├── input/sample1 exemplo (aparece no enunciado) obrigatório, >= 1
│ ├── output/sample1 resposta do exemplo
│ ├── input/<nome> teste oculto (corrige a submissão)
│ ├── output/<nome> resposta do teste oculto
│ └── score grupos de pontuação (subtarefas) 254 (opcional)
├── sols/
│ ├── good/ soluções corretas 453 (obrigatório, >= 1)
│ ├── wrong/ soluções erradas de propósito 18 (opcional)
│ ├── slow/ soluções lentas de propósito 7 (opcional)
│ ├── pass/ soluções que devem passar raspando 3 (opcional)
│ └── upcoming/ soluções em rascunho 1 (opcional)
└── scripts/ correção especial 79 (opcional)
├── compare.sh comparador próprio (checker) 18
├── validator.cpp validador de ENTRADA (testlib) opcional
└── <lang>/compile.sh compilação própria (submissão de função) 201
Dois arquivos aparecem no acervo mas não fazem parte do formato:
problem.yaml e .kattis.json (em 395
pacotes) são metadados do formato Kattis, gravados pelo
importador/exportador (mojtools/kattis/). O MOJ os ignora
por completo..moj-id (em 336 pacotes) é um arquivo do
cliente, não do pacote. Ele foi parar ali por descuido
de migrações antigas. Ver a seção 6.docs/enunciado.mdO texto do problema. Aceita três formatos, procurados nesta ordem:
enunciado.md, enunciado.org,
enunciado.tex. O .md é o canônico e o
recomendado.
Como escrever o texto — Markdown, fórmulas em TeX, parênteses, casos, matrizes, o que evitar e como cada construção sai no PDF do caderno, com exemplos: ENUNCIADO.
Três regras que o portão de qualidade cobra:
## Entrada e ## Saída
são obrigatórias. Sem elas o problema não passa na validação.
(O validador também aceita ## Input, ## Output
e ## Salida, e de um a três #.)% Título do problema é legado: o
renderizador a remove. O título verdadeiro é o campo
display_title do .moj-meta.json (seção 5), e o
renderizador injeta um <h1> a partir dele.tests/input/sample* e
tests/output/sample* e injetados no fim do HTML. Se você
escrever um exemplo à mão dentro do enunciado, ele vai aparecer
duplicado. (A validação avisa, mas não bloqueia.) A exceção é o problema
sem exemplo (SAMPLE=no no
conf, seção 4, "Problema sem exemplo"): nele o exemplo vai
no texto, numa seção ## Exemplo, e a validação não
avisa.Imagens — dois jeitos, ambos viram HTML autocontido
(o renderizador roda com --embed-resources e embute tudo em
base64):
data:URI DENTRO do texto do enunciado — não existe arquivo
separado, viaja com o markdown por qualquer via.docs/ +
 no texto: a figura fica editável como
arquivo. Viaja no moj push/clone (campo
docs_files, o análogo do scripts_files; cap
2MB por imagem), no moj upload (tar) e aparece no
Pré-visualizar (o preview recebe as imagens do pacote/locais). Nomes
simples ([A-Za-z0-9._-], extensão
png/jpg/jpeg/gif/svg/webp).Grafos: um bloco de código com a classe
.graph (fonte graphviz
DOT) é renderizado como SVG — a fonte DOT fica
editável no enunciado, não é uma imagem colada. Ex.:
```{ .graph .center caption="…"} graph G { a -- b; } ```.
Detalhes e atributos em
mojtools/docs/enunciado-grafos.md.
Quem renderiza é um script só:
mojtools/render-statement.sh. O botão "Pré-visualizar" do
editor, o HTML que o aluno lê e o HTML que a validação confere são
exatamente o mesmo. Não existe segundo renderizador, e não se deve criar
um.
docs/notes/<sample>.md
— a explicação de cada exemploOpcional. Um arquivo Markdown por exemplo, com o
MESMO nome do teste de exemplo: docs/notes/sample1.md
explica o tests/input/sample1, e assim por diante. É
markdown normal — parágrafos, listas, código, e
imagens ( com a figura em
docs/, igual ao enunciado; o render embute em base64). A
nota aparece logo abaixo do exemplo correspondente.
docs/notes/sample1.md # "Neste exemplo temos:\n\n- 4 grupos...\n\n"
docs/notes/sample2.md
Você nunca edita JSON: o editor web (campo
"explicação" de cada exemplo), o moj edit (opção
[n]ota) e o moj push/clone leem e
gravam esses arquivos; a serialização é da plataforma. A validação avisa
quando há nota sem exemplo correspondente.
Legado: docs/sample-notes.json (array
JSON de strings, por índice) ainda é LIDO em pacotes antigos, mas
nunca mais é escrito — qualquer salvamento converte
para docs/notes/.
docs/solucao.mdOpcional. É o editorial: a explicação da ideia da
solução, para o autor e para quem for reusar o problema. O aluno
nunca vê este arquivo. O gen-problem-json.sh o
ignora de propósito. É o lugar certo para escrever "a solução é uma DP
em O(n log n)" sem medo. O documento de editorial de um contest lê este
arquivo (ou a tradução solucao.<lang>.md,
abaixo).
enunciado.<lang>.md,
notes/<sample>.<lang>.md,
solucao.<lang>.mdUm problema pode ter o enunciado em mais de um idioma. As regras são simples:
| Arquivo | O que é | Se faltar |
|---|---|---|
docs/enunciado.md |
o enunciado em português. É o texto principal e é obrigatório | o problema não valida |
docs/enunciado.<lang>.md |
a tradução. <lang> é en ou
es. Só markdown |
o problema tem um idioma só |
docs/notes/<sample>.<lang>.md |
a explicação traduzida do exemplo | o exemplo mostra a explicação em português |
docs/solucao.<lang>.md |
o editorial traduzido | o documento de editorial usa o português |
titles no .moj-meta.json |
o título de cada tradução (seção 5) | o título em português |
Os exemplos vêm dos testes e aparecem em todos os idiomas, com os
rótulos do idioma (Exemplos/Entrada/Saída/Explicação ·
Examples/Input/Output/Explanation ·
Ejemplos/Entrada/Salida/Explicación). As figuras ficam em
docs/ e servem a todos os idiomas.
A validação trata cada tradução como o português: ela tem de
renderizar e tem de ter as seções de entrada e de saída
(## Input/## Output,
## Entrada/## Salida). Uma tradução sem a
explicação de um exemplo gera o aviso
nota-sem-traducao(<sample>,<lang>). O aviso não
bloqueia.
O índice do treino (var/jsons/<id>.json) leva
statement_langs (a lista, português primeiro) e
statements{<lang>:{title,html_b64}}. O português
continua em title e statement_html_b64, como
sempre. A página do problema mostra um chip por idioma. Em um contest, o
admin ou o juiz-chefe escolhe os idiomas que a sanfona oferece
(STATEMENT_LANGS; ver API.md).
Um exemplo com mais de 256 KB entra truncado no HTML
do enunciado (só o começo, com o aviso "Exemplo grande"); com mais de
4 MB ele não vai como dado
({name, size, too_big:true}). Exemplo é para ler: teste
grande é teste oculto.
O índice leva também samples:
[{name, input, output}], o texto dos exemplos. A seleção é
a MESMA do HTML do enunciado (stmt_sample_names em
mojtools/statement-langs.sh: os
tests/input/sample*, ou nenhum com SAMPLE=no).
Um teste oculto nunca entra nesse campo. É o que alimenta o botão
⬇ Exemplos e o
moj-comp samples/fetch, pela rota
/treino/problem e pela /contest/samples.
Na API de autoria, as traduções viajam no campo
translations de /problems/source e
/problems/edit:
{"<lang>": {title, enunciado_md, editorial_md, notes:{"<sample>": md}}}.
Idioma ausente do objeto fica como está. Idioma com valor
null é apagado por inteiro. A CLI
(moj clone/push) e o editor web usam esse
campo; você só edita os arquivos.
Não confundir com a mecânica da correção especial, que é assunto do
scripts/ e está documentada em
mojtools/docs/correcao-especial.md.
tests/input/ e
tests/output/Todo arquivo em tests/input/ precisa ter um arquivo de
mesmo nome em tests/output/. Isso é
checado na validação, nos dois sentidos (input sem output e output sem
input reprovam).
O nome do arquivo decide o papel do teste:
| Nome | Papel |
|---|---|
sample1, sample2, … |
exemplo: aparece no enunciado, e também corrige |
| qualquer outro nome | teste oculto: só corrige, o aluno nunca vê |
Os exemplos são todos os arquivos que começam com
sample, ordenados por ls -1v (ou seja,
sample2 vem antes de sample10, e não depois).
A validação exige pelo menos um exemplo, ou a
declaração de que o problema não tem exemplo (SAMPLE=no,
abaixo). Teste oculto nunca aparece como exemplo, nem
quando falta sample*.
SAMPLE=noEm alguns problemas, entrada e saída de exemplo não fazem sentido para o aluno:
Nesses problemas:
tests/input/sample*.SAMPLE=no no conf. No editor
web, é a opção este problema não tem exemplos da aba
Limites. Na CLI, moj edit → 8 (conf) → 6.
O moj interactive já grava a linha.## Exemplo: uma figura, uma chamada da função e o que ela
devolve, a transcrição da conversa com o árbitro.O efeito de SAMPLE=no:
samples do índice fica vazio: não há botão
⬇ Exemplos no treino, nem link
Exemplos no contest (has_samples:false em
/contest/problems), e o moj-comp samples não
baixa nada. Vale mesmo se existirem arquivos sample*: eles
continuam corrigindo, como qualquer teste, mas nenhum deles vira
exemplo;SAMPLE não entra no
tl-checksum: marcar ou desmarcar não pede recalibração.Valores aceitos: no, n, nao,
não, false, 0 (com ou sem aspas).
Sem a linha, o problema tem exemplos (tests/input/sample*).
Até 2026-09-23 existiam dois legados que saíram: o arquivo
samples na raiz do pacote (vazio = sem exemplos) e o
fallback que mostrava os dois primeiros testes quando faltava
sample* — em problema de função ele exibia o formato
interno do driver.
O nome dos testes ocultos é livre. As convenções que aparecem no
acervo são test-001, test-002 (estilo APC) e
<prob>_1_1, <prob>_1_2 (estilo
OBI, que agrupa por subtarefa; ver tests/score).
tests/scoreOpcional. Liga a pontuação por grupos (subtarefas). Sem este arquivo, a nota do problema é a porcentagem de testes que passaram.
O formato é texto puro, uma linha por grupo:
sample* - 0 pontos
2015f2p1_capitais_1_*, 2015f2p1_capitais_2_* - 40 pontos
2015f2p1_capitais_3_*, 2015f2p1_capitais_4_*, 2015f2p1_capitais_5_* - 60 pontos
Lendo a linha: um ou mais globs de nome de teste,
depois -, depois o peso do grupo.
Regras:
", "). Isso não é estética: é o que o parser espera dos
dois lados (API e juiz).# é comentário.
Qualquer outra linha que não seja
<globs> - <N> pontos é ignorada com
aviso no log do juiz — não vire grupo.aula_* casa aula_2_1), e todo teste
precisa cair num grupo. O validate-problem.sh
confere tudo isso no upload (check score_file_sane): teste
sem grupo, ou grupo de peso>0 sem nenhum teste, é pacote quebrado —
se chegar ao juiz mesmo assim, a submissão sai Judge
Error com nota 0 (erro do pacote, não do aluno). Grupo de
peso 0 sem teste (ex.: sample* - 0 pontos
num problema SAMPLE=no) é aceito e não derruba nada.O veredicto é o do pior teste; os grupos decidem só a
nota. Um grupo que caiu por estouro de tempo sai Time
Limit Exceeded com a nota dos grupos que passaram (e não
"resposta errada"): é o mesmo veredicto que os testes dariam sem grupos.
A string que o juiz devolve (e que o history guarda) é
<veredicto canônico>,<pontos>p. Pontos | <por grupo> | [quantitativos <código>(<n>) …]:
Accepted,100p. Pontos | 30 | 70 |
Time Limit Exceeded,30p. Pontos | 30 | 0 | quantitativos TLE(2) AC(8)
Judge Error,0p. teste 'extra1' sem grupo em tests/score (erro do pacote)
O que o aluno lê é o prefixo (o servidor o canoniza na leitura) e a
nota é o primeiro NNp da string. Até 24/09/2026 toda falha
de grupo saía Wrong,<n>p — um TLE chegava ao aluno
como "Wrong Answer". O histórico gravado antes disso fica como está
(Wrong,… e o legado Wrong. Pontos | … seguem
lidos como Wrong Answer); um rejulgamento traz o veredicto real.
Quem interpreta é o mojtools/score-summary.sh, no juiz.
Editar o tests/score (ou um tests/output/*)
muda o checksum do pacote — o juiz re-baixa e recalibra sozinho.
sols/As soluções de referência, separadas por categoria. A
extensão do arquivo é o que define a linguagem
(sol.c é C, sol.cpp é C++,
Main.java é Java, e assim por diante). C++ aceita quatro
extensões: .cpp, .cc, .cxx e
.c++. O julgador trata as quatro como cpp.
| Diretório | O que é | O que a calibração exige dela |
|---|---|---|
good/ |
soluções corretas | obrigatório, pelo menos uma. Aceita em todos os
testes, dentro do tempo-limite efetivo (o que o juiz
cobra, com TLOVERRIDE). É a que a calibração usa para medir
o tempo-limite |
wrong/ |
soluções erradas de propósito | reprovada, de preferência por resposta errada (WA): prova que os testes pegam o erro |
slow/ |
soluções lentas de propósito | TLE em pelo menos 1 teste e aceita nos outros: prova que o tempo-limite reprova a solução ruim |
pass/ |
soluções que devem passar raspando | aceita em todos os testes, dentro do tempo-limite efetivo: prova que o limite não é apertado demais |
upcoming/ |
rascunhos | não roda |
A calibração confere cada solução contra essa tabela (seção 10,
"Soluções") e o resultado aparece no editor, no Painel e no
moj calib/moj check.
Na prática, ponha uma good em cada linguagem que você
quer que o aluno possa usar. O tempo-limite é calibrado por
linguagem, e uma linguagem sem solução good aceita
simplesmente não ganha tempo-limite naquele juiz (o aluno não consegue
usá-la).
Salvar QUALQUER solução manda o juiz buscar o pacote de
novo (é o pkg_version da seção 10), então "Salvar"
+ "Calibrar" roda o sols/ que você acabou de escrever. Só
good/ mexe no TL.
scripts/ (correção
especial)Opcional. É como o problema customiza a compilação,
a execução ou a comparação. O build-and-test.sh procura os
arquivos do problema antes dos padrões de
mojtools/lang/<lang>/, então qualquer coisa que você
ponha aqui vence o comportamento normal.
Os usos mais comuns:
| Arquivo | Uso | Quantos no acervo |
|---|---|---|
scripts/<lang>/compile.sh |
submissão de função: o aluno entrega só a função, e
este script injeta o main que lê a entrada, chama a função
e imprime o resultado. Declare a linguagem em
FUNCTION_LANGS no conf (abaixo): é o que faz o
editor do aluno abrir vazio. O mesmo arquivo também serve para
ban e para flags de OpenMP/MPI, que não são de
função |
201 |
scripts/compare.sh |
checker: a resposta não é única (tolerância de ponto flutuante, várias respostas válidas), então o problema traz o próprio comparador | 18 |
scripts/checker.cpp |
o fonte do checker quando ele é testlib (padrão
Polygon/Maratona). Vem junto de um compare.sh de 10 linhas
— o stub — instalado por
mojtools/testlib/install-checker.sh. O
testlib.h NÃO vai no pacote (é vendorado no
mojtools) e o binário do checker nunca é commitado (a
bridge do mojtools o compila no juiz, sob demanda, e cacheia
FORA de scripts/). |
|
scripts/arbitro.{cpp,py,sh} +
scripts/c/{prep,run}.sh |
problema interativo
(mojtools/interactive/install-interactive.sh) |
— |
scripts/validator.cpp |
validador de ENTRADA (testlib
registerValidation, o padrão do Polygon): confere se cada
tests/input/* segue o formato e os limites do enunciado.
Não julga solução nenhuma. A calibração completa o roda
no juiz (dimensão Entradas, seção 10); na sua máquina,
moj validator. Fica fora do tl_checksum (mexer
nele não recalibra) e dentro da versão do pacote. Guia:
mojtools/docs/validador-testlib.md |
— |
O contrato do comparador: recebe $1 = saída do aluno,
$2 = saída esperada, $3 = entrada, e responde
pelo código de saída (4 = aceito, 5 = aceito
com erro de formatação, 6 = resposta errada, qualquer outro
= erro de juiz).
Stub, não cópia. O que roda no host do
juiz — scripts/compare.sh,
scripts/<lang>/prep.sh,
scripts/summary.sh — vai no pacote como um
stub que chama o driver canônico do mojtools; só o que
entra na jaula
(scripts/<lang>/run.sh, compile.sh) é
cópia de verdade. É o que permite consertar um bug do driver em
um lugar só: quando cada pacote levava a sua cópia da
bridge do checker, um bug nela nasceu replicado em 198 pacotes
(e derrubava todos os testes de quem o usasse). Um
problema pode, claro, trocar o stub pelo seu próprio comparador (é o
caso dos 18 do acervo, todos escritos à mão).
Todo .sh em scripts/ precisa do bit de
execução (chmod +x) — e o bit viaja (o
moj push/clone e o upload
preservam). Sem ele o juiz recebe Permission denied ao executar
o script: compare.sh/prep.sh rodam no
host (fora da jaula) e viram erro de juiz (UE) em todos
os testes; run.sh/compile.sh são
montados na jaula e viram Compilation Error. O
validate-problem.sh reprova o pacote
(scripts_exec) antes que isso aconteça.
Modo dos arquivos: 644 (ou 755 com +x),
sempre. O servidor normaliza em toda escrita, pelos dois
caminhos (moj push e moj upload) — não é o
umask do processo que decide. Isso importa porque o
tl-checksum inclui o modo de
scripts/*: se o mesmo conteúdo entrar com modo diferente
conforme o caminho, o juiz vê "pacote mudou" e recalibra à
toa.
Mexer em scripts/ obriga a recalibrar
(seção 10) — exceto no scripts/validator.cpp, que não muda
o julgamento.
Os arquivos de scripts/ formam 4 slots
independentes que COMPÕEM — compile (submissão de função/ban),
run (interativo), compare (checker/tolerância), summary (pontuação) —
então função + checker especial é combinação normal; só o interativo não
mistura. O validator.cpp não ocupa slot nenhum: compõe com
todos. O guia-hub é mojtools/docs/correcao-especial.md
(proibir funções da biblioteca, visão geral); os guias longos:
mojtools/docs/submissao-de-funcao.md (submissão de
função — templates prontos via moj fn ou pelo
editor web, com a sentinela anti-IO), checker-testlib.md e
problema-interativo.md.
confOs limites e ajustes de execução. É um arquivo de shell, lido
com source, então nunca interpole nele conteúdo
vindo de usuário.
Um conf típico do acervo é curto:
TLMOD[calibrafactor]=1.35
TLMOD[java.drift]=0.02
TLMOD[spim.sum]=1
ULIMITS[-u]=10000
ALLOWPARALLELTEST=yTodas as chaves que o build-and-test.sh entende:
Tolerância (drift) no relatório. Um teste aceito com tempo acima do limite passou pela tolerância. O
report.htmlmostra esse tempo em amarelo, com quanto passou (0.98s (+0.16s na tolerância)). Azul é dentro do limite e a cor de TLE é estouro. A tabela de testes do editor (test-run e calibração) usa o mesmo amarelo.
| Chave | Default | O que faz | Uso hoje |
|---|---|---|---|
TLMOD[calibrafactor] |
1.35 |
multiplicador aplicado ao tempo da solução good para
virar o tempo-limite. Subir dá folga ao aluno |
453 |
TLMOD[<lang>.drift] |
0 |
tolerância (em segundos) acima do tempo-limite antes de dar TLE,
naquela linguagem: o teste só é TLE quando
tempo − TL > tolerância |
404 (java) |
TLMOD[default.drift] |
— | a mesma tolerância para toda linguagem que não tem
a sua (TLMOD[<lang>.drift] vence). Vale no julgamento
e na conferência das soluções da calibração (uma good
dentro da tolerância não é "divergente") |
0 |
TLMOD[<lang>.sum] |
0 |
soma um valor fixo (em segundos) ao tempo-limite daquela linguagem | 405 (spim) |
TLMOD[<lang>.mult] |
1 |
multiplica o tempo-limite daquela linguagem | 0 |
ULIMITS[-u] |
1024 |
número máximo de processos. Java e outras runtimes precisam de mais
(o acervo usa 10000) |
453 |
ULIMITS[-s] |
131072 (128 MB, em KB) |
tamanho da pilha. Prefira STACKLIMITMB |
0 |
ULIMITS[-f] |
256000 |
tamanho máximo de arquivo que o programa pode escrever | 0 |
ALLOWPARALLELTEST |
ligado (ausente = y) |
y = o juiz pode rodar vários testes
desta submissão ao mesmo tempo, cada um nas suas k CPUs, quando tem CPU
ociosa (política do admin; em prova fica desligada); n = um
teste por vez. Não muda o tempo-limite: a calibração é
sempre um teste por vez. Ver "Problemas paralelos" abaixo |
453 |
STACKLIMITMB |
128 | pilha em MB. Vence o ULIMITS[-s]. A JVM espelha isso no
-Xss |
0 |
MEMLIMITMB |
sem limite por RSS | limite de memória em MB, medido pelo pico de RSS.
Ligar isso desliga o limite de memória virtual (que penalizaria
injustamente JVM e Go). A JVM usa este valor no -Xmx. A
unidade é MB: 256 MB é 256, não
262144. Acima do que um slot de juiz comporta (hoje ~9 GB),
cada teste ocupa mais slots e a submissão espera mais; acima da memória
de qualquer juiz, o problema não é julgável (Judge Error) — o Painel e a
aba Limites do editor avisam |
0 |
COMPILEMEMLIMIT |
2048 |
memória em MB liberada para a compilação (o
kotlinc passa de 600 MB) |
0 |
MAXPARALLELTESTS |
teto do juiz (4) | teto de testes ao mesmo tempo deste problema (inteiro ≥ 1); nunca
passa do teto do juiz (parallel_max, default 4) nem de
nproc/k rodando à mão |
0 |
CPUNEEDED |
1 |
CPUs que cada teste precisa (1..64; problema
paralelo — OpenMP/MPI/pthreads). O juiz junta k slots para cada teste e
a jaula entra com
MOJ_TEST_CPUS/OMP_NUM_THREADS = k. Mudar
recalibra. Ver "Problemas paralelos" |
0 |
SAMENUMA |
n |
com CPUNEEDED>1, y = as k CPUs de cada
teste no mesmo nó NUMA |
0 |
STOPWHEN_WA |
não para | y interrompe no primeiro Wrong Answer |
0 |
STOPWHEN_TLE |
não para | y interrompe no primeiro Time Limit Exceeded |
0 |
STOPWHEN_RE |
não para | y interrompe no primeiro Runtime Error |
0 |
Os três STOPWHEN_* e o número de testes vão ao json
servível (stop_when, tests). Em prova
ICPC só o primeiro erro importa, e a 🏁 Central do
contest avisa o problema que segue rodando depois dele (TCP 2026: um
problema com 214 testes e STOPWHEN_TLE=n levava até 4
minutos para dar TLE).
| TLERERUN | y | repete o teste uma vez
antes de confirmar um TLE (evita TLE por ruído da máquina) | 0 | |
CALIBRATIONTL | 5 | tempo-limite usado
durante a calibração, antes de existir um TL real | 0 |
| ALLOWTLEDURINGCALIBRATION | desligado | y
aceita solução good com TLE como "calibrou" (a linguagem
ganha TL mesmo estourando o CALIBRATIONTL — casos raros de
good deliberadamente no limite) | 0 | | SAMPLE | exemplos =
tests/input/sample* | no declara que o
problema não tem exemplos (seção 4, "Problema sem
exemplo"): o enunciado sai sem a caixa e nada é oferecido para baixar.
Não é lido pelo juiz e não entra no tl-checksum (não pede recalibração)
| 0 | | FUNCTION_LANGS | nenhuma | as linguagens de
submissão de função (FUNCTION_LANGS=c,py;
ids de linguagem de submissão: py, não py3).
Nelas o aluno envia SÓ a função, e o main vem do
scripts/<lang>/compile.sh. O editor do aluno (treino
e o módulo esqueletos do contest) abre
vazio nessas linguagens: o esqueleto de código tem
main, e o aluno levaria Compilation Error por main
duplicado. É declarada pelo autor: ter
scripts/<lang>/compile.sh não basta, porque o mesmo
slot serve para ban e OpenMP/MPI, em que o aluno escreve o programa
inteiro. O moj fn e o template "Submissão de função" do
editor web gravam a linha; no editor, é o campo Submissão de
função da aba Limites; na CLI, moj edit → 8 (conf)
→ 10. A validação reprova linguagem listada sem
scripts/<lang>/compile.sh e avisa driver fora da
lista. O json servível a leva como function_langs. Não é
lida pelo juiz e não entra no tl-checksum (não pede recalibração) | 0 |
| TLOVERRIDE[<lang>] /
TLOVERRIDE[default] | sem override | o autor decide
o TL na marra (segundos, por linguagem + default). A calibração
continua rodando (e o histórico dela fica visível), mas o valor FINAL —
no julgamento (o juiz aplica DEPOIS dos TLMOD, então ele
vence tudo) e em TODA exibição (treino, contest, folha de TL da prova,
/problems/tl) — é
TLOVERRIDE[lang] // TLOVERRIDE[default] // calibrado[lang].
Só valor numérico literal (TLOVERRIDE[java]=2.5); o
servidor lê por grep, nunca executa o conf. ⚠ Use
TLOVERRIDE[py], nunca
py3/py2 — são chaves LEGADAS: o
servidor as normaliza para py ao exibir e o juiz também
(desde 2026-08-24), mas antes disso um TLOVERRIDE[py3] era
EXIBIDO e não era JULGADO. A gestão de problemas
(Painel, editor, /problems/{get,status,calib,tl}) também
mostra o efetivo — com um selo ⚡ e o calibrado ao lado; os tempos dos
cartões de calibração seguem sendo a MEDIÇÃO, porque a calibração ignora
o override de propósito. ⚠ mudar o override muda o tl-checksum ⇒ dispara
uma recalibração (inofensiva — o override vence de qualquer jeito) | 0
|
A coluna "uso hoje" conta em quantos dos 453 conf do
acervo a chave aparece. Um zero não quer dizer que a chave não funciona:
quer dizer que o default serve para quase todo problema. Mexa só quando
tiver um motivo (um problema que exige muita memória, ou uma linguagem
que precisa de folga).
PUBLIC=no no conf é
legado. Hoje quem decide se o problema é público é o
campo public do .moj-meta.json.
CPUNEEDED, SAMENUMA) e testes em
paraleloDuas coisas diferentes com a mesma palavra:
| O que é | Chave | |
|---|---|---|
| teste paralelo | UM teste usa k CPUs ao mesmo tempo (o programa do aluno é paralelo) | CPUNEEDED=k, SAMENUMA=y |
| testes em paralelo | o juiz roda vários testes da mesma submissão ao mesmo tempo, cada um nas suas k CPUs | ALLOWPARALLELTEST, MAXPARALLELTESTS |
Os juízes oficiais são particionados em slots de 1
CPU. Um problema com CPUNEEDED=k:
SAMENUMA=y, k slots livres no mesmo nó); o
agente os junta num grupo, pina o teste nele (dentro da jaula
nproc = k) e os separa no fim;CPUNEEDED/SAMENUMA recalibra (o
conf entra no checksum);MOJ_TEST_CPUS e
OMP_NUM_THREADS (= k, exportados pelo
binfile.sh): OpenMP se dimensiona sozinho; o
run.sh de MPI faz mpirun -np "$MOJ_TEST_CPUS"
— nunca um -np fixo. Templates prontos:
paralelo-openmp e paralelo-mpi (seletor do
editor);SAMENUMA=y) o julgamento espera e, passado um tempo, recebe
Judge Error com o motivo — o Validar avisa antes.ALLOWPARALLELTEST ligado (o default) só diz que o juiz
pode rodar vários testes ao mesmo tempo quando tem CPU
ociosa (a política global do admin decide; em prova fica desligada).
Cada teste continua sozinho nas suas CPUs, o tempo é medido como sempre
e um TLE visto assim é refeito serialmente antes de valer;
MAXPARALLELTESTS é o teto por problema. O relatório da
submissão diz o que aconteceu: "Paralelismo: P teste(s) ao mesmo tempo ×
k CPU(s) por teste". A validação reprova valor inválido nas quatro
chaves. O json servível (var/jsons/<id>.json) carrega
cpu_needed e same_numa — é por ele que o
checklist pré-prova do contest (judges_cpus) avisa quando
nenhum juiz do pool tem as CPUs, sem abrir pacote. Guia completo:
mojtools/docs/problema-paralelo.md.
authorTexto livre, um autor por linha. É servido ao aluno
verbatim (as linhas são juntadas com
", "). Não separe por vírgula esperando que o sistema
divida: a vírgula já aparece dentro das linhas ("Fulano, adaptado por
Beltrano").
O arquivo é obrigatório: sem ele, a validação reprova.
tagsOs assuntos do problema, uma tag por linha, começando com
#, em minúsculas:
#grafos
#bfs
#matriz
As tags alimentam a busca do treino e o sorteio de problemas na criação de contest.
Tags são curadoria, não conteúdo julgável — e o
moj upload as trata assim: tar sem o
arquivo tags ⇒ o servidor preserva as que
já tem (um diretório montado à mão raramente traz o arquivo, e o
espelhamento as apagava em silêncio); tar com o arquivo
(mesmo vazio) ⇒ substitui. Apagar todas de propósito = enviar o arquivo
vazio (ou usar o editor web / moj push).
Dificuldade não é uma tag e não existe no pacote. Ela é calculada a partir da taxa de acerto real dos alunos (fácil se pelo menos metade acerta, difícil se menos de 20% acerta, desconhecida se ninguém tentou). Não adianta procurar um campo de dificuldade para preencher.
tl e tl.<host>Você não escreve estes arquivos. Eles são gerados pela calibração, no juiz. Ver a seção 10.
.moj-meta.json: os metadados do problemaÉ o metadado canônico do problema: o que não cabe em nenhum dos arquivos acima. Fica dentro do pacote e é commitado junto com ele.
Quem escreve é o servidor, sempre (função
write_meta, em server/api/v1/lib/problems.sh).
Nem o autor nem a CLI editam este arquivo à mão: eles mandam os campos
pela API, e o servidor grava.
No moj upload (o pacote sobe num tar), o
servidor separa os campos em dois grupos:
display_title,
collections e languages: vêm do
.moj-meta.json do tar (é o pacote que sabe como o
problema se chama e em que linguagens aceita submissão). Ausentes ou
vazios ([]) ⇒ o servidor preserva o que já
tinha — zerar a whitelist de linguagens é pelo
moj push/editor, nunca por omissão num tar.public,
public_at e owner: nunca vêm
do tar; só as rotas próprias os mudam (/problems/set-public
etc.). Se viessem, bastava baixar um problema público, adaptá-lo para
uma prova numa org privada e dar moj upload — a próxima
indexação publicaria a prova.A CLI fecha o círculo: no moj upload de um
diretório, ela sintetiza um .moj-meta.json
no tar a partir do .moj-id local
(título/coleções/languages) — um pacote de moj clone sobe
completo. (Um tar de moj download já traz o meta real do
servidor.)
Exemplo real (moj-problems/apc/seno/.moj-meta.json):
{
"public": true,
"collections": ["problemas-apc"],
"display_title": "Seno por série de Taylor",
"owner": "ribas.admin",
"gitea": { "owner": "ribas.admin", "repo": "apc" },
"languages": ["c", "cpp", "java", "py", "rs"]
}Campo a campo:
| Campo | Tipo | O que é |
|---|---|---|
display_title |
texto | O título do problema. É a fonte única. Se o autor
não mandar um título e o campo ainda não existir, o servidor
deriva um (do % do enunciado, do
#+title: do org, do \section{} do tex, ou, em
último caso, do nome do diretório). Por isso o campo nunca fica
vazio |
titles |
objeto {"en": texto, "es": texto} |
o título de cada tradução do enunciado (seção 4,
"Idiomas"). O servidor só guarda o idioma que tem
docs/enunciado.<lang>.md. Idioma sem título usa o
display_title. Na CLI é o campo titles do
.moj-id
(moj title --lang en "Hello World") |
owner |
login | o dono do problema |
public |
booleano | se true, o problema entra no treino livre. Publicar
exige que a org permita (seção 7) |
collections |
lista de textos | as coleções em que o problema está (seção 8). Pode estar em várias |
languages |
lista de ids | as linguagens de submissão permitidas neste
problema. Vazio ou ausente = todas as linguagens padrão. É o que permite
um problema só-PDDL, por exemplo. O servidor normaliza (minúsculas,
py2/py3 viram py,
cc/cxx/c++ viram
cpp, sem repetidos). A API REJEITA submissão fora
da lista (400 lang_not_allowed, no
/submit e no offline — não é só o filtro do dropdown),
essencial em problema de função/ban: sem isso, trocar a extensão burlava
o driver |
public_at |
epoch | quando o problema foi publicado pela primeira vez. Fica lá mesmo se despublicarem depois. Alimenta a estatística de entrada de problemas públicos |
migrated_at |
epoch | quando o problema veio de uma migração. Só informativo |
Dois campos são legado e não devem ser usados em código novo:
gitea.{owner,repo}: sobrou da época em que os pacotes
eram espelhados num Gitea. O Gitea foi removido. O campo continua nos
453 metas do acervo e ainda é lido em um único lugar, como alternativa
para descobrir o owner de pacotes antigos.collaborators: é lido em alguns pontos, mas
nunca é escrito, e está vazio em todo o acervo. No
modelo por org, colaborar em um problema é ser membro da
org (seção 7).Quem lê o .moj-meta.json: o
gen-problem-json.sh (para montar o índice do aluno), o
gen-problem-owners.sh (para montar o índice de donos), e a
API, ao devolver o problema ao editor e à CLI.
.moj-id: o
ponteiro local da CLIAtenção, porque este é o ponto que mais confunde:
.moj-id não faz parte do pacote. Repare
também que ele não tem extensão .json (não
existe nenhum arquivo .moj-id.json no MOJ), mesmo que o
conteúdo seja JSON.
Ele é criado pelo moj-cli, na sua
máquina, quando você roda moj clone ou
moj new. Serve para o clone local lembrar de qual problema
ele é, e para carregar os campos editáveis do metadado de ida e volta. O
moj push exclui este arquivo do que
sobe.
{ "id": "apc#seno", "repo": "apc", "prob": "seno", "title": "Seno por série de Taylor",
"format": "md", "collections": ["problemas-apc"], "public": true, "base_rev": "9f2c61d0a8b37e14" }| Campo | O que é |
|---|---|
id, repo, prob |
qual problema este diretório é
(<org>#<prob>) |
title |
espelho local do display_title. Editar aqui e dar
push muda o título no servidor. O push
recusa enviar com o título vazio |
titles |
espelho local do titles do meta: o título de cada
tradução ({"en": "Hello World"}).
moj title <dir> --lang en "…" edita |
trans_rt |
true em clone que conhece as
traduções: o push manda translations com todos
os idiomas, e idioma sem arquivo local vira null (apaga no
servidor). Clone antigo não apaga a tradução de ninguém |
format |
md, org ou tex, o formato do
enunciado deste clone |
collections, languages,
public |
espelhos locais dos campos do .moj-meta.json, com ida e
volta pelo push (e o moj upload de diretório
leva título/coleções/languages num meta sintetizado a
partir daqui; public nunca sobe).
moj languages <dir> edita a whitelist sem abrir o
arquivo |
scripts_rt |
marca que este clone sabe fazer ida e volta de scripts/
e tests/score. Sem essa marca, o push não tem
permissão de apagar esses arquivos no servidor (protege
clones antigos de destruir a correção especial sem querer) |
base_rev |
a revisão do servidor (rev) no último
clone, pull ou push desta pasta.
O push e o upload a mandam como
base_rev: se o problema mudou no servidor desde então
(editor web, outro autor), o servidor recusa com 409 e nada é gravado.
moj push --overwrite envia por cima. Vazio = pasta de antes
desta trava (o push grava por cima, como sempre) |
Ao lado do .moj-id a CLI grava o
.moj-base, a linha de base da
pasta: uma linha <hash>\t<caminho> para cada
arquivo do pacote (o conjunto que o push envia), mais uma
linha <hash>\t.moj-id com os campos de autoria do
.moj-id (título, títulos, linguagens, coleções). É ele que
o moj pull usa para saber o que você mudou
desde o último
clone/pull/push:
pull troca os arquivos do pacote (inclusive apaga os que
sumiram no servidor) e mantém os que não são do pacote (um
gerador.py, por exemplo);pull recusa e
lista os arquivos; moj pull --force copia a pasta inteira
para <pasta>.local-AAAAMMDD-HHMMSS e então traz a
versão do servidor;.moj-base (clonada antes do pull
existir): o pull compara com o servidor; se forem iguais,
só grava a linha de base; se não, recusa (não dá para saber quem mudou)
e sugere --force.O .moj-base também não sobe (nem no push,
nem no tar do moj upload).
Resumindo a diferença:
.moj-meta.json |
.moj-id |
|
|---|---|---|
| Onde vive | dentro do pacote, no servidor | no clone local do autor |
| Quem escreve | o servidor | o moj-cli |
| Vai para o servidor? | é o do servidor | não, é excluído do envio (e o
.moj-base também) |
| Para que serve | ser o metadado canônico | lembrar de qual problema é o diretório e levar os campos de ida e volta |
Os 336 .moj-id que aparecem hoje dentro de
moj-problems/ são resíduo de migrações
antigas que copiaram diretórios inteiros. O servidor os ignora.
Uma org é um grupo de acesso. Ela é a parte antes do
# no id do problema (apc#fatorial está na org
apc), e é ela que decide quem pode editar
o problema.
O registro fica em contests/treino/var/orgs.json, e o
código em server/api/v1/lib/orgs.sh.
{
"monitores": {
"created_by": "ribas.admin",
"title": "monitores",
"members": ["ribas.admin", "ryshim.admin"],
"admins": ["ribas.admin"],
"public_allowed": true,
"at": 1783051935
},
"ribas.admin": {
"created_by": "ribas.admin", "title": "ribas.admin",
"members": ["ribas.admin"], "admins": ["ribas.admin"],
"public_allowed": false, "implicit": true, "at": 1783515797
}
}| Campo | O que é |
|---|---|
members |
quem escreve nos problemas da org. Ser membro de uma org dá acesso de edição a todos os problemas dela |
admins |
quem gere os membros e mexe na trava
public_allowed |
public_allowed |
se false (o default),
nenhum problema da org pode ficar público |
implicit |
marca a org pessoal de um usuário (ver abaixo) |
created_by, title, at |
quem criou, rótulo de exibição, quando |
As regras que valem a pena guardar:
.admin vê o código-fonte, as soluções ou o pacote de um
problema de uma org de que não é membro.public_allowed: false). Isso é proposital: uma prova em
elaboração não pode escapar por acidente. Enquanto a trava estiver
fechada, publicar um problema da org retorna erro. Se um admin
rebaixar a org depois, os problemas públicos dela são
despublicados em cascata..admin.Um problema pode ser movido de org enquanto for
rascunho (moj mv, ou pelo editor). Isso muda o id, então o
MOJ recusa mover problema que já é público ou que já está em uso em
algum contest.
Rotas: /orgs/* em API.md. Pela
CLI: moj org list|create|members|public|rm e
moj share <org> <login>.
Uma coleção é um rótulo de agrupamento, e nada mais.
problemas-apc, obi2016,
obi2016-fase2-senior são coleções.
O registro fica em
contests/treino/var/collections.json:
{
"problemas-apc": { "owner": "ribas.admin", "created_by": "ribas.admin", "at": 1782519704 },
"obi2016-fase2-senior": { "owner": "ribas.admin", "created_by": "ribas.admin", "at": 1782927032 }
}O que um problema está em quais coleções, isso mora no
.moj-meta.json dele, no campo collections (uma
lista, porque um problema pode estar em várias coleções ao mesmo
tempo, e elas podem ser de orgs diferentes).
Pontos importantes:
"Maratona 2024, fase 1" é um nome válido). Ele nunca vira
caminho de arquivo nem id..admin) renomeia ou
apaga.Para que servem, na prática:
grafos,
dificuldade média", e o sistema sorteia (de forma reproduzível, a partir
de uma semente).Rotas: /problems/collection* em API.md. Pela CLI:
moj collection ls|show|create|add|remove|rename|delete.
Este é o par que mais gera confusão, então vale a tabela. Os dois são ortogonais: um problema tem exatamente uma org e pode ter várias coleções.
| ORG | COLEÇÃO | |
|---|---|---|
| Para que serve | acesso (quem edita, quem vê) | agrupamento (navegar, sortear) |
| Quantas por problema | exatamente uma | várias, ou nenhuma |
| Aparece no id? | sim, é o <org> de
<org>#<prob> |
não |
| Atravessa orgs? | não faz sentido | sim, uma coleção junta problemas de orgs diferentes |
| Tem membros? | sim (members, admins) |
não |
| Controla publicação? | sim (public_allowed) |
não |
| Onde é registrada | contests/treino/var/orgs.json |
contests/treino/var/collections.json |
| Onde o problema a declara | no próprio id | no .moj-meta.json, campo collections |
Em uma frase: a org diz quem manda no problema, a coleção diz onde ele aparece.
rascunho ──► pacote conferido ──► calibrado ──► PRONTO ──► público
(org privada) (botão Validar: (no juiz: TL, (nenhuma (treino livre)
estático) soluções, pendência,
entradas) nenhuma issue
aberta)
"Pronto" não é um passo que alguém executa: é o nome do estado em que todas as dimensões abaixo estão verdes. Publicar continua possível sem ele, mas pede confirmação (subseção "Publicação").
O problema nasce na sua org (a pessoal, se você não escolher outra). Ele é privado: ninguém além dos membros da org vê que ele existe.
Roda mojtools/validate-problem.sh, que grava um
relatório em run/validation/<id>.json. É uma
conferência estática do conteúdo do pacote: arquivos,
seções do enunciado, exemplos, testes emparelhados. Não roda
solução nenhuma. Quem roda as soluções é a calibração (abaixo).
Por isso a tela diz "Pacote", e não mais "Validado": o
nome antigo fazia o autor achar que as soluções estavam conferidas
(relato do Arthur Botelho, 22/09/2026).
Todas as checagens abaixo precisam passar (não existe checagem "opcional" que reprove pela metade):
| Checagem | O que exige |
|---|---|
has_author |
existe o arquivo author |
has_statement |
existe docs/enunciado.{md,org,tex} |
html_builds |
o pandoc consegue renderizar o enunciado |
secao_entrada |
o enunciado tem ## Entrada |
secao_saida |
o enunciado tem ## Saída (aceita Output e
Salida) |
html_builds_<lang>,
secao_entrada_<lang>,
secao_saida_<lang> |
o mesmo, para cada tradução
docs/enunciado.<lang>.md presente |
examples_present |
existe pelo menos um par input/output |
tests_paired |
todo input tem seu output, e vice-versa |
has_good_sol |
existe pelo menos uma solução em sols/good/ |
good_sol_accepts |
toda solução good é aceita |
Alguns avisos são informativos e não reprovam: LaTeX
vazando na prosa do enunciado, exemplo escrito à mão dentro do texto, e
checker commitado como binário (padrão antigo, deprecado: mande o fonte
scripts/checker.cpp e deixe a bridge compilar).
Sobre o good_sol_accepts: rodar as soluções exige um
sandbox de verdade, e o servidor não tem. A conferência do pacote
adia essa checagem para a calibração, que roda num juiz
real (o relatório diz "verificado na calibração (juiz)"). O resultado de
cada solução aparece na dimensão Soluções.
Se a validação passa, ela indexa o problema (chama o
gen-problem-json.sh), que gera o JSON que o aluno de fato
consome, com o enunciado já em HTML.
O tempo-limite não é escrito à mão no pacote. Ele é medido.
Um juiz baixa o pacote, roda cada solução de sols/good/,
pega o pior tempo de cada linguagem, multiplica pelo
TLMOD[calibrafactor] (1.35 por padrão) e reporta o
resultado para o servidor. O resultado fica em
run/tl/<id>.json, guardado por
máquina:
{ "id": "apc#ajude_simplificado", "checksum": "df7f628e84bfc6c3", "updated_at": 1783534737,
"hosts": {
"cpu1": { "tl": { "c": ".0335", "cpp": ".0335", "java": ".3710", "py": ".1685",
"default": ".0335" }, "at": 1783534737 },
"cpu2": { "tl": { "…": "…" }, "at": 1783534733 } } }O tempo-limite servido ao aluno é o maior
entre as máquinas, para que a submissão não seja reprovada por
ter caído num juiz mais lento. Uma linguagem só ganha tempo-limite se
alguma solução good naquela linguagem foi
aceita em algum juiz. Sem tempo-limite, a linguagem não
fica disponível.
A calibração roda um teste por vez e, num problema
paralelo, cada teste com as k CPUs do
CPUNEEDED — exatamente a forma em que o julgamento roda
cada teste (por isso o TL de k=2 não vale para k=4 e mudar a chave
recalibra).
O "Calibrar" explícito (editor, moj calibrate, publicar)
roda todas as soluções. A calibração sob demanda, que
um juiz faz sozinho na 1ª submissão de um pacote novo, roda só
as good (é rápida de propósito): depois dela, as
outras categorias aparecem "sem resultado".
A calibração devolve, por juiz e por solução, o código de
cada teste (AC, WA,
TLE, MLE, RE, UE). O
servidor compara com a categoria
(server/api/v1/lib/calib-expect.sh, a fonte única; o
editor, o Painel e a CLI só mostram o resultado) e dá um de quatro
estados:
| Estado | Quando |
|---|---|
| ✓ conforme | a solução fez exatamente o que a categoria pede (tabela da seção
sols/) |
| ≈ conforme, outro motivo | fez o que a categoria pede, mas não do jeito típico:
wrong reprovada só por TLE/MLE/RE (sem WA);
slow com TLE, mas também com WA/RE em outros testes;
good com TLE e
ALLOWTLEDURINGCALIBRATION=y |
| ✗ divergente | não fez: good/pass reprovada ou
mais lenta que o tempo-limite efetivo (ex.:
TLOVERRIDE abaixo do tempo medido — no julgamento ela
tomaria TLE); slow sem TLE; wrong aceita |
| ✗ não rodou | CE, UE (erro do corretor/juiz), linguagem indisponível no juiz, ou sem veredicto: a solução não exercitou os testes, então não prova nada |
Duas regras que mudaram em 22/09/2026 (antes o juízo era só da tela e olhava a string do veredicto):
slow assim é ≈, e uma
wrong assim é ✓ (tem WA);wrong que não compila era "ok"
(não foi aceita). Hoje é ✗ não rodou.Em problema pontuado (tests/score) a string traz o
veredicto do pior teste e a nota dos grupos
(Time Limit Exceeded,30p. Pontos | …; até 24/09/2026 era
sempre Wrong,Np). De qualquer jeito o juízo olha os testes:
uma slow com TLE é ✓.
O resultado vale para a versão do pacote que foi
calibrada. Salvar algo que a calibração exercita (sols/,
tests/, scripts/, conf) marca as
soluções como "não conferidas desde a última edição"
até a próxima calibração. Salvar o enunciado não marca.
São DOIS carimbos, calculados pelo mesmo
tl-checksum.sh, porque as duas perguntas são diferentes:
"o tempo-limite medido ainda vale?" e "o juiz ainda tem o
pacote certo em cache?".
tl_checksum (estreito) |
pkg_version (largo) |
|
|---|---|---|
| Como se calcula | tl-checksum.sh <pkg> |
tl-checksum.sh --all-sols <pkg> |
| Cobre | conf (menos a linha SAMPLE),
tests/input/*, tests/output/* (não-vazios),
tests/score, sols/good/*,
scripts/* (conteúdo e bit de execução)
menos scripts/validator.cpp |
tudo o que o estreito cobre + sols/pass,
sols/slow, sols/wrong,
sols/upcoming +
scripts/validator.cpp |
| Para que serve | amarra o TL ao pacote: é o checksum de
run/tl/<id>.json, o do índice de donos e o que o
/contest/problems compara |
é a chave do cache do juiz e a identidade de uma
calibração: /judge/package-meta o devolve como
checksum e o agente re-baixa quando muda |
Nenhum dos dois cobre docs/enunciado.*,
tags, author nem o .moj-meta.json
(título/coleções/tags).
tests/output/*etests/scoreentraram no checksum em 2026-07-19: sem eles, um gabarito ou uma pontuação corrigida nunca chegava ao juiz (o cache do problema não invalidava).A separação em dois carimbos é de 2026-09-20 (relato do Arthur Botelho). Antes havia só o estreito, e ele fazia os dois papéis: mexer numa solução
pass/slow/wrongnão mudava a chave, então o juiz recalibrava osols/do cache velho — julgando solução que o autor já tinha apagado, ignorando a que ele acabou de escrever, e cada juiz com um conjunto diferente sob o mesmo checksum. Alargar o carimbo estreito não serve: ele também é o que diz se o TL vale, e o TL sumiria da prova a cada solução salva.
Se o tl_checksum do pacote deixa de bater com o
guardado, o TL é considerado velho e some (o problema
passa a aparecer como "precisa recalibrar"). Ou seja: corrigir
um typo no enunciado não força recalibração; trocar um teste, uma
solução good, o conf ou um script
força. Salvar uma solução
pass/slow/wrong
não invalida o TL, mas manda o juiz buscar o pacote
novo — é exatamente o que o "Calibrar" precisa para rodar o que você
acabou de salvar.
Se o pacote tem scripts/validator.cpp (seção
scripts/), a calibração completa o roda no juiz, antes das
soluções, sobre cada tests/input/*: a testlib reprova a
entrada que foge do formato ou dos limites, com uma mensagem que diz
onde
(FAIL Integer parameter [name=N] equals to 1296, violates the range [1, 1000]).
O resultado aparece no cartão de cada juiz (linha
Entradas), no Painel e no
moj check/moj calib. Entrada inválida, ou
validador que não rodou (não compilou, passou de 5 s numa entrada ou de
60 s no total), deixa o problema não pronto. Pacote sem
validador não é pendência — só aparece como "sem validador". A
calibração rápida da 1ª submissão não roda o validador.
O problema está pronto quando o
/problems/status não tem nenhuma pendência
(pending):
| Pendência | Significa |
|---|---|
package_failed / package_unchecked |
a conferência do pacote reprovou / nunca rodou (botão Validar) |
uncalibrated / needs_recalibration |
sem calibração / o pacote mudou desde a calibração |
good_no_tl:<langs> |
solução good sem tempo-limite nessas linguagens (falhou
em todos os juízes) |
sols_divergent:<n> |
n soluções divergentes ou que não rodaram |
sols_unchecked |
há solução sem resultado (calibração rápida) ou o pacote mudou desde a calibração |
inputs_invalid:<n> /
inputs_error |
o validador de entrada (scripts/validator.cpp, subseção
"Entradas") reprovou n testes / não rodou |
issues_open:<n> |
n issues abertas (subseção "Issues") |
O editor mostra o selo "✓ Pronto" ou "N pendências" na barra de cima.
O Painel tem o card "prontos" e a coluna Soluções.
moj check diz "pronto: SIM" ou lista as pendências.
A revisão da banca fica em issues por problema:
qualquer membro da org abre uma issue ("o teste 7 está fora do limite do
enunciado", "o TL do Python está apertado"), comenta e fecha. Enquanto
houver issue aberta, o problema não está pronto. Web: aba 🐞
Issues do editor (o Painel mostra 🐞N com link); CLI:
moj issues. As issues não fazem parte do
pacote: ficam no servidor
(contests/treino/var/problem-issues/), então não mudam o
rev, não somem num moj upload e não vão ao
juiz. Mover o problema de org leva as issues; apagar o problema as
apaga.
Publicar (moj publish, ou o botão no editor) faz o
servidor conferir o pacote e calibrar. O problema entra
no treino livre quando a conferência do pacote passa (é ela que gera o
enunciado servido). E, antes de tudo isso, a org
precisa ter public_allowed: true (seção 7).
Publicar um problema que ainda não está pronto pede
confirmação com a lista de pendências (no editor, no Painel e
no moj publish/moj public on;
--yes só mostra a lista e segue). Nada
bloqueia a publicação: é decisão de quem publica.
Onde eu ponho o título? No campo
display_title do .moj-meta.json, e na prática
você o edita pelo editor web ou pelo campo title do
.moj-id (a CLI). Nunca no texto do enunciado.
Como eu escrevo o tempo-limite? Você não escreve.
Ele é medido pela calibração. O que você pode ajustar é a
folga, pelo TLMOD[calibrafactor] no
conf.
Quero que o problema só aceite Python. Ponha
["py"] no campo languages do
.moj-meta.json — pelo editor web, por
moj languages <dir> py + moj push, ou
editando o .moj-id.
Meu problema tem várias respostas certas. Você
precisa de um checker: scripts/compare.sh. Ver
mojtools/docs/correcao-especial.md e o guia de testlib em
mojtools/docs/checker-testlib.md.
O aluno vai entregar só uma função, não o programa
inteiro. É a submissão de função:
scripts/<lang>/compile.sh. Mesmo guia.
Editei o enunciado. Preciso recalibrar? Não. O enunciado não entra no checksum. Tradução também não.
Como traduzo um problema? Crie
docs/enunciado.en.md (ou .es.md) ao lado do
docs/enunciado.md. Traduza a explicação de cada exemplo em
docs/notes/<sample>.en.md e o editorial em
docs/solucao.en.md. Dê o título com
moj title . --lang en "Hello World". No editor web, use os
chips PT · EN · ES da aba Enunciado. O português continua
obrigatório.
Onde fica a dificuldade do problema? Em lugar nenhum do pacote. Ela é calculada da taxa de acerto real dos alunos.
Meu problema é de função (ou interativo). Mostrar a entrada
não faz sentido. Não crie sample*, ponha
SAMPLE=no no conf (no editor web: aba Limites,
"este problema não tem exemplos") e explique o exemplo no texto do
enunciado, numa seção ## Exemplo. Ver seção 4, "Problema
sem exemplo".
Editei na web. Como trago para a minha pasta? Rode
moj pull dentro da pasta do problema. Se a pasta tem
mudanças suas que não foram enviadas, o pull recusa. Envie
antes (moj push) ou rode moj pull --force, que
guarda a sua pasta numa cópia.
O moj push disse que o problema mudou no
servidor. Alguém salvou o problema (web ou outro clone) depois
do seu último clone/pull/push.
Nada foi enviado. Rode moj pull --force para trazer a
versão nova e reaplique as suas mudanças a partir da cópia
.local-*, ou rode moj push --overwrite para
enviar a sua versão por cima. O editor web tem a mesma trava: ele avisa
quem mudou e oferece "Recarregar" ou "Salvar por cima".
Qual é a diferença entre .moj-meta.json e
.moj-id? Ver a tabela no fim da seção 6. Em uma
frase: o primeiro é o metadado do servidor, o segundo é um bilhete que a
CLI deixa no seu diretório local e que nunca sobe.
mojtools/README.md.mojtools/docs/correcao-especial.md,
mojtools/docs/checker-testlib.md,
mojtools/docs/problema-interativo.md.moj):
moj-cli/README.md.