Merge branch 'review/slice-8-recheck'
This commit is contained in:
@@ -32,3 +32,32 @@
|
||||
- `AddDataProtection()` без ключей рядом с `saves/`. Рестарт того же профиля Windows куки, скорее всего, не сбрасывает; новый контейнер — может.
|
||||
- Текст фазы 39 пишет `limit-reached`; живой код и `protocol.md` — `school-limit-reached` (так было до среза). Не менялось.
|
||||
- Бесхозная карточка показывает «Хозяин: —», не голый прочерк. `GET /api/schools/{id}` из дизайна нет: `mine`/`owner` живут в списке меню.
|
||||
|
||||
## Перепроверка. Срез 8. Сессия и темп
|
||||
|
||||
- **Фазы:** 38–40
|
||||
- **Проверен на:** `5490cfb`, 2026-08-20
|
||||
- **Пути:** `src/HSchool.Server/{Session,Api/SessionEndpoints.cs,Api/SchoolAccess.cs,Game/SchoolOwnership.cs,Game/UserStore.cs,Game/SchoolStore.cs,Net/ClientRegistry.cs,Net/GameSocketHandler.cs,Game/GameLoopService.cs}`, `src/HSchool.Simulation/{ClockSpeed.cs,GameClock.cs,School.cs,SimulationOptions.cs}`, `src/HSchool.Client/src/{ui/sessionGate.ts,ui/mainMenu.ts,ui/gameScreen.ts,ui/schoolCard.ts,net/protocol.ts,net/api.ts,main.ts}`, `docs/protocol.md`, `../../design/08-session/session.md`, `tests/HSchool.AppHost.Tests/{SessionApiTests.cs,SchoolOwnerApiTests.cs,GameSocketTests.cs}`, `tests/HSchool.Simulation.Tests/{ClockTempoTests.cs,GameClockTests.cs}`, `tests/HSchool.Server.Tests/SchoolStoreTests.cs`, `src/HSchool.Client/src/ui/{sessionGate,gameScreen,schoolCard,mainMenu}.test.ts`
|
||||
- **Итог:** перепроверка после дрейфа с `1c3ae9a`. Обещания 38–40 держатся; `users.json` больше не читается как сейв школы; бесхозная карточка показывает голый прочерк. Следующий срез не брался.
|
||||
|
||||
Что подтверждено:
|
||||
|
||||
- Дрейф после `1c3ae9a`: `UserStore.FileName` открыт для `SchoolStore.LoadAll` (пропуск `users.json`); `GameLoopService` / `School.Tick` получили речь и talk-circle на страйде тяжёлых систем. Сессия, хозяин, таблица скоростей и `TryClaimUserName` не разъехались.
|
||||
- Все тесты из списков фаз 38–40 на месте: сессия 401/200, `users.json` и регистр, `name-online`, сокет без куки и второй сокет `PolicyViolation`, клиентский пароль без сетки; два игрока по две школы / `server-full` / гость 403 и `SetRunning` / бесхозная в `others`; ×1 = 1 минута, индексы 3/4, страйд ×5/×10, индекс 5, пять кнопок.
|
||||
- Протокол на проводе сейчас v9 (срез 9, talk-circle); раскладка Hello/Welcome/`SetSpeed` и смысл `MaxSchools` не менялись. `HSchool.Simulation` без ASP.NET. Воркер 20 Гц, потолок догона 5.
|
||||
|
||||
Дописано:
|
||||
|
||||
- `ClockTempoTests.LowSpeed_EveryImpulseRunsHeavySystems` — ×½ и ×2 каждый импульс, не только ×1
|
||||
- `schoolCard.test.ts` — бесхозная карточка с голым прочерком, не «Хозяин: —»
|
||||
|
||||
Исправлено:
|
||||
|
||||
- Бесхозная карточка оборачивала «—» в шаблон «Хозяин: {owner}». Меню теперь отдаёт уже готовую подпись: чужая — «Хозяин: Bob», бесхозная — «—».
|
||||
|
||||
Открыто:
|
||||
|
||||
- `POST /api/session` с другим именем при живой куке перезаписывает сессию без `DELETE`. Дизайн: «Без выхода второе имя с той же куки не взять». Кода ошибки нет — новый HTTP-код молча не вводился.
|
||||
- `AddDataProtection()` без ключей рядом с `saves/`. Рестарт того же профиля Windows куки, скорее всего, не сбрасывает; новый контейнер — может.
|
||||
- Текст фазы 39 пишет `limit-reached`; живой код и `protocol.md` — `school-limit-reached` (так было до среза). Не менялось.
|
||||
- `GET /api/schools/{id}` из дизайна нет: `mine`/`owner` живут в списке меню (`schoolFromList`).
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
| Срез 6. Фундамент | `1c63357` | [06-foundation](06-foundation/reviewed.md) |
|
||||
| Срез 7. Этап A — данные и гардероб | `5468852` | [07-inventory](07-inventory/reviewed.md) |
|
||||
| Срез 7. Этап B–C — климат, жизнь одежды и правила | `5dcdee6` | [07-inventory](07-inventory/reviewed.md) |
|
||||
| Срез 8. Сессия и темп | `1c3ae9a` | [08-session](08-session/reviewed.md) |
|
||||
| Срез 8. Сессия и темп | `5490cfb` | [08-session](08-session/reviewed.md) |
|
||||
| Срез 9. Этапы A–B — мнения и разговоры | `b38d0bb` | [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) |
|
||||
|
||||
@@ -37,8 +37,11 @@ describe('MainMenu ownership', () => {
|
||||
const orphan = menu.element.querySelector('[data-school-id="3"]');
|
||||
expect((foreign?.querySelector('.button--danger') as HTMLElement).hidden).toBe(true);
|
||||
expect((orphan?.querySelector('.button--danger') as HTMLElement).hidden).toBe(false);
|
||||
expect(foreign?.textContent).toContain('Bob');
|
||||
expect(orphan?.textContent).toContain(t('schoolOwnerless'));
|
||||
expect(foreign?.querySelector('.card__owner')?.textContent).toBe(t('schoolOwner', { owner: 'Bob' }));
|
||||
expect(orphan?.querySelector('.card__owner')?.textContent).toBe(t('schoolOwnerless'));
|
||||
expect(orphan?.querySelector('.card__owner')?.textContent).not.toBe(
|
||||
t('schoolOwner', { owner: t('schoolOwnerless') }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -181,7 +181,8 @@ export class MainMenu {
|
||||
const menuSchool: MenuSchool = { ...other, mine: false };
|
||||
this.patchCard(menuSchool, 'other', this.othersGrid, {
|
||||
canDelete: other.owner === null,
|
||||
ownerLabel: other.owner ?? t('schoolOwnerless'),
|
||||
ownerLabel:
|
||||
other.owner === null ? t('schoolOwnerless') : t('schoolOwner', { owner: other.owner }),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { t } from '../i18n/strings.ts';
|
||||
import { SchoolCard } from './schoolCard.ts';
|
||||
|
||||
describe('SchoolCard ownership', () => {
|
||||
@@ -21,12 +22,41 @@ describe('SchoolCard ownership', () => {
|
||||
onOpen: () => {},
|
||||
onDelete: () => {},
|
||||
canDelete: false,
|
||||
ownerLabel: 'Bob',
|
||||
ownerLabel: t('schoolOwner', { owner: 'Bob' }),
|
||||
},
|
||||
);
|
||||
|
||||
document.body.append(card.element);
|
||||
|
||||
expect((card.element.querySelector('.button--danger') as HTMLElement).hidden).toBe(true);
|
||||
expect(card.element.querySelector('.card__owner')?.textContent).toBe(t('schoolOwner', { owner: 'Bob' }));
|
||||
});
|
||||
|
||||
it('shows a bare dash on an ownerless card, not Owner: —', () => {
|
||||
const card = new SchoolCard(
|
||||
{
|
||||
id: 3,
|
||||
name: 'Orphan',
|
||||
gameTime: '2012-03-31T06:00:00.000Z',
|
||||
running: true,
|
||||
speedIndex: 1,
|
||||
seed: 2,
|
||||
mine: false,
|
||||
owner: null,
|
||||
},
|
||||
{
|
||||
onOpen: () => {},
|
||||
onDelete: () => {},
|
||||
canDelete: true,
|
||||
ownerLabel: t('schoolOwnerless'),
|
||||
},
|
||||
);
|
||||
|
||||
document.body.append(card.element);
|
||||
|
||||
expect(card.element.querySelector('.card__owner')?.textContent).toBe(t('schoolOwnerless'));
|
||||
expect(card.element.querySelector('.card__owner')?.textContent).not.toBe(
|
||||
t('schoolOwner', { owner: t('schoolOwnerless') }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,6 +68,7 @@ export class SchoolCard {
|
||||
this.update(this.school);
|
||||
}
|
||||
|
||||
/** `label` is already painted: `Owner: Bob` or a bare dash — do not wrap it again. */
|
||||
setOwnerLabel(label: string | null): void {
|
||||
if (label === null) {
|
||||
this.owner.hidden = true;
|
||||
@@ -76,7 +77,7 @@ export class SchoolCard {
|
||||
}
|
||||
|
||||
this.owner.hidden = false;
|
||||
this.owner.textContent = t('schoolOwner', { owner: label });
|
||||
this.owner.textContent = label;
|
||||
}
|
||||
|
||||
update(school: MenuSchool): void {
|
||||
|
||||
@@ -23,18 +23,21 @@ public class ClockTempoTests
|
||||
Assert.Equal(1d, school.LastHeavyGameMinutes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtX1_EveryImpulseRunsHeavySystems()
|
||||
[Theory]
|
||||
[InlineData(0, 0.5)]
|
||||
[InlineData(1, 1d)]
|
||||
[InlineData(2, 2d)]
|
||||
public void LowSpeed_EveryImpulseRunsHeavySystems(int speedIndex, double expectedMinutes)
|
||||
{
|
||||
using var school = School.Create(1, "Каждый импульс", Start);
|
||||
school.Clock.SpeedIndex = 1;
|
||||
school.Clock.SpeedIndex = speedIndex;
|
||||
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
school.Tick(OneTwentiethOfASecond, GameMinutesPerRealSecond);
|
||||
}
|
||||
|
||||
Assert.Equal(Start.AddMinutes(1), school.Clock.Time);
|
||||
Assert.Equal(Start.AddMinutes(expectedMinutes), school.Clock.Time);
|
||||
Assert.Equal(20, school.HeavySystemsInvocations);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user