Final Lap | GSoC 2025
Final GSoC 2025 Update | Wrapping Up My GCompris Contribution
TL;DR
📍 Before Week 6: Built Editor for Learn Decimals Activity and its sub activities which are Learn Decimal Addition and Subtraction
- 🚀 Weeks 6–12:
- Built editors for Graduated Line Read and Use, pending improvements.
- Restructured datasets to make them compatible with the editor.
- Added initial validation (range checks), pending valid targetnumber check.
- ⚡ Challenges: Hit roadblocks with
FieldEdit.qml
(string-only input). Solved by creating multiple input fields instead of reusing one. - 🎯 Learnings: Start with a minimal working editor, iterate quickly, and get feedback early rather than overthinking one solution.
- ⏭️ Next Up:
- Add a number-array input component.
- Implement dataset validation inside the editor.
- work on next activities Share and Fractions create and use
August is winding down, and so is my GSoC 2025 journey with KDE. From setting the foundations during community bonding to overcoming mid-term challenges, these final weeks have been all about refining, polishing, and delivering meaningful improvements to the GCompris educational suite.
In this final blog, I’ll walk you through the last leg of the sprint: the features I completed, deeper lessons I learned, the unexpected hurdles I navigated, and what’s next—both for the project and for me :)
What I Built
- Dataset Restructure: Created a cleaner dataset format to support both random and fixed question generation.
- Fixed Question Support: Teachers can now set target numbers (e.g., “Find 13 on the ruler”) instead of relying only on random questions.
- Validation & Fallbacks: Added constraints to prevent invalid datasets and ensure smooth fallback behavior.
- Dynamic Titles: Teachers can write custom titles for exercises.
- Graduated Lines read and Use Editor: Built a basic dataset editor UI for creation of exercises for both the activities.
Let’s talk about the core logic flow first
Mode-Specific Behavior
The activity supports two interaction modes:
tick2number Mode
- Question: “What number is at this position?”
- Behavior: Cursor placed at target position, user types the answer
number2tick Mode
- Question: “Where is number X?”
- Behavior: User navigates cursor to find the target number
Code Changes Analysis
Before vs After Comparison
updated createLevel() Function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createLevel() {
exercices = [];
if (levelRules.dataType === "fixed") {
// Create single targeted exercise
var exo = createExercice(0, levelRules, 0);
exercices.push(exo);
return;
}else{
for(var s = 0; s < levelRules.steps.length; s++) {
for(var i = 0; i < nbTicks; i++) {
// Generate all possible random exercises
//basically loop from i=0 to total possible ticks and use it
}
}
}
}
createExercise ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
function createExercise(idx, rules, s) {
...
// we need to handle logic differently for dataType =fixed
if(rules.dataType==="fixed"){
// for fixed datatype , fitlimits is always set to true
nbSeg=Math.floor((rules.rangeMax-rules.rangeMin)/step)+1 // 0-3 , step 1 4 0-1-2-3
var start =rules.rangeMin
//get target number and adjust it if needed
var originalTarget= rules.targetNumber
var adjustedTarget= findNearestLeftTarget(originalTarget,start,step)
//ensure adjusted target is within ruler bounds
// that is adjusted target number > start and lies between rangeMin and rangeMax
var maxRulerValue=start+ ((nbSeg-1)*step)
if(adjustedTarget>maxRulerValue){
adjustedTarget=findNearestLeftTarget( maxRulerValue,start,step)
}
var targetPosition =(adjustedTarget-start)/step // 0 -10 step is 1 target:6 6-0/1 =6 index starts at 0->0
if(targetPosition===0&& nbSeg>2){
targetPosition=1 // skip start and end
adjustedTarget=start +step //move the target to second min if its < start
} else if(targetPosition===nbSeg-1&& nbSeg>2){
targetPosition=nbSeg-2
adjustedTarget=start +((nbSeg-2)*step) // move the target to second max , if it exits ruler bounds
}
return {
"solution":targetPosition,
"step":step,
"nbSeg":nbSeg,
"start": start,
"rangeMin": rules.rangeMin,
"rangeMax": rules.rangeMax,
"isFixed": true,
"originalTarget": originalTarget,
"adjustedTarget": adjustedTarget
}
}else if (rules.dataType==="random"){
// when datatype is random i.e select random index as questions
if (rules.fitLimits) {
nbSeg = Math.floor((rules.rangeMax - rules.rangeMin) / step) + 1 //10-1 /3 =3+1 =4
} else {
nbSeg = 1 + rules.displayedRangeLengthMin + randInt(1 + rules.displayedRangeLengthMax - rules.displayedRangeLengthMin)
maxOffset = rules.rangeMax - (nbSeg * step)
while (maxOffset < 0) { // if data is not valid, reduce the number of segment
nbSeg--
maxOffset = rules.rangeMax - (nbSeg * step)
}
maxOffset = rules.rangeMax - ((nbSeg - 1) * step)
}
...
return {
"solution": idx,
"step": step,
"nbSeg": nbSeg,
"start": start,
"rangeMin": rules.rangeMin,
"rangeMax": rules.rangeMax
}
}
}
Implemented findNearestLeftTarget()
to handle misaligned targets:
1
2
3
4
function findNearestLeftTarget(targetNumber, rangeMin, step) {
var stepsFromMin = Math.floor((targetNumber - rangeMin) / step);
return rangeMin + (stepsFromMin * step);
}
The above check for adjustment of target number will not be needed once in future this check is implemented at the editor end , that is user would not be able to create invalid dataset
the above createExercise() function generates exercises following way
1
2
3
4
5
6
7
8
9
10
11
12
exercices = [
{solution: 1, step: 2, nbSeg: 11, start: 0}, // asks for position 1 (value=2)
{solution: 2, step: 2, nbSeg: 11, start: 0},
{solution: 3, step: 2, nbSeg: 11, start: 0},
//another case
{solution: 1, step: 5, nbSeg: 5, start: 0},
{solution: 2, step: 5, nbSeg: 5, start: 0}, //asks for position 2 value 0 + 5*2 =10
{solution: 3, step: 5, nbSeg: 5, start: 0},
]
Variable Tracking: The start
Variable
Understanding the start
variable behavior across different configurations:
Case | fitLimits | start Value | Behavior |
---|---|---|---|
Random + true | true | Always rangeMin | Full range ruler |
Random + false | false | Random within range | Sliding window ruler |
Fixed | true (always) | Always rangeMin | Full range ruler |
Key Insight: When fitLimits = false
, start
is calculated as:
1
2
maxOffset = rangeMax - (nbSeg * step);
start = rangeMin + randInt(maxOffset);
This ensures the ruler segment never exceeds rangeMax
.
Aspect | Random (fitLimits=true) | Random (fitLimits=false) | Fixed |
---|---|---|---|
Exercise Count | Multiple (based on steps × ticks) | Multiple (same calculation) | Single |
Ruler Structure | Always spans full range | Variable size & position | Always spans full range |
Target Selection | Random from all positions | Random from all positions | Specific target number |
Shuffling | Yes, exercises shuffled | Yes, exercises shuffled | No shuffling |
Solution Field | Position index | Position index | Position index of target |
Answer Field | Ruler value at solution | Ruler value at solution | Adjusted target value |
Test Scenarios Implemented
- Valid Fixed Target:
rangeMin: 0, rangeMax: 20, step: 2, targetNumber: 8
- Expected:
adjustedTarget: 8, targetPosition: 4
- Expected:
- Target Adjustment:
rangeMin: 0, rangeMax: 20, step: 2, targetNumber: 13
- Expected:
adjustedTarget: 12, targetPosition: 6
- Expected:
- Out-of-Bounds Target:
rangeMin: 50, rangeMax: 100, step: 5, targetNumber: 30
Restructured the Dataset for both Graduated line read and use
Basic Fixed Question:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"title": "Find the requested number",
"rules": [{
"dataType": "fixed",
"nbOfQuestions": 1,
"fitLimits": true,
"rangeMin": 0,
"rangeMax": 20,
"steps": [2],
"targetNumber": 13
"displayedRangeLengthMin" :0
"displayedRangeLengthMax" :0
}]
}
rangeMin / rangeMax → define the limits of the ruler.
steps → the interval between values (e.g., step size = 2).
targetNumber → the number the learner is asked to find.
fitLimits → when set to true, the ruler uses the exact min and max values given.
displayedRangeLengthMin / Max → not relevant in this fixed dataset, so both are set to 0.
Example question generated: “Find the requested number between 0 and 20 with step size = 2.”
In random mode, there are two cases:
fitLimits = true → the activity randomly chooses a target number within the fixed range (0–20).
fitLimits = false → the activity randomly selects a segment between displayedRangeLengthMin and displayedRangeLengthMax, then picks a random number inside that segment.
when Dataset is of type Random:
1
2
3
4
5
6
7
8
9
10
11
{
"title": "Find numbers 0-20",
"rules": [{
"dataType": "random",
"nbOfQuestions": 5,
"fitLimits": true,
"rangeMin": 0,
"rangeMax": 20,
"steps": [1]
}]
}
In case of random there are two modes one where fitlimits is true and one where its false in case of fitlimits set to true activity randomly chooses an index to ask , while keeping the range fixed , in other case activity picks up a segment in between displayedRangeLength min and max and picks up random index from that segment and creates a question. Ensured cursor randomization works correctly in both modes:
1
2
3
4
5
6
7
8
9
10
11
if (activityMode === "number2tick") {
if (exo.isFixed) {
// fixed data: randomize cursor, but keep target as answer
items.solutionGrad = randInt(exo.nbSeg - 2) + 1
items.answer = exo.adjustedTarget.toString() // Always the fixed target
} else {
// random data: randomize cursor and use that position's value
items.solutionGrad = randInt(exo.nbSeg - 2) + 1
items.answer = items.rulerModel.get(items.solutionGrad).value_.toString()
}
}
What's Achieved So Far
Working Editor for Both Graduated Line Read & Use Activities
A working editor now exists for both Graduated Line Read and Graduated Line Use activities.
However, major improvements are still pending—for example, the editor currently does not validate datasets.
Right now, it only checks that rangeMin < rangeMax
.
In the future, I plan to add a text warning for teachers when an invalid dataset is created.
Added a popup dialog to provide instructions .
Logs for tracking the flow of activity to ease things.
1
2
3
4
5
qml: Range Min changed: rangeMin = 1 at index 0
qml: range Min cannot be greater than or equal to Range Max
qml: Range Max changed: rangeMax = 1 at index 0
qml: range Max cannot be less than or equal to Range Min
qml: text from the dialog is accepted: Number to find between 10 and 20
graduated line read
graduated line use
Future Work Post GSoC
Future Tasks
- Build a number-array input component
Most of the difficulty came from the limitation inFieldEdit.qml
, which converts everything to strings.
FieldEdit.qml returns different custom components — for example, a string array, a spinbox, a text area, etc. — based on requirement. It consists of generic components that are reused by editors, helping keep the code modular.
Currently, it’s not possible to have the same field component represent different input types, so I’ll work on creating a dedicated component for number arrays.
Add dataset validation for target numbers in the Graduated Line editor
Right now, only a basicrangeMin < rangeMax
check exists. I plan to add a proper validation step for target numbers inside the editor.Work on the next set of activities
Most of my time went into resolving issues. One major challenge was with FieldEdit —
initially, I tried to reuse the same field for multiple input types, but that approach didn’t work.
In the end, we dropped the idea and created multiple fields instead.
For example, while working on the Graduated Line activity, we started with the following default dataset:
1
2
3
4
5
6
7
8
9
10
11
{
"title": "Find the requested number",
"rules": [{
"nbOfQuestions": 1,
"fitLimits": true,
"range": [0, 10],
"steps": [2, 5],
"displayedRangeLengthMin": 0,
"displayedRangeLengthMax": 0
}]
}
In cases where the dataset type is fixed, the steps field should accept only one value. But for random, it needs to accept multiple values.
Since FieldEdit does not allow the same field to handle both an array and a single integer, we chose the approach of creating two separate fields:
1
2
3
stepFixed (for fixed datasets)
steps (for random datasets)
Another difficulty was handling the legacy dataset. At first, I thought I needed to find a way to make it work,
but I soon realized that restructuring the dataset was necessary for the editor to function properly because the server was appending the dataset a certain format , and it was probably easier to restructure the default dataset.
With the experience I’ve gained from working on GCompris, if I were starting over,
I’d first focus on getting a basic working editor and then add functionalities later.
A key lesson for me is to try out different ideas earlier and seek feedback,
rather than spending too much time stuck on one solution.
If I had done this sooner, I would have learned more and saved time — something I’ll remember for the future 🙂.
overall Impact
Overall Impact
I worked on adding editor support for several activities that previously lacked it, including:
- Learn Decimals
- Quantity
- Learn Decimals: Addition & Subtraction
- Graduated Line Read & Use
With these editors in place:
- 👩🏫 Teachers can now create and customize exercises for these activities.
- 🛠️ The editors provide basic validation, reducing the chance of invalid datasets.
This makes the activities much more flexible and adaptable for classroom use,
Conclusion
In conclusion, two activities — Share and Grammar Analysis — were dropped after the midterm due to time constraints.
Out of the five initially planned activities, the following have been successfully completed:
- Editor for Learn Decimals and its related activities
- Learn Quantity
- Learn Decimal Addition and Subtraction
- A basic editor for Graduated Line Read & Use (with dataset validation still pending)
In terms of experience, I gained substantial knowledge while working on this project.
I became comfortable with Git, and more importantly, I learned how to navigate and contribute to a large-scale codebase.
I particularly appreciated how modular the GCompris codebase is — each function is designed to perform a single, well-defined task without overlapping responsibilities. (As a rule of thumb, if a function is more than 10 lines long, it can usually be optimized further 🙂.)
Although I did not directly work with the networking and database components, my involvement in developing and understanding the activities gave me valuable insights into how different systems integrate and function together.
This experience not only strengthened my technical skills but also played a significant role in helping me secure a position at a major product-based company.
Looking ahead, I have the next couple of months to myself, and I commit to completing the pending backlog.