Skip to content

Hints and progression

Hints are short messages that the student can display on demand by clicking a 💡 button in the overlay. They are designed as a pedagogical safety net: the student solves the enigma without them as long as they can; if they get stuck, the instructor can authorize them. This page describes the three-level mechanism that drives hint visibility: the hintAvailable field of each enigma, the real-time instructor toggle, and the conditional rendering on the React side.

The three visibility levers

A 💡 button only appears on an enigma if the following three conditions are satisfied simultaneously:

flowchart LR
    A["enigme.hintAvailable<br/>(boolean defined in Java)"] --> AND{AND}
    B["pedagogiqueHintsEnabled<br/>(instructor toggle)"] --> AND
    C["enigme.hint != null<br/>(i18n-resolved text)"] --> AND
    AND --> OUI[💡 Button displayed]
    AND -.->|missing 1|NON[No button]
Control level Scope Decided by When
Per enigmahintAvailable A single step Catalog designer (Java code) At Enigme record definition
Global sessionpedagogiqueHintsEnabled All steps of the active journey Instructor At runtime, during the journey, via a chaos-admin button
Client rendering — absence of hint A single step i18n configuration At React session load

The first two levers are independent: disabling the global toggle hides the buttons for all enigmas, including those with hintAvailable = true. Conversely, the toggle does not make anything appear for an enigma with hintAvailable = false.

The hintAvailable field per enigma

In the Enigme record, hintAvailable is a boolean defined at construction:

new Enigme("BAC3-17", "bac3", 17,
    t("bac3", "BAC3-17.text"), "right",
    t("bac3", "BAC3-17.hint"),
    true,                     // ← hintAvailable
    h("…"),
    t("bac3", "BAC3-17.culturalNote"),
    "checkout"
);

Today, all 100 enigmas across the five levels have hintAvailable = true. The field is kept as an extension point: a designer could disable the hint on a specific enigma (for example a timed final challenge in a contest) without touching the global logic.

The hint itself (enigme.hint) is resolved from the i18n JSON via t(level, "BACx-n.hint"). If the key is absent from the file, t() returns [BACx-n.hint] — visible on screen, which immediately flags the missing key to the designer without preventing the enigma from working.

The instructor toggle

The global toggle is held by the pedagogiqueHintsEnabled field of ChaosStudentController, declared volatile to guarantee visibility between threads:

private volatile boolean pedagogiqueHintsEnabled = true;

It is initialized to true at application startup and reset to true on every deactivate or level change — each journey therefore starts with hints enabled. The instructor can toggle this flag at any time via a dedicated endpoint.

Endpoint POST /pedagogique/hints

@PostMapping("/pedagogique/hints")
public ResponseEntity<?> setHintsEnabled(@RequestBody Map<String, Object> body,
        HttpSession session, @RequestHeader(value = "X-Admin-Token", required = false) String tok) {
    if (!AdminAuth.isAdmin(session, tok)) return unauth();
    Object val = body.get("enabled");
    if (!(val instanceof Boolean))
        return ResponseEntity.badRequest().body(Map.of("error",
            i18n.t("student.pedagogique.error.hints_enabled_required")));
    pedagogiqueHintsEnabled = (Boolean) val;
    log.info("[Pedagogical] Hints {} by the instructor",
        pedagogiqueHintsEnabled ? "enabled" : "hidden");
    return ResponseEntity.ok(Map.of("success", true,
        "hintsEnabled", pedagogiqueHintsEnabled));
}
Property Value
Method POST
URL /api/chaos/student/pedagogique/hints
Authentication X-Admin-Token header or admin session
Body {"enabled": true|false}
Response 200 {"success": true, "hintsEnabled": <bool>}
Response 400 If enabled is absent or not a boolean
Response 401 If the admin token is missing or invalid

Body validation explicitly rejects anything that is not a boolean — a client sending {"enabled": "true"} (string) or {"enabled": 1} (integer) receives a 400.

Propagation to students

Unlike other parameters, the toggle is not pushed to students: it is re-read on each poll in the /status response. This "pull" strategy simplifies the architecture (no WebSocket, no SSE) and remains reactive enough since polling runs every 15 seconds.

