feat: rebuild math life around marks and directed endings

This commit is contained in:
2026-08-10 01:06:06 +08:00
parent b8916180a9
commit aafaf1b77a
14 changed files with 838 additions and 29 deletions
+123 -1
View File
@@ -1,14 +1,28 @@
import pytest
from django.conf import settings
from rest_framework.exceptions import ValidationError
from accounts.models import User
from math_life.models import Story, StoryChoice, StoryRun, StoryVersion
from math_life.models import (
Story,
StoryChoice,
StoryMark,
StoryRun,
StoryVersion,
)
from math_life.services import (
get_run_payload,
make_choice,
score_mathbti,
start_story,
validate_story_content,
)
from math_life.story_v12 import (
EASTER_EGGS,
MARK_DEFINITIONS,
V12_IDENTITY_SCORES,
build_v12_story,
)
@pytest.fixture
@@ -142,3 +156,111 @@ def test_make_choice_负数索引必须拒绝且存档不变(story_setup):
run = StoryRun.objects.get(id=started["run_id"])
assert run.current_node == "start"
assert StoryChoice.objects.count() == 0
def v12_story_document():
import json
path = settings.PROJECT_ROOT / "docs" / "信仰者线_story.json"
return build_v12_story(json.loads(path.read_text(encoding="utf-8")))
def test_v12_story_包含20印记且完全移除旧数值体系():
document = v12_story_document()
acquired = {
code
for node in document["nodes"].values()
for choice in node.get("choices", [])
for code in choice.get("effects", {}).get("marks", [])
}
effect_keys = {
key
for node in document["nodes"].values()
for choice in node.get("choices", [])
for key in choice.get("effects", {})
}
assert len(MARK_DEFINITIONS) == 20
assert acquired == {item["code"] for item in MARK_DEFINITIONS}
assert not {"xinzhi", "shuli", "xiayi"} & effect_keys
assert validate_story_content(document) == []
def test_v12_数学家五维评分均有高分且不使用过低分():
assert len(V12_IDENTITY_SCORES) == 16
for scores in V12_IDENTITY_SCORES.values():
assert set(scores) == {"眼光", "人文", "侦探", "建模", "联结"}
assert min(scores.values()) >= 75
assert max(scores.values()) >= 90
@pytest.mark.django_db
def test_v12_story_印记永久收集_彩蛋开放并按领域分发结局():
user = User.objects.create_user(
username="marks_story_user",
password="StrongPass_2026",
nickname="印记玩家",
)
story = Story.objects.create(slug="marks-story", title="印记人生")
document = v12_story_document()
StoryVersion.objects.create(
story=story,
version=2,
is_published=True,
content=document,
)
started = start_story(user, story)
first = make_choice(
run_id=started["run_id"],
user=user,
choice_index=0,
idempotency_key="mark-first",
)
assert "prime_margin" in first["state"]["marks"]
assert StoryMark.objects.filter(
user=user,
story=story,
code="prime_margin",
).exists()
run = StoryRun.objects.get(id=started["run_id"])
gauss = next(item for item in EASTER_EGGS if item["code"] == "gauss")
run.current_node = gauss["trigger"]
run.state = {
"marks": gauss["required_marks"],
"easter_eggs": [],
"mark_counts": {
"number_theory": 2,
"algebraic_geometry": 0,
"analysis": 0,
"applied_mathematics": 0,
},
}
run.save(update_fields=["current_node", "state"])
payload = get_run_payload(run)
assert any(choice["special"] for choice in payload["node"]["choices"])
run.current_node = "c8_noether_merge"
run.state = {
"marks": ["epsilon_promise", "late_draft", "limit_definition"],
"easter_eggs": [],
"mark_counts": {
"number_theory": 0,
"algebraic_geometry": 0,
"analysis": 3,
"applied_mathematics": 0,
},
}
run.save(update_fields=["current_node", "state"])
ending = make_choice(
run_id=run.id,
user=user,
choice_index=0,
idempotency_key="analysis-ending",
)
assert ending["status"] == StoryRun.Status.COMPLETED
assert ending["current_node"] == "ending_analysis"
assert ending["chapter"] == {}
assert ending["collection"]