-
Notifications
You must be signed in to change notification settings - Fork 5
Challenge specifications
This section gives detailed specifications of each of the four challenges.
Your TIL-AI 2026 mission consists of four challenges across distinct fields of study in artificial intelligence: automatic speech recognition (ASR), computer vision (CV), natural language processing (NLP), and autonomous exploration (AE).
This document provides the technical specifications for the four challenges. It aims to rigorously define the design and requirements, not to be a first-time guide to the TIL-AI mission for the uninitiated. For a non-technical introduction, take a look at the Opening Ceremony slides.
A copy of the input and output formats is provided in the README.md file in each model's directory in the til-ai/til-26 template repository. These copies are provided for your convenience; though if there is a conflict in meaning between the copy in README.md and the specifications in this document, this document shall take precedence.
Your overall score is calculated by a weighted sum of the scores for each challenge. The weights are as follows:
| Challenge | Weight |
|---|---|
| ASR | 20% |
| CV | 20% |
| NLP | 20% |
| AE | 40% |
Each challenge's score is calculated by a weighted sum of accuracy/reward and inference speed score:
| Challenge | Accuracy/reward weight | Speed score weight |
|---|---|---|
| ASR | 75% | 25% |
| CV | 75% | 25% |
| NLP | 75% | 25% |
| AE | 75% | 25% |
The speed score is calculated by:
where
In short, the less time your model takes to generate its predictions, the better your speed score. A perfect score requires instantaneous inference.
Your automatic speech recognition challenge is to transcribe a noisy recording of speech. Given an audio file, your model is expected to produce a text transcript of what was spoken.
In each audio file, there is only one speaker. Note that many of the words used within each audio file are not necessarily normal dictionary words. The generated audio is all set in the fictional world of the NLP RAG corpus, which means it will include various slang terms and jargon relevant to that world.
For the Novice track, all speech will be in English, but the speaker may speak with a variety of accents.
For the Advanced track, all speech will be in one of four languages in roughly even proportion across the dataset:
- English
- Malay
- Tamil
- Chinese
Fictional slang terms will tend to be, but not necessarily, be in English, regardless of the language of the rest of the audio. Thus, non-English language clips are primarily non-English, but with some English words scattered in. In addition, the input audio for the Advanced track will contain more noise than that for Novice.
The accuracy score of your ASR model on English, Tamil, and Malay, is calculated by
jiwer.Compose([
jiwer.ToLowerCase(),
jiwer.SubstituteRegexes({"-": " ", "—": " ", "–": " "}),
jiwer.RemoveMultipleSpaces(),
jiwer.RemovePunctuation(),
jiwer.Strip(),
jiwer.ReduceToListOfListOfWords(),
])As for Chinese, it is calculated by
jiwer.Compose([
jiwer.ToLowerCase(),
jiwer.SubstituteRegexes({"-": "", "—": "", "–": ""}),
jiwer.RemoveWhiteSpace(replace_by_space=False),
jiwer.RemovePunctuation(),
jiwer.ReduceToListOfListOfChars(),
])The individual error rate of each language is then averaged to get your final accuracy score.
Input and output formats
Input
The input is sent via a POST request to the /asr route on port 5001. It is a JSON document structured as such:
{
"instances": [
{
"key": 0,
"b64": "BASE64_ENCODED_AUDIO"
},
...
]
}The b64 key of each object in the instances list contains the base64-encoded bytes of the input audio in WAV format. The length of the instances list is variable.
Output
Your route handler function must return a dict with this structure:
{
"predictions": [
"Predicted transcript one.",
"Predicted transcript two.",
...
]
}where each string in predictions is the predicted ASR transcription for the corresponding audio file.
The predictions must be the prediction corresponding to the instances for all predictions must equal that of instances.
Your CV challenge is to detect and classify objects in an image. Given an image, your model is expected to produce the bounding box and predicted category of every occurrence of an object belonging to a category in the target list. Each scene may contain zero or more targets.
Your output is expected to contain the following values:
-
$(l, t)$ : The coordinates in pixels of the top-left corner of the predicted bounding box.$l$ is the horizontal coordinate and$t$ Is the vertical. -
$(w, h)$ : The width and height in pixels of the predicted bounding box -
category_id: The index of the predicted category in the target list
All bounding box coordinates should be zero-indexed; that is,
NOTE: this is known as an LTWH bounding box. Various CV systems (e.g. YOLO), output subtly different bounding boxes (e.g. XYWH), that need to be converted into this format. e.g.
from ultralytics.utils.ops import xywh2ltwh
from ultralytics import YOLO
model = YOLO("./best.pt")
result = model.predict(...)
boxes = xywh2ltwh(result.boxes.xywh.cpu().numpy()).tolist()| Category index | Object type |
|---|---|
| 0 | cargo aircraft |
| 1 | commercial aircraft |
| 2 | drone |
| 3 | fighter jet |
| 4 | fighter plane |
| 5 | helicopter |
| 6 | light aircraft |
| 7 | missile |
| 8 | truck |
| 9 | car |
| 10 | tank |
| 11 | bus |
| 12 | van |
| 13 | cargo ship |
| 14 | yacht |
| 15 | cruise ship |
| 16 | warship |
| 17 | sailboat |
The input images for the Advanced track will contain more noise than those for the Novice track. The targets in the Advanced track input images will also be smaller in size than those for the Novice track.
The accuracy score is calculated by mean average precision over IoU thresholds ranging from 0.5 to 0.95 with a stride of 0.05, otherwise known as mAP@.5:.05:.95. See here for details.
Input and output formats
Input
The input is sent via a POST request to the /cv route on port 5002. It is a JSON document structured as such:
{
"instances": [
{
"key": 0,
"b64": "BASE64_ENCODED_IMAGE"
},
...
]
}The b64 key of each object in the instances list contains the base64-encoded bytes of the input image in JPEG format. The length of the instances list is variable.
Output
Your route handler function must return a dict with this structure:
{
"predictions": [
[
{
"bbox": [l, t, w, h],
"category_id": category_id
},
...
],
...
]
}where l, t, w, h, and category_id are defined as above.
If your model detects no objects in a scene, your handler should output an empty list for that scene.
The predictions must be the prediction corresponding to the instances for all predictions must equal that of instances.
An additional component to the CV challenge is for your team to adversarially noise images for your competitors to use as inputs to their CV models.
During the Finals, when an opposing team is meant to receive a CV challenge, you may get the chance to add noise to their image before they process it. You thus get the chance to disrupt their CV performance.
None.
There is an evaluator that will determine whether the noise you have added is within acceptable limits. It uses the following criteria:
- SSIM
- RMSE L2 norm
You will not be directly rewarded for the noise you create in the Qualifiers. However, by making the CV images of your opponents harder, you will be indirectly rewarded for it in the Finals. In turn, you will have to make your CV model robust to whatever your opponents may throw at you.
Input and output formats
Input
The input is sent via a POST request to the /noise route on port 5003. It is a JSON document structured as such:
{
"instances": [
{
"key": 0,
"b64": "BASE64_ENCODED_IMAGE"
},
...
]
}The b64 key of each object in the instances list contains the base64-encoded bytes of the input image in JPEG format. The length of the instances list is variable.
Output
Your route handler function must return a dict with this structure:
{
"predictions": [
"BASE_64_ENCODED_IMAGE",
...
]
}where each string in predictions is your adversarially noised version of the corresponding input image.
The predictions must be the prediction corresponding to the instances for all predictions must equal that of instances.
Your NLP challenge is to answer questions with the information found within a large corpus of documents. You will first be given the corpus, and then given batches of questions to answer.
The documents are set in the fictional world of Clairos, a futuristic cyberpunk world just shy of a hundred ty. Following the Cascade — a great flood caused by melting polar ice caps and rising sea levels — and the subsequent collapse of the majority of the world's governments, megacorporations fill the void left in their wake, vying for societal and economic control of Haven, the last great city on Clairos.
Yes, we semi-gratuitously came up with an entire fictional world just for this. If you're curious about the details, we talk a little more about it here.
In the nlp directory of your track's data directory, you have the following:
- the
documents/directory contains all the plain-text documents associated with thetraincorpus. - the
nlp.jsonlfile contains question-answer pairs and some useful associated metadata. -
models/nlp_eval.zipcontains the model weights for the answer equivalence model used for evaluation, see Scoring.
Questions are categorized into 5 levels, and those for the Advanced track will be more challenging (L3 onwards are exclusive to the Advanced track).
| Level | Name | What it tests |
|---|---|---|
| L1 | Direct extraction | Single fact stated verbatim in the document |
| L2 | Inference / combination | Requires combining 2–3 facts, or simple arithmetic, or recognising what the doc does not state |
| L3 | Cross-document | Requires facts from two or more documents |
| L4 & L5 | Unanswerable | Question references content that does not exist in the corpus, or consist a premise that contradicts the corpus; RAG models are expected to return an empty string |
First, the top 3 returned document IDs are checked for overlap with the document within which the information is contained. If none of the documents match the target document id, retrieval is considered to be unsuccessful, and a score of 0 is assigned for the test case.
Correctness of generated answers is verified using a procedure adapted from Bulian et al., 2022. In brief, answerable questions are evaluated using a version of ModernBERT-base fine-tuned for determining answer equivalence from (question, reference, candidate) triples. Candidate answers are first pre-processed, removing all non-printable characters, and then truncating them to a maximum length of 64 tokens. The answer is marked correct if the model returns a probability of answer equivalence above the set threshold (0.9).
If retrieval is successful but the submitted candidate answer is not considered equivalent to the reference, you are given 0.4 score (i.e. 40%) for successful retrieval.
For Advanced teams, there are also three additional categories of questions:
- L3 questions are answerable, and require combining information from multiple documents. If any of the required documents are present in the returned
documentslist, that is counted as a successful retrieval. - L4 questions are unanswerable, and have no corpus reference. As such, an empty
answerstring and empty list ofdocumentsis expected to be returned. - L5 questions are unanswerable, but have a corpus reference from which a false premise is drawn. As such, an empty
answerstring and a non-empty list ofdocumentsis expected to be returned.
If an empty answer string or documents list is returned when not appropriate, or a non-empty answer string or documents list returned when an empty one is expected, those cases will be scored as wrong.
See the implementation in the test_nlp.py script for details.
Input and output formats
Corpus Setup
To load the test corpus, the first request sent to your endpoint will be of the following structure:
{
"instances": [
{
"documents": [
{"id": "DOC-0001", "document": "Text of document one."},
{"id": "DOC-0002", "document": "Text of document two."},
...
]
}
]
}This is expected to be parsed by your NLP RAG QA system to be used as context for RAG. You can thus do your embedding/chunking/etc on this data. Once your model has completed processing it, return the following:
{
"predictions": [{"status": "loaded"}]
}This will be taken as the signal that your system is ready to move on to receiving input.
Input
The input is sent via a POST request to the /nlp route on port 5004. It is a JSON document structured as such:
{
"instances": [
{
"question": "QUESTION_TEXT"
},
...
]
}The question key of each object in the instances list contains the text of the question to be answered by your NLP RAG QA system. The length of the instances list is variable.
Output
Your route handler function must return a dict with this structure:
{
"predictions": [
{"documents": ["DOC-0001"], "answer": "Answer one."},
{"documents": ["DOC-0001"], "answer": "Answer two."},
...
]
}where each item in predictions corresponds to the relevant document IDs and the predicted NLP answer for the corresponding question.
The predictions must be the prediction corresponding to the instances for all predictions must equal that of instances.
Your AE challenge is to direct your agent through the game map while interacting with other agents and completing challenges.
Given an observation, your model is expected to produce the next action to take in order to complete its objective.
This section provides a technical overview of the TIL-AI 2026 mission gameplay, and assumes you are already familiar with the concept. Otherwise, take a look at the Opening Ceremony slides.
Each team's AE agent navigates a maze-like game map in a high-stakes wargame scenario. The map is structured as a 16x16 grid, to which agents' moves are discretized.
Each match is played by six teams, and consists of just one round. Each team has one robot in the environment, with
The game takes place over discrete time steps. All agents move in step, and at the same rate. The game ends when 200 time steps elapse. For additional details on the AE challenge specifically, see the dedicated AE challenge page.
Note
During Qualifiers, each challenge (ASR, CV, NLP, and AE) is evaluated and scored separately. The AE agent will continue to receive rewards for activating challenges, but no challenge is actually sent to the ASR, CV, or NLP models, and their performance has no effect on the AE score.
The container containing your trained AE agent is likely to be independent of the environment, and thus can have a more limited set of requirements/packages installed solely for inference.
For the Novice track, the map used in the environment will be held fixed.
For the Advanced track, the map used in the environment will vary for each match, requiring participant's agents to be robust and able to adapt to many novel environments.
For the automated evaluation, the score returned will be a sum of all rewards attained by the agent during evaluation, divided by the number of rounds, then divided by a max score of 1000.
Input and output formats
The input is sent via a POST request to the /ae route on port 5005. It is a JSON object structured as such:
{
"instances": [
{
"observation": {
"agent_viewcone": [[[0, ...], ...], ...],
"base_viewcone": [[[0, ...], ...], ...],
"direction": 0,
"location": [0, 0],
"base_location": [0, 0],
"health": [60.0],
"frozen_ticks": 0,
"base_health": [100.0],
"team_resources": [0.0],
"team_bombs": 0,
"step": 0,
"action_mask": [1, 1, 1, 1, 1, 0]
}
}
]
}agent_viewcone is a 7 × 5 × 25 float32 array (rows × cols × channels) oriented relative to the agent's facing direction. base_viewcone is a 5 × 5 × 25 float32 array centered on the team base.
The observation is a representation of the inputs the agent senses in its environment. See the observation space specifications to learn how to interpret the observation.
The length of the instances array is 1.
During evaluation for Qualifiers, a GET request will be sent to the /reset route to signal that a round has ended, all agents are being reset to their starting positions (possibly with new roles), and any persistent state information your code may have stored must be cleared.
Your route handler function must return a dict with this structure:
{
"predictions": [
{
"action": 0
}
]
}The action is an integer representing the next movement your agent intends to take. See the action space specifications for a list of possible movements.
BrainHack Website · Strategist's Handbook · Access to VM Instances · Curriculum (Google Drive | GitHub) · #hackoverflow
Basics
- Getting started
- Building Docker images
- Testing models
- Submitting models
- Challenge specifications
- AE with
til_environment
Troubleshooting
Diving deeper
Finals