Constrain CCS patient dialogue

This commit is contained in:
hz
2026-08-06 05:13:21 -05:00
parent 2c7c5dd387
commit dfc224660e
4 changed files with 78 additions and 7 deletions
+4 -1
View File
@@ -103,6 +103,7 @@ antialiased = true
[node name="DialogueBubble" type="Control" parent="."]
z_index = 30
clip_contents = true
layout_mode = 1
anchors_preset = 0
anchor_left = 0.53
@@ -127,7 +128,7 @@ texture = ExtResource("6_chat_bubble")
expand_mode = 1
stretch_mode = 0
[node name="Text" type="Label" parent="DialogueBubble"]
[node name="Text" type="RichTextLabel" parent="DialogueBubble"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
@@ -141,6 +142,8 @@ grow_vertical = 2
theme_override_colors/font_color = Color(0.055, 0.105, 0.145, 1)
theme_override_font_sizes/font_size = 20
text = "Hello, doctor."
fit_content = false
scroll_active = false
autowrap_mode = 2
vertical_alignment = 1
+1 -1
View File
@@ -18,7 +18,7 @@ const DIALOGUE_PAGE_MAX_CHARS := 82
@onready var second_hand: Node2D = $ClockMount/SecondHand
@onready var patient: TextureRect = $Patient
@onready var dialogue_bubble: Control = $DialogueBubble
@onready var dialogue_text: Label = $DialogueBubble/Text
@onready var dialogue_text: RichTextLabel = $DialogueBubble/Text
@onready var dialogue_continue_hint: Label = $DialogueBubble/ContinueHint
@onready var choice_area: Control = $DialogueChoiceArea
@onready var choice_container: HBoxContainer = $DialogueChoiceArea/DialogueChoices
+49
View File
@@ -4,6 +4,11 @@ const Brand := preload("res://scripts/ui/medmind_style.gd")
const Style := preload("res://scripts/ui/ui_style.gd")
const DOCTOR_SCENE := preload("res://scenes/ccs_cases_2/doctor_office.tscn")
const DESIGN_SIZE := Vector2(1500.0, 800.0)
const FOCUSED_INTERVIEW_CASE_ID := "ccs-gastro-duodenal-ulcer-office-001"
const RETIRED_INTERVIEW_TOPIC_IDS := [
"abdominal_symptoms", "bleeding_alarm_features", "medications",
"medical_history", "social_history", "additional_review"
]
var main
var stage_root: Control
@@ -16,6 +21,7 @@ var current_view := ""
var cases_loaded := false
var active_loaded := false
var pending_action := ""
var restart_case_external_id := ""
func _ready() -> void:
@@ -39,10 +45,13 @@ func _connect_service() -> void:
CCSService.attempt_changed.connect(_on_attempt_changed)
if not CCSService.request_failed.is_connected(_on_request_failed):
CCSService.request_failed.connect(_on_request_failed)
if not CCSService.attempt_abandoned.is_connected(_on_attempt_abandoned):
CCSService.attempt_abandoned.connect(_on_attempt_abandoned)
func on_show(_payload: Variant = null) -> void:
CCSService.launch_origin = "ccs_cases_2"
restart_case_external_id = ""
cases_loaded = false
active_loaded = false
pending_action = "loading"
@@ -52,6 +61,7 @@ func on_show(_payload: Variant = null) -> void:
func on_hide() -> void:
restart_case_external_id = ""
_clear_active_scene()
@@ -102,6 +112,9 @@ func _render_attempt(attempt: Dictionary) -> void:
if node.is_empty():
_show_unsupported_attempt("The graphical interview could not load its current dialogue.")
return
if _is_retired_interview_revision(attempt, node):
_restart_with_current_interview(attempt)
return
if str(attempt.get("experienceMode", "management")) != "graphical_interview" \
and bool(attempt.get("openingCompleted", false)) and not bool(node.get("isTerminal", false)):
_show_unsupported_attempt("This case has progressed beyond the graphical interview.")
@@ -263,6 +276,37 @@ func _repeat_interview() -> void:
CCSService.create_attempt(case_external_id, "timed")
func _is_retired_interview_revision(attempt: Dictionary, node: Dictionary) -> bool:
if str(attempt.get("caseExternalId", "")) != FOCUSED_INTERVIEW_CASE_ID:
return false
if str(node.get("id", "")) in RETIRED_INTERVIEW_TOPIC_IDS:
return true
for raw_choice in node.get("choices", []):
if str(raw_choice.get("id", "")) in RETIRED_INTERVIEW_TOPIC_IDS:
return true
var patient_text := str(node.get("patientText", "")).to_lower()
return patient_text.contains("stomach bothers me") or patient_text.contains("gnawing ache")
func _restart_with_current_interview(attempt: Dictionary) -> void:
if pending_action == "refreshing_interview":
return
restart_case_external_id = str(attempt.get("caseExternalId", ""))
pending_action = "refreshing_interview"
_show_selection("Updating this interview to its latest dizziness and fatigue dialogue...")
CCSService.abandon()
func _on_attempt_abandoned() -> void:
if restart_case_external_id.is_empty():
return
var case_external_id := restart_case_external_id
restart_case_external_id = ""
pending_action = "creating"
status_label.text = "Starting the updated interview..."
CCSService.create_attempt(case_external_id, "timed")
func _on_request_failed(message: String) -> void:
if not visible:
return
@@ -272,6 +316,11 @@ func _on_request_failed(message: String) -> void:
status_label.text = "An active case was found. Loading it now..."
CCSService.load_active()
return
if failed_action == "refreshing_interview":
restart_case_external_id = ""
status_label.text = "The updated interview could not be started. %s" % message
retry_button.visible = true
return
if active_scene != null:
active_scene.set_choices_pending(false)
else:
+24 -5
View File
@@ -54,6 +54,20 @@ func _run() -> void:
await process_frame
_assert(screen.current_view == "case_selection", "CCS Cases 2 should begin with compatible case selection")
_assert(screen.case_list.get_child_count() == 1, "legacy cases should be excluded from graphical selection")
var retired_attempt := {
"caseExternalId": "ccs-gastro-duodenal-ulcer-office-001"
}
_assert(screen._is_retired_interview_revision(retired_attempt, {
"id": "fatigue_course", "patientText": "My stomach bothers me.", "choices": []
}), "the retired stomach response should trigger a current-revision restart")
_assert(screen._is_retired_interview_revision(retired_attempt, {
"id": "chief_concern", "patientText": "Dizzy.",
"choices": [{"id": "abdominal_symptoms", "text": "Ask about stomach pain"}]
}), "retired stomach-topic choices should trigger a current-revision restart")
_assert(not screen._is_retired_interview_revision(retired_attempt, {
"id": "chief_concern", "patientText": "Dizzy and tired.",
"choices": [{"id": "dizziness_character", "text": "Ask about dizziness"}]
}), "the focused dizziness and fatigue revision should remain active")
service.attempt = _sample_attempt(false)
screen._render_state()
@@ -219,11 +233,16 @@ func _choice_labels_fit(scene: Control) -> bool:
return true
func _label_content_fits(label: Label) -> bool:
var font := label.get_theme_font("font")
var font_size := label.get_theme_font_size("font_size")
var content_height := float(label.get_line_count()) * font.get_height(font_size)
return content_height <= label.size.y + 0.5
func _label_content_fits(label: Control) -> bool:
if label is RichTextLabel:
return label.get_content_height() <= label.size.y + 0.5
var plain_label := label as Label
if plain_label == null:
return false
var font := plain_label.get_theme_font("font")
var font_size := plain_label.get_theme_font_size("font_size")
var content_height := float(plain_label.get_line_count()) * font.get_height(font_size)
return content_height <= plain_label.size.y + 0.5
func _assert(condition: bool, message: String) -> void: