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.
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 (também aceita .org e .tex) 453 (obrigatório)
│ ├── sample-notes.json explicação de cada exemplo opcional
│ └── solucao.md editorial, só para o autor 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
└── <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.
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 e
## Output, 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.)Imagens devem ser embutidas em base64. O renderizador roda com
--embed-resources, então o HTML servido ao aluno é
autocontido, sem depender de arquivo externo.
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/sample-notes.jsonOpcional. Um array JSON de textos, um por exemplo, na ordem dos exemplos:
["No primeiro exemplo, os dois times empatam, então a saída é 0.",
"Aqui o segundo time vence por 3 pontos."]Cada nota é renderizada em markdown e aparece logo abaixo do exemplo correspondente.
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.
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).
É preciso ter pelo menos um par de teste, e na prática
pelo menos um exemplo.
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).Quem interpreta é o mojtools/score-summary.sh, no
juiz.
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).
| Diretório | O que é | Para que serve |
|---|---|---|
good/ |
soluções corretas | obrigatório, pelo menos uma. É o que a calibração roda para descobrir o tempo-limite, e o que a validação exige que seja aceito |
wrong/ |
soluções erradas de propósito | conferir que os testes pegam o erro |
slow/ |
soluções lentas de propósito | conferir que o tempo-limite realmente reprova a solução ruim |
pass/ |
soluções que devem passar raspando | conferir que o tempo-limite não é apertado demais |
upcoming/ |
rascunhos | não entram na conferência |
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).
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 |
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) |
— |
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).
O guia completo (submissão de função, proibir funções da biblioteca,
checker com testlib, problema interativo) está em
mojtools/docs/correcao-especial.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:
| 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 de variação de tempo naquela linguagem antes de dar TLE | 404 (java) |
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 | n força os testes a rodarem um de cada vez (necessário
quando o problema é sensível a tempo) |
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 |
0 |
COMPILEMEMLIMIT |
2048 |
memória em MB liberada para a compilação (o
kotlinc passa de 600 MB) |
0 |
MAXPARALLELTESTS |
nº de CPUs | teto de testes em paralelo | 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 |
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 |
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.
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.
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 e
collections: vêm do .moj-meta.json do
tar (é o pacote que sabe como o problema se chama). Ausentes ⇒
o servidor preserva o que já tinha.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.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 |
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, sem
repetidos) |
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 }| 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 |
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 |
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) |
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 |
| 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 ──► validação ──► calibração ──► público
(org privada) (portão) (nos juízes) (treino livre)
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.
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 |
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. Na máquina de desenvolvimento o bwrap é
um no-op (fbwrap), então a validação adia
essa checagem para a calibração, que roda num juiz real. Isso não é
bug.
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.
O campo checksum acima é o que amarra o TL ao pacote.
Ele é calculado pelo tl-checksum.sh e cobre só o
que pode mudar o tempo de execução:
| Entra no checksum | Não entra |
|---|---|
conf |
docs/enunciado.* |
tests/input/* |
tags |
sols/good/* |
author |
scripts/* (conteúdo e bit de
execução) |
tests/output/* |
Se o 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.
Publicar (moj publish, ou o botão no editor) faz o
servidor validar e calibrar. O problema só entra no
treino livre se os dois passarem. E, antes de tudo isso, a
org precisa ter public_allowed: true
(seção 7).
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 ou pelo
.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.
Onde fica a dificuldade do problema? Em lugar nenhum do pacote. Ela é calculada da taxa de acerto real dos alunos.
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.