Restore distractor-specific follow-up questions

This commit is contained in:
hz
2026-08-01 18:06:52 -05:00
parent 8c5aa17d89
commit c7f5006cb6
14 changed files with 76770 additions and 100 deletions
+37 -29
View File
@@ -29,7 +29,6 @@ var simulation_total_blocks := 12
var simulation_deadline_unix := 0.0
var review_first_submission_recorded := false
var core_block_size := DEFAULT_CORE_BLOCK_SIZE
var show_review_followups := true
const CORE_MIN_TARGET_SECONDS := 45.0
const CORE_MAX_TARGET_SECONDS := 150.0
@@ -40,12 +39,10 @@ const CORE_BLOCK_SIZE_MAX := 40
const SETTINGS_PATH := "user://settings.cfg"
const SETTINGS_SECTION := "session"
const SETTINGS_KEY_CORE_BLOCK_SIZE := "core_block_size"
const SETTINGS_KEY_SHOW_REVIEW_FOLLOWUPS := "show_review_followups"
func _ready() -> void:
_load_core_block_size()
_load_show_review_followups()
func set_core_block_size(value: int) -> void:
@@ -60,17 +57,6 @@ func set_core_block_size(value: int) -> void:
push_warning("Unable to save core question block size preference.")
func set_show_review_followups(value: bool) -> void:
if show_review_followups == value:
return
show_review_followups = value
var config := ConfigFile.new()
config.load(SETTINGS_PATH)
config.set_value(SETTINGS_SECTION, SETTINGS_KEY_SHOW_REVIEW_FOLLOWUPS, show_review_followups)
if config.save(SETTINGS_PATH) != OK:
push_warning("Unable to save follow-up question preference.")
func _load_core_block_size() -> void:
var config := ConfigFile.new()
if config.load(SETTINGS_PATH) == OK:
@@ -79,12 +65,6 @@ func _load_core_block_size() -> void:
CORE_BLOCK_SIZE_MIN, CORE_BLOCK_SIZE_MAX)
func _load_show_review_followups() -> void:
var config := ConfigFile.new()
if config.load(SETTINGS_PATH) == OK:
show_review_followups = bool(config.get_value(SETTINGS_SECTION, SETTINGS_KEY_SHOW_REVIEW_FOLLOWUPS, true))
func start_dynamic_question(question: Dictionary) -> bool:
if question.is_empty():
return false
@@ -284,6 +264,7 @@ func answer_current(choice_id: String, eliminated: Array, flagged: bool, is_subm
"question": question.duplicate(true),
"selected_choice_id": choice_id,
"is_correct": choice_id == str(question.get("correct_choice_id", "")),
"first_attempt_is_correct": choice_id == str(question.get("correct_choice_id", "")) and (session_kind != "core" or not core_current_had_wrong_attempt),
"eliminated_choice_ids": eliminated.duplicate(),
"flagged": flagged,
"elapsed_seconds": get_current_elapsed_seconds(),
@@ -311,6 +292,7 @@ func submit_core_attempt(choice_id: String, eliminated: Array, flagged: bool) ->
"question": question.duplicate(true),
"selected_choice_id": choice_id,
"is_correct": choice_id == str(question.get("correct_choice_id", "")),
"first_attempt_is_correct": choice_id == str(question.get("correct_choice_id", "")) and not core_current_had_wrong_attempt,
"eliminated_choice_ids": eliminated.duplicate(),
"flagged": flagged,
"elapsed_seconds": get_current_elapsed_seconds(),
@@ -343,26 +325,50 @@ func submit_core_attempt(choice_id: String, eliminated: Array, flagged: bool) ->
}
func submit_bookmark_attempt(choice_id: String, eliminated: Array, flagged: bool) -> Dictionary:
func submit_tutor_attempt(choice_id: String, eliminated: Array, flagged: bool, had_wrong_attempt: bool) -> Dictionary:
var question := get_current_question()
if question.is_empty() or mode != "Tutor" or session_kind not in ["block", "dynamic"]:
return {}
var current_correct := choice_id == str(question.get("correct_choice_id", ""))
var answer := {
"question": question.duplicate(true),
"selected_choice_id": choice_id,
"is_correct": current_correct and not had_wrong_attempt,
"first_attempt_is_correct": current_correct and not had_wrong_attempt,
"resolved_correct": current_correct,
"eliminated_choice_ids": eliminated.duplicate(),
"flagged": flagged,
"elapsed_seconds": get_current_elapsed_seconds(),
"target_seconds": 0.0
}
if current_correct:
answers[current_index] = answer
session_changed.emit()
return {"answer": answer, "is_correct": current_correct, "final": current_correct}
func submit_bookmark_attempt(choice_id: String, eliminated: Array, flagged: bool, had_wrong_attempt := false) -> Dictionary:
var question := get_current_question()
if session_kind != "bookmark" or question.is_empty():
return {}
var answer := {
"question": question.duplicate(true),
"selected_choice_id": choice_id,
"is_correct": choice_id == str(question.get("correct_choice_id", "")),
"is_correct": choice_id == str(question.get("correct_choice_id", "")) and not had_wrong_attempt,
"first_attempt_is_correct": choice_id == str(question.get("correct_choice_id", "")) and not had_wrong_attempt,
"eliminated_choice_ids": eliminated.duplicate(),
"flagged": flagged,
"elapsed_seconds": get_current_elapsed_seconds(),
"target_seconds": 0.0
}
if bool(answer.is_correct):
var current_correct := choice_id == str(question.get("correct_choice_id", ""))
if current_correct:
answers[current_index] = answer
session_changed.emit()
return {"answer": answer, "is_correct": bool(answer.is_correct), "final": bool(answer.is_correct)}
return {"answer": answer, "is_correct": current_correct, "final": current_correct}
func submit_review_attempt(choice_id: String, eliminated: Array, flagged: bool) -> Dictionary:
func submit_review_attempt(choice_id: String, eliminated: Array, flagged: bool, had_wrong_attempt := false) -> Dictionary:
var question := get_current_question()
if session_kind != "review" or question.is_empty():
return {}
@@ -370,20 +376,22 @@ func submit_review_attempt(choice_id: String, eliminated: Array, flagged: bool)
var answer := {
"question": question.duplicate(true),
"selected_choice_id": choice_id,
"is_correct": choice_id == str(question.get("correct_choice_id", "")),
"is_correct": choice_id == str(question.get("correct_choice_id", "")) and not had_wrong_attempt,
"first_attempt_is_correct": choice_id == str(question.get("correct_choice_id", "")) and not had_wrong_attempt,
"eliminated_choice_ids": eliminated.duplicate(),
"flagged": flagged,
"elapsed_seconds": get_current_elapsed_seconds(),
"target_seconds": 0.0
}
review_first_submission_recorded = true
if bool(answer.is_correct):
var current_correct := choice_id == str(question.get("correct_choice_id", ""))
if current_correct:
answers[current_index] = answer
session_changed.emit()
return {
"answer": answer,
"is_correct": bool(answer.is_correct),
"final": bool(answer.is_correct),
"is_correct": current_correct,
"final": current_correct,
"first_submission": first_submission
}
+1 -1
View File
@@ -26,7 +26,7 @@ func calculate_block_rewards(answers: Array, mode: String, system_name: String)
var ending_streak := running_streak
for answer in answers:
if bool(answer.get("is_correct", false)):
if bool(answer.get("first_attempt_is_correct", answer.get("is_correct", false))):
correct += 1
ending_streak += 1
longest_streak = max(longest_streak, ending_streak)
+19 -22
View File
@@ -1613,31 +1613,32 @@ func _submit_or_next() -> void:
if selected_choice == "":
return
var question_id_for_attempt := str(BlockSessionService.get_current_question().get("id", ""))
if BlockSessionService.session_kind == "dynamic":
var elapsed := int(floor(BlockSessionService.get_current_elapsed_seconds()))
DynamicQuestionService.submit_attempt(selected_choice, elapsed)
current_answer = BlockSessionService.answer_current(selected_choice, eliminated, false)
_show_answer_review()
return
if BlockSessionService.session_kind in ["core", "bookmark", "review"]:
var immediate_feedback := BlockSessionService.session_kind in ["core", "bookmark", "review", "dynamic"] or (BlockSessionService.session_kind == "block" and BlockSessionService.mode == "Tutor")
if immediate_feedback:
var attempted_choice := selected_choice
var had_wrong_attempt := not locked_wrong_choice_ids.is_empty()
var met_time_limit := true
if BlockSessionService.session_kind == "core":
met_time_limit = BlockSessionService.get_current_elapsed_seconds() <= BlockSessionService.core_current_target_seconds
if BlockSessionService.session_kind in ["core", "block"] and not had_wrong_attempt:
if BlockSessionService.session_kind == "core":
met_time_limit = BlockSessionService.get_current_elapsed_seconds() <= BlockSessionService.core_current_target_seconds
QuestionBankService.record_first_attempt(
question_id_for_attempt,
attempted_choice,
_presented_choice_ids(),
int(floor(BlockSessionService.get_core_question_total_elapsed_seconds())),
int(floor(BlockSessionService.get_core_question_total_elapsed_seconds())) if BlockSessionService.session_kind == "core" else int(floor(BlockSessionService.get_current_elapsed_seconds())),
met_time_limit
)
elif BlockSessionService.session_kind == "dynamic" and not had_wrong_attempt:
DynamicQuestionService.submit_attempt(attempted_choice, int(floor(BlockSessionService.get_current_elapsed_seconds())))
var result: Dictionary
if BlockSessionService.session_kind == "core":
result = BlockSessionService.submit_core_attempt(selected_choice, eliminated, flagged)
elif BlockSessionService.session_kind == "bookmark":
result = BlockSessionService.submit_bookmark_attempt(selected_choice, eliminated, flagged)
result = BlockSessionService.submit_bookmark_attempt(selected_choice, eliminated, flagged, had_wrong_attempt)
elif BlockSessionService.session_kind == "review":
result = BlockSessionService.submit_review_attempt(selected_choice, eliminated, flagged, had_wrong_attempt)
else:
result = BlockSessionService.submit_review_attempt(selected_choice, eliminated, flagged)
result = BlockSessionService.submit_tutor_attempt(selected_choice, eliminated, flagged, had_wrong_attempt)
if result.is_empty():
return
var answer: Dictionary = result.get("answer", {})
@@ -1817,7 +1818,6 @@ func _dock_correct_review_panel(correct_id: String) -> void:
func _render_answer_review_sections() -> void:
_render_teaching_cards()
_render_review_followups()
_render_unselected_distractor_explanations()
@@ -1893,12 +1893,9 @@ func _render_review_followups() -> void:
if review_followups_content == null:
return
_clear_container_children(review_followups_content)
if BlockSessionService.session_kind == "simulation":
review_followups_section.visible = false
return
if not BlockSessionService.show_review_followups:
review_followups_section.visible = false
return
review_followup_states.clear()
review_followups_section.visible = false
return
var question := BlockSessionService.get_current_question()
var authored: Array = question.get("review_followups", [])
if review_followup_states.size() != authored.size():
@@ -3774,14 +3771,14 @@ func _followup_is_dark() -> bool:
func _choice_review_explanation(choice_id: String, state: Dictionary) -> String:
var teaching_point := str(state.get("teaching_point", "")).strip_edges()
if teaching_point != "":
return teaching_point
var question := BlockSessionService.get_current_question()
var explanations: Dictionary = question.get("choice_explanations", {})
var explanation := str(explanations.get(choice_id, "")).strip_edges()
if explanation != "":
return explanation
var teaching_point := str(state.get("teaching_point", "")).strip_edges()
if teaching_point != "":
return teaching_point
if bool(state.get("is_main_correct", false)):
return str(question.get("explanation", "Review the correct answer explanation."))
return "Review the stem finding that makes this option less appropriate."
-9
View File
@@ -9,7 +9,6 @@ const CORE_BLOCK_SIZE_STEP := 5
var main: Control
var theme_toggle: CheckButton
var sound_toggle: CheckButton
var followups_toggle: CheckButton
var question_scale_value_label: Label
var ccs_scale_value_label: Label
var core_block_size_value_label: Label
@@ -73,13 +72,6 @@ func _ready() -> void:
sound_toggle.toggled.connect(UISoundService.set_sounds_enabled)
content.add_child(sound_toggle)
followups_toggle = CheckButton.new()
followups_toggle.text = "Follow-up questions"
followups_toggle.tooltip_text = "Optional reinforcement questions shown after you answer"
followups_toggle.custom_minimum_size = Vector2(0, 52)
followups_toggle.toggled.connect(BlockSessionService.set_show_review_followups)
content.add_child(followups_toggle)
content.add_child(_build_text_scale_row("Question bank text size", "question"))
content.add_child(_build_text_scale_row("CCS case text size", "ccs"))
content.add_child(_build_core_block_size_row())
@@ -117,7 +109,6 @@ func on_show(payload: Variant = null) -> void:
back_to_session_button.visible = not return_screen.is_empty()
theme_toggle.set_pressed_no_signal(UIThemeService.is_dark_mode())
sound_toggle.set_pressed_no_signal(UISoundService.are_sounds_enabled())
followups_toggle.set_pressed_no_signal(BlockSessionService.show_review_followups)
_refresh_text_scale_labels()