Star system¶
At the end of a journey, the student receives a number of stars
that rewards both the difficulty of the level reached and
respecting the allotted time. This score is displayed on the
success page (/s/:token) and used by the instructor panel to order
results in the session summary.
The computeStars method¶
The computation is centralized in
ChaosStudentController.computeStars():
static int computeStars(String level, boolean inTime) {
int base = switch (level != null ? level : "") {
case "bac1" -> 1;
case "bac2" -> 2;
case "bac3" -> 3;
case "bac4" -> 4;
case "bac5" -> 5;
default -> 1;
};
return inTime ? base : Math.max(1, base - 1);
}
Two simple principles combine:
- Base = level number.
bac1yields at most 1 star,bac5at most 5. The maximum star for a given level is a constant, not a dynamic difficulty variable. - Penalty of one star if time exceeded. If the session is not
completed within the allotted time (
durationSeconds > pedagogiqueTimer), one star is subtracted, with a floor of 1 star — it is impossible to fall back to 0, even on a journey finished several hours past the deadline.
Possible scores table¶
| Level | Allotted time | Stars if in time | Stars if out of time |
|---|---|---|---|
bac1 |
30 min | 1 ⭐ | 1 ⭐ |
bac2 |
45 min | 2 ⭐⭐ | 1 ⭐ |
bac3 |
60 min | 3 ⭐⭐⭐ | 2 ⭐⭐ |
bac4 |
75 min | 4 ⭐⭐⭐⭐ | 3 ⭐⭐⭐ |
bac5 |
90 min | 5 ⭐⭐⭐⭐⭐ | 4 ⭐⭐⭐⭐ |
Why bac1 never loses a star
The floor of 1 star guarantees that the bac1 level — designed as
an introductory journey — does not "punish" a student who takes
the time to explore the store without stress. An overrun on bac1
makes the duration visible to the instructor but preserves the
symbolic reward.
Computing inTime¶
The inTime boolean is computed when the journey is completed, in
validatePedagogique():
Important point: the comparison uses the absolute time elapsed
since the instructor started the journey (pedagogiqueStartTime),
not the time since the individual student joined the session
(session.joinedAt). A student who joins the session 10 minutes after
the /activate therefore has 20 minutes for a bac1, not 30,
because their countdown shares the same time base as the instructor's.
This choice is pedagogical: the instructor can start a journey and let students join progressively, but the "all together" approach forces latecomers to speed up — which matches the reality of a classroom.
Fallback for completedAt = 0¶
In the buildPedagogiqueState() block that serves the /status polls,
a safeguard handles the case where completedAt = 0 while the final
step is marked as done:
int stars;
if (session.completedAt > 0) {
long dur = (session.completedAt - session.joinedAt) / 1000L;
stars = computeStars(pedagogiqueLevel, dur <= pedagogiqueTimer);
} else {
log.warn("[Pedagogical] completedAt=0 for {} — fallback global time",
session.displayAlias());
stars = computeStars(pedagogiqueLevel, elapsed <= pedagogiqueTimer);
}
If completedAt is at zero (abnormal situation — a session that went
through a badly cleaned restart, for example), the computation falls
back to the instructor's global time. A warning is logged to alert the
operations team.
Score propagation¶
sequenceDiagram
autonumber
actor E as Student
participant OVR as PedagogiqueOverlay
participant CTR as ChaosStudentController
participant DB as MySQL
participant SUC as PedagogiqueSucces
participant ADM as chaos-admin
Note over E,OVR: Final DYNAMIC step validated
OVR->>CTR: POST /validate step=N
CTR->>CTR: session.completedAt = now()
CTR->>DB: UPDATE pedagogique_sessions<br/>SET completed_at
CTR->>CTR: stars = computeStars(level,<br/>dur <= timer)
CTR-->>OVR: {valid, completed:true,<br/>stars:4, maxStars:5}
Note over E: Navigation /s/{token}
SUC->>CTR: GET /pedagogique/succes/{token}
CTR->>DB: SELECT session
CTR->>CTR: stars = computeStars(level, inTime)
CTR-->>SUC: {alias, stars:4, maxStars:5,<br/>durationSeconds:3842}
SUC->>SUC: SuccesStars renders ⭐⭐⭐⭐☆
Note over ADM: Instructor views sessions
ADM->>CTR: GET /pedagogique/sessions
CTR->>CTR: For each completed session:<br/>computeStars(level, dur<=timer)
CTR-->>ADM: [{alias, stars, maxStars, ...}]
The score is computed in three places that must remain consistent:
validatePedagogique()— response to the validation of the final step, directly to the student overlay.buildPedagogiqueState()— included in each/statuspoll for students whose session is already completed (persistence of the display after reload).getSessions()andbuildSessionSnapshot()— for the real-time instructor view and for archiving inlastSessionSummaryat deactivation time.
The three calls use the same static method
computeStars(level, inTime) — guaranteeing an identical result
regardless of the read path.
Archiving in the instructor snapshot¶
When the instructor clicks Deactivate (or starts a new journey),
buildSessionSnapshot() builds an immutable snapshot of all sessions
for the lastSessionSummary view:
if (completed && s.completedAt > 0) {
long dur = (s.completedAt - s.joinedAt) / 1000L;
m.put("durationSeconds", dur);
m.put("stars", computeStars(pedagogiqueLevel, dur <= pedagogiqueTimer));
m.put("maxStars", computeStars(pedagogiqueLevel, true));
}
The snapshot stores both the stars obtained and the maximum
stars of the level — which allows the UI to correctly display
4 / 5 ⭐ even after the journey has been stopped and cleared from
the database by clearAll().
Pedagogical role of the penalty¶
The one-star penalty for overrun is a soft signal, not a sanction.
A student who finishes a bac5 in 2 hours instead of 1h30 leaves with
4 stars instead of 5 — they know they finished but that the pace was
not optimal, which feeds the debrief discussion.
This choice is deliberately not configurable from the instructor UI: customizing the scale during a session would break comparability between cohorts. Changing the behavior requires editing the source code and a new build.
maxStars field in API responses¶
All progress responses include both stars (stars obtained) and
maxStars (stars achievable at this level). This redundancy
simplifies frontend rendering — no component needs to know the scale
by heart:
The React SuccesStars component reads these two values and renders
three filled stars (⭐) followed by zero empty stars (☆) — or, for a
bac5 out of time, four filled followed by one empty (4/5).
Next pages: ← Agent code · Hints → · Success page →