Sample outdoor weather from the climate preset so people can freeze and the clock can show it.

Protocol v8 adds tenths of a °C and precipitation to the clock frame; warmth drains from insulation versus place temperature.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 03:49:40 +03:00
co-authored by Cursor
parent d8f4958167
commit 8e7ab46e79
40 changed files with 1008 additions and 39 deletions
+13 -1
View File
@@ -33,6 +33,8 @@ public readonly record struct ServerPongMessage(long ClientTimeMs, uint ServerTi
/// interpreted as UTC — the game calendar has no time zone.
/// <paramref name="SkipAllowed"/> is the server's verdict; the client must not recompute it.
/// <paramref name="SkipTargetUnixMs"/> is 0 when skip is refused.
/// <paramref name="TemperatureTenths"/> is outdoor °C × 10. <paramref name="Precipitation"/> is
/// <see cref="PrecipitationKind"/>.
/// </summary>
public readonly record struct ServerClockMessage(
int SchoolId,
@@ -40,7 +42,17 @@ public readonly record struct ServerClockMessage(
bool Running,
byte SpeedIndex,
bool SkipAllowed = false,
long SkipTargetUnixMs = 0);
long SkipTargetUnixMs = 0,
short TemperatureTenths = 0,
byte Precipitation = 0);
/// <summary>Outdoor precipitation on the clock frame. Below 0 °C the same weather roll is snow.</summary>
public static class PrecipitationKind
{
public const byte None = 0;
public const byte Rain = 1;
public const byte Snow = 2;
}
/// <summary>The open school no longer exists (deleted from another tab); the client returns to the menu.</summary>
public readonly record struct ServerSchoolGoneMessage(int SchoolId);
+8
View File
@@ -49,6 +49,14 @@ public ref struct PacketReader(ReadOnlySpan<byte> buffer)
return value;
}
public short ReadInt16()
{
EnsureAvailable(sizeof(short));
var value = BinaryPrimitives.ReadInt16LittleEndian(_buffer[_position..]);
_position += sizeof(short);
return value;
}
public int ReadInt32()
{
EnsureAvailable(sizeof(int));
+7
View File
@@ -52,6 +52,13 @@ public ref struct PacketWriter(Span<byte> buffer)
_position += byteCount;
}
public void WriteInt16(short value)
{
EnsureRoom(sizeof(short));
BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value);
_position += sizeof(short);
}
public void WriteInt32(int value)
{
EnsureRoom(sizeof(int));
+14 -2
View File
@@ -13,7 +13,7 @@ public static class ProtocolCodec
/// Largest <em>fixed-size</em> frame this codec produces. Variable map snapshots and
/// presence frames use <see cref="ProtocolConstants.MaxMessageSize"/> instead.
/// </summary>
public const int MaxFrameSize = 24;
public const int MaxFrameSize = 27;
public static int WriteHello(Span<byte> destination, in ClientHelloMessage message)
{
@@ -99,6 +99,8 @@ public static class ProtocolCodec
writer.WriteByte(message.SpeedIndex);
writer.WriteByte(message.SkipAllowed ? (byte)1 : (byte)0);
writer.WriteInt64(message.SkipTargetUnixMs);
writer.WriteInt16(message.TemperatureTenths);
writer.WriteByte(message.Precipitation);
return writer.Position;
}
@@ -317,7 +319,17 @@ public static class ProtocolCodec
var speedIndex = reader.ReadByte();
var skipAllowed = reader.ReadByte() != 0;
var skipTarget = reader.ReadInt64();
return new ServerClockMessage(schoolId, gameTime, running, speedIndex, skipAllowed, skipTarget);
var temperatureTenths = reader.ReadInt16();
var precipitation = reader.ReadByte();
return new ServerClockMessage(
schoolId,
gameTime,
running,
speedIndex,
skipAllowed,
skipTarget,
temperatureTenths,
precipitation);
}
public static ServerSchoolGoneMessage ReadSchoolGone(ReadOnlySpan<byte> source)
+1 -1
View File
@@ -4,7 +4,7 @@ namespace HSchool.Protocol;
public static class ProtocolConstants
{
/// <summary>Bumped on every breaking change to the binary layout.</summary>
public const byte Version = 7;
public const byte Version = 8;
/// <summary>Upper bound for a single WebSocket frame accepted by the server.</summary>
public const int MaxMessageSize = 8 * 1024;