Merge branch 'review/slice-7-bc-recheck'
This commit is contained in:
@@ -58,48 +58,67 @@
|
|||||||
берёт первую страну каталога — это create, не сейв.
|
берёт первую страну каталога — это create, не сейв.
|
||||||
- Этап B–C в этом проходе не проверялся.
|
- Этап B–C в этом проходе не проверялся.
|
||||||
|
|
||||||
## Срез 7. Этап B–C — климат, жизнь одежды и правила
|
## Срез 7. Этап B–C — климат, жизнь одежды и правила (перепроверка)
|
||||||
|
|
||||||
- **Фазы:** 32–37
|
- **Фазы:** 32–37
|
||||||
- **Проверен на:** `5dcdee6`, 2026-08-20
|
- **Проверен на:** `505bcc6`, 2026-08-20 — **перепроверка** после `5dcdee6`
|
||||||
- **Пути:** `src/HSchool.Simulation/{WeatherSampler,WarmthDecay,PlaceClimate,ApparelDresser,ApparelWear,MorningDress,PersonLogBrowser}.cs`,
|
- **Пути:** `src/HSchool.Simulation/{WeatherSampler,WarmthDecay,PlaceClimate,ApparelDresser,ApparelWear,MorningDress,PersonLogBrowser,School}.cs`,
|
||||||
`src/HSchool.People/LockerAssigner.cs`, `src/HSchool.Content/{Appropriateness,ClimatePreset,DressRules}.cs`,
|
`src/HSchool.People/LockerAssigner.cs`, `src/HSchool.Content/{Appropriateness,ClimatePreset,DressRules}.cs`,
|
||||||
`src/HSchool.Ai/ApparelGoals.cs`, `src/HSchool.Protocol`, `src/HSchool.Server/Api/{SchoolEndpoints,DressRulesModels}.cs`,
|
`src/HSchool.Ai/ApparelGoals.cs`, `src/HSchool.Protocol`, `src/HSchool.Server/Api/{SchoolEndpoints,DressRulesModels}.cs`,
|
||||||
`src/HSchool.Server/Game/{PersonCardReader,SchoolWorker}.cs`, `src/HSchool.Client/src/{ui/dressRulesPanel.ts,ui/managementPanel.ts,format/weather.ts,net/api.ts}`,
|
`src/HSchool.Server/Game/{PersonCardReader,SchoolWorker.Tick}.cs`, `src/HSchool.Client/src/{ui/dressRulesPanel.ts,ui/managementPanel.ts,format/weather.ts,net/api.ts}`,
|
||||||
`docs/protocol.md`, `tests/HSchool.Simulation.Tests`, `tests/HSchool.People.Tests/LockerAssignerTests.cs`,
|
`docs/protocol.md`, `tests/HSchool.Simulation.Tests`, `tests/HSchool.People.Tests/LockerAssignerTests.cs`,
|
||||||
`tests/HSchool.Content.Tests/{ClimatePresetTests,AppropriatenessTests,ChangingRoomMapTests}.cs`,
|
`tests/HSchool.Content.Tests/{ClimatePresetTests,AppropriatenessTests,ChangingRoomMapTests}.cs`,
|
||||||
`tests/HSchool.AppHost.Tests/{PersonLogApiTests,DressRulesApiTests}.cs`, `tests/HSchool.Protocol.Tests`,
|
`tests/HSchool.AppHost.Tests/{PersonLogApiTests,DressRulesApiTests}.cs`, `tests/HSchool.Protocol.Tests`,
|
||||||
`src/HSchool.Client/src/ui/{personCard,dressRulesPanel,managementPanel}.test.ts`
|
`src/HSchool.Client/src/ui/{personCard,dressRulesPanel,managementPanel}.test.ts`
|
||||||
- **Итог:** фазы 32–37 подтверждены; дописано 5 тестов симуляции; исправлены 2 бага переодевания и 2
|
- **Итог:** обещания 32–37 после дрейфа (сессия, разговоры, сплит воркера и карточки, протокол v9)
|
||||||
регрессии тестов (не протокол)
|
в целом держатся; дописан тест «куртка не телепортируется»; три PE-теста прошлого прохода
|
||||||
|
красные на текущем main — открыто
|
||||||
|
|
||||||
Что подтверждено:
|
Что подтверждено:
|
||||||
|
|
||||||
- **32–36:** погода детерминирована; skip выставляет утро; кадр часов v8 (27 байт) в трёх местах;
|
- **32:** тот же пресет+сид+время → та же улица; январь холоднее июля; ниже нуля снег;
|
||||||
`Warmth` и техник; шкафчики; износ и утренняя замена; уместность и PE; вкладки карточки и HTTP-лог.
|
skip ставит утро понедельника, не субботу; naked на морозе теряет `Warmth`, дома возвращает;
|
||||||
- **37:** вкладка «Правила» в «Управлении»; четыре комбобокса; GET/POST `dress-rules` с pending;
|
кадр часов 27 байт (tenths + precipitation) в `ProtocolCodec` / `protocol.ts` / `docs/protocol.md`;
|
||||||
`unknown-form`/`unknown-color` → `400`; правило с завтра; `ColorAllowed` для `whiteTopBlackBottom`;
|
клиент рисует кадр, не считает месяц (`weather.test.ts`); Hello со старой версией отваливается.
|
||||||
`dressRulesPanel.test.ts` — combobox без API до Apply, ошибка текстом; `ManagementPanel` не ломает штат
|
Версия протокола сейчас 9 (круги разговоров), раскладка часов не менялась.
|
||||||
и сетку.
|
- **33:** кабинет без техника теплее улицы и холоднее, чем с техником; в июле техник держит
|
||||||
|
ниже улицы; две раздевалки, граф связен; 10 шкафчиков — старшие девочки; мальчик не в
|
||||||
|
девичьей; выпускник слот не держит; `ChangingSpot` (4) отдельно от шкафчиков.
|
||||||
|
- **34:** час на теле снижает прочность на `ApparelWearPerHour`; сумка и шкафчик нет;
|
||||||
|
skip через ночь = прожитая ночь; подпись порогов с сервера, клиент не считает.
|
||||||
|
- **35:** «без яркого» утром; короткая форма и возраст; PE: доходят до своей раздевалки
|
||||||
|
(`PeBreak_PupilReachesOwnChangingRoom`); мальчик не в женской; без шкафчика снятое в сумку;
|
||||||
|
мороз — `Outer`, в июле шубы нет; pending правило с завтра. Куртка при смене погоды не
|
||||||
|
телепортируется (урок держит, длинная перемена — ходьба и смена).
|
||||||
|
- **36:** лог не в карточке; страницы не пересекаются; 404 unknown-person; после 6:00 вчера
|
||||||
|
пусто; вкладка «Одежда» без таблицы лога; «Сейчас» вне школы — «дома».
|
||||||
|
- **37:** GET дефолт обычная/без яркого; POST unknown-form/color → 400; вкладка не ломает штат
|
||||||
|
и сетку; четыре комбобокса одежды на месте (речь среза 9 рядом, не вместо).
|
||||||
|
|
||||||
Дописано (32–36, прошлый проход):
|
Дописано:
|
||||||
|
|
||||||
- `AppropriatenessSimulationTests.MorningDress_StagesPeKitForTodaysLesson`
|
- `AppropriatenessSimulationTests.WeatherDrop_WalksToChangingRoom_DoesNotTeleportJacket`
|
||||||
- `AppropriatenessSimulationTests.PeBreak_CompletesChangeBeforeLesson`
|
- `personCard.test.ts` — `hasLocker: true` показывает «есть шкафчик»
|
||||||
- `AppropriatenessSimulationTests.PeLesson_WearsPeThenReturnsToEveryday`
|
|
||||||
- `AppropriatenessSimulationTests.AfterPeBreak_ModeExpectsEveryday`
|
|
||||||
- `PresenceTests.PhysicalEducationBeforeLesson_GoesToChangingRoomOnTheBreak`
|
|
||||||
|
|
||||||
Исправлено:
|
Исправлено:
|
||||||
|
|
||||||
- `PresenceSystem`: ходьба по duty не обрывает активный `ChangeClothes`.
|
- (кода симуляции PE не меняли: попытка починить звонок/очередь в раздевалке не закрыла
|
||||||
- `ApparelPresence.ModeAt`: после перемены following PE — режим everyday.
|
красные PE-тесты; правки откатили)
|
||||||
- `managementPanel.test.ts`: панель штата скрывается через `hidden`, не удаляется из DOM.
|
|
||||||
- `PortraitApiTests.GetSwarmUiSettings_ReturnsPresets`: URL `/api/settings/swarmui` (CS1503).
|
Открыто:
|
||||||
- `dressRulesPanel.ts`: неиспользуемый импорт `clear` — TS6133 на `npm run build`.
|
|
||||||
|
- `PeBreak_CompletesChangeBeforeLesson`, `PeLesson_WearsPeThenReturnsToEveryday`,
|
||||||
|
`AfterPeBreak_ModeExpectsEveryday` красные уже на `505bcc6` без правок этого прохода.
|
||||||
|
В 9:24 ученик в своей раздевалке, к 9:30 — в зале в футболке: 4 `ChangingSpot`, звонок
|
||||||
|
(duty 10 > одежда 6) уводит до `ChangeClothes`; праздные в комнате не перерешаются, когда
|
||||||
|
место освобождается. Это дрейф после разговоров/конфликта, не чинили молча в срезе 9.
|
||||||
|
- Короткая уместность на вкладке «Одежда» по-прежнему прочерк: сервер не шлёт подпись
|
||||||
|
(`«к уроку»` / `«холодно»` / `«не по форме»`). Молча расширять HTTP ревью не стало.
|
||||||
|
- Короткой перемены (10 мин) с дальнего кабинета не хватает дойти и переодеться; тест погоды
|
||||||
|
идёт через длинную после 3 урока.
|
||||||
|
|
||||||
Замечено рядом:
|
Замечено рядом:
|
||||||
|
|
||||||
- `PhysicalEducationBeforeLesson_…` не гоняет полный цикл «зал с формой» — это в
|
- `dressRulesPanel` рисует ещё комбобоксы речи (срез 9) — четыре комбобокса одежды на месте.
|
||||||
`AppropriatenessSimulationTests.PeLesson_…`.
|
- Кадр часов остался 27 байт при PROTOCOL_VERSION 9.
|
||||||
- Портреты/SwarmUI — вне среза 7, но `PortraitApiTests` чинился, чтобы AppHost-сборка не падала.
|
- AppHost `PersonLogApiTests` / `DressRulesApiTests` в этом проходе не гонялись (хост).
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
| Срез 5. Жизнь школы | `1118c59` | [05-ai](05-ai/reviewed.md) |
|
| Срез 5. Жизнь школы | `1118c59` | [05-ai](05-ai/reviewed.md) |
|
||||||
| Срез 6. Фундамент | `1c63357` | [06-foundation](06-foundation/reviewed.md) |
|
| Срез 6. Фундамент | `1c63357` | [06-foundation](06-foundation/reviewed.md) |
|
||||||
| Срез 7. Этап A — данные и гардероб | `5468852` | [07-inventory](07-inventory/reviewed.md) |
|
| Срез 7. Этап A — данные и гардероб | `5468852` | [07-inventory](07-inventory/reviewed.md) |
|
||||||
| Срез 7. Этап B–C — климат, жизнь одежды и правила | `5dcdee6` | [07-inventory](07-inventory/reviewed.md) |
|
| Срез 7. Этап B–C — климат, жизнь одежды и правила | `505bcc6` | [07-inventory](07-inventory/reviewed.md) |
|
||||||
| Срез 8. Сессия и темп | `7100065` | [08-session](08-session/reviewed.md) |
|
| Срез 8. Сессия и темп | `7100065` | [08-session](08-session/reviewed.md) |
|
||||||
| Срез 9. Этапы A–B — мнения и разговоры | `b38d0bb` | [09-social](09-social/reviewed.md) |
|
| Срез 9. Этапы A–B — мнения и разговоры | `b38d0bb` | [09-social](09-social/reviewed.md) |
|
||||||
| Срез 9. Этапы C–D — конфликт, присутствие, речь и romance | `19554d4` | [09-social](09-social/reviewed.md) |
|
| Срез 9. Этапы C–D — конфликт, присутствие, речь и romance | `19554d4` | [09-social](09-social/reviewed.md) |
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ describe('renderPersonCard', () => {
|
|||||||
expect(root.textContent).toContain(t('peopleHomeCount', { n: 2 }));
|
expect(root.textContent).toContain(t('peopleHomeCount', { n: 2 }));
|
||||||
expect(root.textContent).toContain('Учебник · Математика');
|
expect(root.textContent).toContain('Учебник · Математика');
|
||||||
|
|
||||||
|
root.replaceChildren();
|
||||||
|
renderPersonCard(root, card({ hasLocker: true }), () => {}, options({ tab: 'carry' }));
|
||||||
|
expect(root.textContent).toContain(t('peopleLockerYes'));
|
||||||
|
expect(root.textContent).not.toContain(t('peopleLockerNone'));
|
||||||
|
|
||||||
tabButton(root, 'now').click();
|
tabButton(root, 'now').click();
|
||||||
expect(root.querySelector('[data-card-tab="now"]')?.hasAttribute('hidden')).toBe(false);
|
expect(root.querySelector('[data-card-tab="now"]')?.hasAttribute('hidden')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -422,7 +422,18 @@ public sealed class School : IDisposable
|
|||||||
public void SyncWeather(bool force)
|
public void SyncWeather(bool force)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
var next = EvaluateWeather();
|
CommitWeather(EvaluateWeather(), force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test hook: the vanilla diurnal never crosses <c>outerBelowC</c> during a school day, so a
|
||||||
|
/// mid-day frost has to be injected to prove jackets walk to the locker room instead of
|
||||||
|
/// appearing on the body.
|
||||||
|
/// </summary>
|
||||||
|
internal void ForceWeather(OutdoorWeather weather) => CommitWeather(weather, force: true);
|
||||||
|
|
||||||
|
private void CommitWeather(OutdoorWeather next, bool force)
|
||||||
|
{
|
||||||
if (force || next.Tenths != Weather.Tenths || next.Precipitation != Weather.Precipitation)
|
if (force || next.Tenths != Weather.Tenths || next.Precipitation != Weather.Precipitation)
|
||||||
{
|
{
|
||||||
var before = Weather;
|
var before = Weather;
|
||||||
|
|||||||
@@ -249,6 +249,70 @@ public class AppropriatenessSimulationTests
|
|||||||
Assert.Equal(0, BrightWornCount(school));
|
Assert.Equal(0, BrightWornCount(school));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WeatherDrop_WalksToChangingRoom_DoesNotTeleportJacket()
|
||||||
|
{
|
||||||
|
var (school, pupilId) = StaffedClassWithLocker();
|
||||||
|
using (school)
|
||||||
|
{
|
||||||
|
MorningDress.Apply(school);
|
||||||
|
AdvanceTo(school, new DateTime(2012, 4, 3, 8, 30, 0, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
var here = school.CapturePresence()
|
||||||
|
.Where(row => row.NodeId is not null)
|
||||||
|
.Select(row => row.PersonId)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
Assert.Contains(pupilId, here);
|
||||||
|
var pupil = school.Roster!.People.Single(person => person.Id == pupilId);
|
||||||
|
var items = pupil.Items
|
||||||
|
.Select(item =>
|
||||||
|
item.Location == ItemLocations.Worn
|
||||||
|
&& school.Catalog!.Things[item.Def].Layers.Contains(ApparelLayers.Outer, StringComparer.Ordinal)
|
||||||
|
? item with { Location = ItemLocations.Locker }
|
||||||
|
: item)
|
||||||
|
.ToList();
|
||||||
|
items.Add(new InventoryItem("Jacket", "Blue", 1f, ItemLocations.Locker));
|
||||||
|
ReplaceItems(pupil, items);
|
||||||
|
|
||||||
|
school.ForceWeather(new OutdoorWeather(-8f, Precipitation.Snow));
|
||||||
|
pupil = school.Roster.People.Single(person => person.Id == pupil.Id);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
pupil.Items,
|
||||||
|
item => item.Location == ItemLocations.Worn
|
||||||
|
&& school.Catalog!.Things[item.Def].Layers.Contains(ApparelLayers.Outer, StringComparer.Ordinal));
|
||||||
|
|
||||||
|
AdvanceTo(school, new DateTime(2012, 4, 3, 8, 32, 0, DateTimeKind.Utc));
|
||||||
|
pupil = school.Roster.People.Single(person => person.Id == pupil.Id);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
pupil.Items,
|
||||||
|
item => item.Location == ItemLocations.Worn
|
||||||
|
&& school.Catalog!.Things[item.Def].Layers.Contains(ApparelLayers.Outer, StringComparer.Ordinal));
|
||||||
|
var duringLesson = school.CapturePresence().Single(item => item.PersonId == pupil.Id);
|
||||||
|
Assert.NotEqual(
|
||||||
|
pupil.Female ? "girls-changing-room" : "boys-changing-room",
|
||||||
|
duringLesson.NodeId);
|
||||||
|
|
||||||
|
AdvanceTo(school, new DateTime(2012, 4, 3, 11, 08, 0, DateTimeKind.Utc));
|
||||||
|
pupil = school.Roster.People.Single(person => person.Id == pupil.Id);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
pupil.Items,
|
||||||
|
item => item.Location == ItemLocations.Worn
|
||||||
|
&& school.Catalog!.Things[item.Def].Layers.Contains(ApparelLayers.Outer, StringComparer.Ordinal));
|
||||||
|
var row = school.CapturePresence().Single(item => item.PersonId == pupil.Id);
|
||||||
|
var room = pupil.Female ? "girls-changing-room" : "boys-changing-room";
|
||||||
|
Assert.True(
|
||||||
|
row.NodeId == room || row.Path.Contains(room, StringComparer.Ordinal),
|
||||||
|
$"expected a walk toward {room}, sat at {row.NodeId} path [{string.Join(',', row.Path)}]");
|
||||||
|
|
||||||
|
AdvanceTo(school, new DateTime(2012, 4, 3, 11, 20, 0, DateTimeKind.Utc));
|
||||||
|
pupil = school.Roster.People.Single(person => person.Id == pupil.Id);
|
||||||
|
Assert.Contains(
|
||||||
|
pupil.Items,
|
||||||
|
item => item.Location == ItemLocations.Worn
|
||||||
|
&& school.Catalog!.Things[item.Def].Layers.Contains(ApparelLayers.Outer, StringComparer.Ordinal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WhiteTopBlackBottom_AfterMorning_WhiteShirtsOnStudents()
|
public void WhiteTopBlackBottom_AfterMorning_WhiteShirtsOnStudents()
|
||||||
{
|
{
|
||||||
@@ -317,6 +381,29 @@ public class AppropriatenessSimulationTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (School School, string PupilId) StaffedClassWithLocker()
|
||||||
|
{
|
||||||
|
var (catalog, map) = Vanilla();
|
||||||
|
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", TuesdayMorning);
|
||||||
|
roster = LockerAssigner.Apply(catalog, map, roster);
|
||||||
|
roster = DressGenerator.EnsureRoster(catalog, roster, schoolSeed: 1, TuesdayMorning);
|
||||||
|
var pool = ApplicantPool.Create(catalog, roster, schoolSeed: 1, "Russia", TuesdayMorning);
|
||||||
|
var pupil = roster.People.First(person => person.IsStudent && person.LockerRoomId is not null);
|
||||||
|
var schoolClass = roster.Classes.First(row => row.PupilIds.Contains(pupil.Id));
|
||||||
|
var school = School.Create(1, "Погода одежда", TuesdayMorning, catalog, map);
|
||||||
|
school.InstallPeople(roster, seed: 1, "Russia", pool, climatePresetId: "TemperateContinental");
|
||||||
|
school.SetTimetable(new Timetable(
|
||||||
|
[
|
||||||
|
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1),
|
||||||
|
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 2),
|
||||||
|
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 3),
|
||||||
|
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 4),
|
||||||
|
],
|
||||||
|
[]));
|
||||||
|
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
|
||||||
|
return (school, pupil.Id);
|
||||||
|
}
|
||||||
|
|
||||||
private static (School School, string PupilId) StaffedPeClass(bool forceBoy = false)
|
private static (School School, string PupilId) StaffedPeClass(bool forceBoy = false)
|
||||||
{
|
{
|
||||||
var (catalog, map) = Vanilla();
|
var (catalog, map) = Vanilla();
|
||||||
|
|||||||
Reference in New Issue
Block a user