Merge branch 'review/off-queue-lessons'
This commit is contained in:
@@ -30,7 +30,8 @@
|
|||||||
|
|
||||||
## Критерий готовности
|
## Критерий готовности
|
||||||
|
|
||||||
- Снежным утром класс наполняется позже, чем ясным; ленивый по-прежнему ближе к звонку
|
- Снежным утром `AppearAt` раньше на `commuteSnowMinutes` (запас на дорогу, не более медленная
|
||||||
|
ходьба); ленивый по-прежнему ближе к звонку
|
||||||
- `dotnet test` проходит
|
- `dotnet test` проходит
|
||||||
|
|
||||||
## Стоп
|
## Стоп
|
||||||
|
|||||||
@@ -1,7 +1,37 @@
|
|||||||
# Проверенные фазы «Вне очереди»
|
# Проверенные фазы «Вне очереди»
|
||||||
|
|
||||||
Разделы ниже — отдельные проходы. Чужой раздел не затирать. Уроки 48, 50–53 этот журнал
|
Разделы ниже — отдельные проходы. Чужой раздел не затирать.
|
||||||
не закрывает.
|
|
||||||
|
## Связность урока. Учитель, навык, тепло, учебник, дорога
|
||||||
|
|
||||||
|
- **Фазы:** 48, 50–53
|
||||||
|
- **Проверен на:** `aa20534`, 2026-08-20
|
||||||
|
- **Пути:** `src/HSchool.Ai/LessonLearning.cs`, `src/HSchool.Ai/DayPlan.cs`, `src/HSchool.Simulation/LessonLearningSystem.cs`, `src/HSchool.Simulation/OutdoorWeather.cs`, `src/HSchool.Simulation/PresenceSystem.cs`, `src/HSchool.Simulation/PersonLog.cs`, `src/HSchool.Simulation/School.cs`, `src/HSchool.Content/PeopleDefs.cs`, `src/HSchool.Content/PeopleDefValidator.cs`, `src/HSchool.Server/mods/core/defs/behavior/rules.jsonc`, `src/HSchool.Server/mods/core/localizations/ru.jsonc`, `src/HSchool.Server/mods/core/localizations/en.jsonc`, `tests/HSchool.Ai.Tests/LessonLearningTests.cs`, `tests/HSchool.Ai.Tests/WalkingTests.cs`, `tests/HSchool.Simulation.Tests/LessonTeacherPresentTests.cs`, `tests/HSchool.Simulation.Tests/WeatherTests.cs`, `tests/HSchool.Content.Tests/BehaviorDefTests.cs`, `docs/protocol.md`, `docs/design/off-queue/lesson-consequences.md`, `docs/design/07-inventory/inventory.md`
|
||||||
|
- **Итог:** обещания фаз 48 и 50–53 совпадают с кодом; сокет не бампили (`ProtocolConstants.Version` / `PROTOCOL_VERSION` = 9). Дописаны тесты на действие учителя, тепло без нужды, снег в плане дня и ленивого на снегу. Критерий фазы 53 «класс наполняется позже» поправлен на `AppearAt` раньше (запас на дорогу). DX 49/54/55 и портреты не трогались.
|
||||||
|
|
||||||
|
### Фаза 48
|
||||||
|
|
||||||
|
Рост только если назначенный учитель стоит в `lesson.RoomId` (путь пуст, остаток 0). Нет id в мире — как отсутствующий. Лог `lesson-no-teacher` один раз на предмет в сутки, `thingDef` — предмет, подпись из каталога. Голод по-прежнему режет, когда учитель на месте. Действие учителя урок не отменяет — это проверяет `TeacherActionInRoom_StillGains`.
|
||||||
|
|
||||||
|
### Фаза 50
|
||||||
|
|
||||||
|
`TeacherFactor` — та же кривая, что `NeedFactor` (0 → 0.25, 100 → 1). Навык — среднее по `SubjectDef.skills`, нет ключа — `Range.Min`. Учителя нет в кабинете — рост нулевой.
|
||||||
|
|
||||||
|
### Фаза 51
|
||||||
|
|
||||||
|
`Gain` умножает `NeedFactor(голод) × NeedFactor(тепло)`; нет ключа `Warmth` — как 1. Лог `lesson-cold` один раз на предмет, только если учитель в кабинете. В `inventory.md` штраф рядом с голодом.
|
||||||
|
|
||||||
|
### Фаза 52
|
||||||
|
|
||||||
|
Полный рост только при учебнике этого предмета в сумке; шкафчик и дом — `lessonNoTextbookFactor` (ваниль 0.5). Пак без поля — 0.5. Лог `lesson-no-textbook` один раз на предмет.
|
||||||
|
|
||||||
|
### Фаза 53
|
||||||
|
|
||||||
|
`DayPlans.Build` берёт уже посчитанные минуты; симуляция подставляет `OutdoorWeather.ExtraCommuteMinutes`. Ваниль 3 / 6; пак без полей — 0. Лога нет. `HSchool.Ai` не знает `Precipitation`.
|
||||||
|
|
||||||
|
### Открытое
|
||||||
|
|
||||||
|
Планы дня не пересобираются, если осадки сменились после первой сборки этого утра — так записано («в момент сборки»). Учитель, стоящий в своём кабинете, тоже получает `Gain` по скиллам предмета (`Duty.LessonsToday` для штата); фазы это не запрещают.
|
||||||
|
|
||||||
## Портреты. Модели, слои промпта, LoRA и embeddings
|
## Портреты. Модели, слои промпта, LoRA и embeddings
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
| Срез 9. Этапы C–D — конфликт, присутствие, речь и romance | `19554d4` | [09-social](09-social/reviewed.md) |
|
| Срез 9. Этапы C–D — конфликт, присутствие, речь и romance | `19554d4` | [09-social](09-social/reviewed.md) |
|
||||||
| Срез 10. Этап A — страховка | `e2e7d30` | [10-craft](10-craft/reviewed.md) |
|
| Срез 10. Этап A — страховка | `e2e7d30` | [10-craft](10-craft/reviewed.md) |
|
||||||
| Срез 10. Этапы B–C — работник, карточка и дамп | `f4f726a` | [10-craft](10-craft/reviewed.md) |
|
| Срез 10. Этапы B–C — работник, карточка и дамп | `f4f726a` | [10-craft](10-craft/reviewed.md) |
|
||||||
|
| Связность урока (вне очереди) 48, 50–53 | `aa20534` | [off-queue](off-queue/reviewed.md) |
|
||||||
| Портреты (вне очереди) 60–61 | `5e23d1f` | [off-queue](off-queue/reviewed.md) |
|
| Портреты (вне очереди) 60–61 | `5e23d1f` | [off-queue](off-queue/reviewed.md) |
|
||||||
| Портреты (вне очереди) 62 | `d093ea3` | [off-queue](off-queue/reviewed.md) |
|
| Портреты (вне очереди) 62 | `d093ea3` | [off-queue](off-queue/reviewed.md) |
|
||||||
| Вне очереди. DX 49/54/55 | `ee452b2` | [off-queue](off-queue/reviewed.md) |
|
| Вне очереди. DX 49/54/55 | `ee452b2` | [off-queue](off-queue/reviewed.md) |
|
||||||
|
|||||||
@@ -162,6 +162,30 @@ public class WalkingTests
|
|||||||
Assert.True(implied.Comes);
|
Assert.True(implied.Comes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Lazy_AppearsCloserToTheBell_OnSnow()
|
||||||
|
{
|
||||||
|
var (catalog, map) = Fixtures.Vanilla();
|
||||||
|
var walks = WalkGraph.Build(catalog, map);
|
||||||
|
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 9, "Russia", TuesdayLesson);
|
||||||
|
var lazy = roster.People.First(person => person.IsStudent && person.Traits.Contains("Lazy"));
|
||||||
|
var schoolClass = roster.Classes.First(row => row.Id == lazy.ClassId);
|
||||||
|
var keen = roster.People.First(person =>
|
||||||
|
person.IsStudent && person.ClassId == lazy.ClassId && !person.Traits.Contains("Lazy"));
|
||||||
|
var table = new Timetable(
|
||||||
|
[new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1)],
|
||||||
|
[]);
|
||||||
|
var extra = catalog.BehaviorRules!.CommuteSnowMinutes;
|
||||||
|
var lazyPlan = DayPlans.Build(
|
||||||
|
catalog, walks, lazy, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra);
|
||||||
|
var keenPlan = DayPlans.Build(
|
||||||
|
catalog, walks, keen, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra);
|
||||||
|
|
||||||
|
Assert.True(lazyPlan.Comes);
|
||||||
|
Assert.True(keenPlan.Comes);
|
||||||
|
Assert.True(lazyPlan.AppearAt > keenPlan.AppearAt);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SameSeedAndSnowMinutes_YieldTheSameAppearAt()
|
public void SameSeedAndSnowMinutes_YieldTheSameAppearAt()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,6 +102,24 @@ public class LessonTeacherPresentTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TeacherActionInRoom_StillGains()
|
||||||
|
{
|
||||||
|
var (school, homeroom, pupilId, teacherId) = StaffedMath();
|
||||||
|
using (school)
|
||||||
|
{
|
||||||
|
AdvanceTo(school, LessonStart);
|
||||||
|
SetPlace(school, pupilId, homeroom);
|
||||||
|
SetPlace(school, teacherId, homeroom);
|
||||||
|
SetActivity(school, teacherId, "EatLunch");
|
||||||
|
var before = SkillOf(school, pupilId, "Mathematics");
|
||||||
|
|
||||||
|
LessonLearningSystem.Apply(school, 45);
|
||||||
|
|
||||||
|
Assert.True(SkillOf(school, pupilId, "Mathematics") > before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void HungryStillGainsLess_WhenTeacherIsPresent()
|
public void HungryStillGainsLess_WhenTeacherIsPresent()
|
||||||
{
|
{
|
||||||
@@ -221,6 +239,30 @@ public class LessonTeacherPresentTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MissingWarmthNeed_GainsLikeFullWarmth()
|
||||||
|
{
|
||||||
|
var (school, homeroom, pupilId, teacherId) = StaffedMath();
|
||||||
|
using (school)
|
||||||
|
{
|
||||||
|
AdvanceTo(school, LessonStart);
|
||||||
|
SetPlace(school, pupilId, homeroom);
|
||||||
|
SetPlace(school, teacherId, homeroom);
|
||||||
|
ClearNeed(school, pupilId, "Warmth");
|
||||||
|
var start = SkillOf(school, pupilId, "Mathematics");
|
||||||
|
LessonLearningSystem.Apply(school, 45);
|
||||||
|
var missingGain = SkillOf(school, pupilId, "Mathematics") - start;
|
||||||
|
|
||||||
|
SetSkill(school, pupilId, "Mathematics", start);
|
||||||
|
SetNeed(school, pupilId, "Warmth", 1f);
|
||||||
|
LessonLearningSystem.Apply(school, 45);
|
||||||
|
var warmGain = SkillOf(school, pupilId, "Mathematics") - start;
|
||||||
|
|
||||||
|
Assert.True(missingGain > 0);
|
||||||
|
Assert.Equal(missingGain, warmGain, precision: 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Cold_DoesNotLog_WhenTeacherIsAbsent()
|
public void Cold_DoesNotLog_WhenTeacherIsAbsent()
|
||||||
{
|
{
|
||||||
@@ -394,6 +436,20 @@ public class LessonTeacherPresentTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetActivity(School school, string personId, string actionId)
|
||||||
|
{
|
||||||
|
var query = new QueryDescription().WithAll<PersonIdentity, PersonActivity>();
|
||||||
|
school.World.Query(
|
||||||
|
in query,
|
||||||
|
(ref PersonIdentity identity, ref PersonActivity activity) =>
|
||||||
|
{
|
||||||
|
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
activity = new PersonActivity(actionId, Thing: null, RemainingMinutes: 10f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetNeed(School school, string personId, string need, float value)
|
private static void SetNeed(School school, string personId, string need, float value)
|
||||||
{
|
{
|
||||||
var query = new QueryDescription().WithAll<PersonIdentity, PersonNeeds>();
|
var query = new QueryDescription().WithAll<PersonIdentity, PersonNeeds>();
|
||||||
@@ -438,6 +494,20 @@ public class LessonTeacherPresentTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ClearNeed(School school, string personId, string need)
|
||||||
|
{
|
||||||
|
var query = new QueryDescription().WithAll<PersonIdentity, PersonNeeds>();
|
||||||
|
school.World.Query(
|
||||||
|
in query,
|
||||||
|
(ref PersonIdentity identity, ref PersonNeeds needs) =>
|
||||||
|
{
|
||||||
|
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
needs.Values.Remove(need);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void ClearSkill(School school, string personId, string skill)
|
private static void ClearSkill(School school, string personId, string skill)
|
||||||
{
|
{
|
||||||
var query = new QueryDescription().WithAll<PersonIdentity, PersonSkills>();
|
var query = new QueryDescription().WithAll<PersonIdentity, PersonSkills>();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using HSchool.Ai;
|
||||||
using HSchool.Content;
|
using HSchool.Content;
|
||||||
using HSchool.People;
|
using HSchool.People;
|
||||||
|
using HSchool.Schedule;
|
||||||
|
|
||||||
namespace HSchool.Simulation.Tests;
|
namespace HSchool.Simulation.Tests;
|
||||||
|
|
||||||
@@ -73,6 +75,24 @@ public class WeatherTests
|
|||||||
Assert.Equal(0, new OutdoorWeather(-5f, Precipitation.Snow).ExtraCommuteMinutes(null));
|
Assert.Equal(0, new OutdoorWeather(-5f, Precipitation.Snow).ExtraCommuteMinutes(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SnowOnStreet_PlansAppearEarlierThanClear()
|
||||||
|
{
|
||||||
|
// Tick would SyncWeather and overwrite ForceWeather; Apply is the plan-build path.
|
||||||
|
using var clear = ComingOnTuesday();
|
||||||
|
using var snow = ComingOnTuesday();
|
||||||
|
clear.ForceWeather(new OutdoorWeather(8f, Precipitation.None));
|
||||||
|
snow.ForceWeather(new OutdoorWeather(-5f, Precipitation.Snow));
|
||||||
|
PresenceSystem.Apply(clear, 1);
|
||||||
|
PresenceSystem.Apply(snow, 1);
|
||||||
|
|
||||||
|
var pupilId = FirstComingPupil(clear);
|
||||||
|
var extra = clear.Catalog!.BehaviorRules!.CommuteSnowMinutes;
|
||||||
|
Assert.Equal(6, extra);
|
||||||
|
Assert.True(clear.Plans[pupilId].Comes);
|
||||||
|
Assert.Equal(clear.Plans[pupilId].AppearAt!.Value.AddMinutes(-extra), snow.Plans[pupilId].AppearAt);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SkipEmpty_SetsMondayMorningWeather_NotSaturdays()
|
public void SkipEmpty_SetsMondayMorningWeather_NotSaturdays()
|
||||||
{
|
{
|
||||||
@@ -103,6 +123,25 @@ public class WeatherTests
|
|||||||
Assert.True(room < 18f, $"walls only, got {room}");
|
Assert.True(room < 18f, $"walls only, got {room}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly DateTime TuesdayMorning = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
private static School ComingOnTuesday()
|
||||||
|
{
|
||||||
|
var school = Open(TuesdayMorning);
|
||||||
|
var schoolClass = school.Roster!.Classes.First();
|
||||||
|
school.SetTimetable(new Timetable(
|
||||||
|
[new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1)],
|
||||||
|
[]));
|
||||||
|
return school;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FirstComingPupil(School school)
|
||||||
|
{
|
||||||
|
var pupil = school.Roster!.People.First(person =>
|
||||||
|
person.IsStudent && person.ClassId == school.Timetable!.Lessons[0].ClassId);
|
||||||
|
return pupil.Id;
|
||||||
|
}
|
||||||
|
|
||||||
private static School Open(DateTime start)
|
private static School Open(DateTime start)
|
||||||
{
|
{
|
||||||
var (catalog, map) = VanillaMap();
|
var (catalog, map) = VanillaMap();
|
||||||
|
|||||||
Reference in New Issue
Block a user