sequenceDiagram
    autonumber
    actor F as Instructor
    participant ADM as chaos-admin
    participant CTR as ChaosStudentController
    actor E1 as Student 1
    actor E2 as Student 2

    Note over E1,E2: Polling in progress<br/>(15s interval)

    F->>ADM: Clicks "Hide hints"
    ADM->>CTR: POST /pedagogique/hints {enabled:false}
    CTR->>CTR: pedagogiqueHintsEnabled = false
    CTR-->>ADM: 200 {hintsEnabled:false}

    Note over ADM: UI confirms "hidden"

    par Next student polls
      E1->>CTR: GET /status
      CTR-->>E1: pedagogique.hintsEnabled = false
      E1->>E1: 💡 button disappears
    and
      E2->>CTR: GET /status
      CTR-->>E2: pedagogique.hintsEnabled = false
      E2->>E2: 💡 button disappears
    end

    Note over F: Instructor sees that<br/>all students understood
    F->>ADM: Clicks "Show hints"
    ADM->>CTR: POST /pedagogique/hints {enabled:true}
    CTR->>CTR: pedagogiqueHintsEnabled = true

    par Next polls
      E1->>CTR: GET /status
      CTR-->>E1: pedagogique.hintsEnabled = true
      E1->>E1: 💡 button reappears
    and
      E2->>CTR: GET /status
      CTR-->>E2: pedagogique.hintsEnabled = true
    end

Observed latency

The latency between the instructor's click and the disappearance of the 💡 button on the student side is at worst 15 seconds (polling interval) plus the backend response time (a few milliseconds). In practice, since the polls of several students are not synchronized with each other, the disappearance is progressive across the whole class in less than one full cycle.

This asynchrony is a pedagogical advantage: if the instructor enables hints to "unblock" a specific student, there is no immediate visible effect on the rest of the class that could disturb the others' concentration.

Conditional rendering on the React side

The hintsEnabled field is propagated down to PedagogiqueOverlay through a prop:

<PedagogiqueOverlay
  enigme={state.enigme}
  step={state.step}
  totalSteps={state.totalSteps}
  onValidated={refresh}
  timerExpired={timerExpired}
  skipPollingRef={skipPollingRef}
  hintsEnabled={state.hintsEnabled !== false}
/>

The !== false test is deliberate: it treats undefined and null as "enabled" by default, in order to be backward-compatible with sessions that would not yet include the field (for example during a partial frontend/backend rollback).

The overlay renders the 💡 button only if the three conditions are met:

{enigme.hintAvailable && hintsEnabled && (
  <div style={{ marginBottom: '10px' }}>
    <button onClick={() => setHintVisible(v => !v)}>
      💡 {hintVisible ? t('ped.hideHint') : t('ped.showHint')}
    </button>
    {hintVisible && enigme.hint && (
      <div>{enigme.hint}</div>
    )}
  </div>
)}

The button shows or hides the hint by toggling a local state (hintVisible) — the text is never masked server-side once sent, it is purely a display state. The hintVisible state is automatically reset to false on every step change via the useEffect([enigme?.id]) of the overlay.

Interaction with the Logic theme

The final Logic & Math theme (see themes.md) has its own hint mechanism independent of the instructor toggle. Each question in the pool of 25 comes with a short hint, stored in the hint field of the logique_fr.json file, and displayed via a question-local 💡 button. The global pedagogiqueHintsEnabled toggle does not affect this page.

The reason is functional: the success page and the final theme are displayed after the end of the main journey. At this stage, the notion of "class in progress" has lost its meaning — the student is on their end screen, the instructor is no longer driving anything. Cutting off access to the final theme hints would be a false signal.

Relevant i18n keys

To translate or adapt hint-related messages, target:

Key File Usage
ped.showHint frontend/src/i18n/fr.json and en.json Button label when the hint is hidden
ped.hideHint frontend/src/i18n/fr.json and en.json Label when the hint is visible
BACx-n.hint backend/src/main/resources/i18n/enigmes/bacX/enigmes_XX.json Text of each individual hint
student.pedagogique.error.hints_enabled_required messages_fr.properties and messages_en.properties 400 error if body is malformed

Next pages: ← Star system · Success page → · Final themes →