Skip to content

Ice Palace - #481

Open
samtcifihi wants to merge 23 commits into
AbstractPlay:developfrom
samtcifihi:ice-palace-2026-09-18
Open

samtcifihi wants to merge 23 commits into
AbstractPlay:developfrom
samtcifihi:ice-palace-2026-09-18

Conversation

@samtcifihi

Copy link
Copy Markdown
Contributor

claude and others added 20 commits September 18, 2026 23:20
The rules require the builder to use the maximum possible number of Yard
pyramids, a number the tabletop rules leave to player agreement because
searching the possibilities by hand is impractical. Computing it exactly is
cheap, because founding is unbounded: a stack founded next to a stack of
colour c is itself topped by c, so it chains outwards forever. The problem
reduces to which colours can be got onto an outward-facing top, which is a
small search over (enabled colours, open large tops, open medium tops).

The plan is replayed against the real building code and the achieved length
is what gets reported, so the number is always one the builder can reach.
Over-estimating would wedge the build phase; under-estimating only relaxes it.

Cross-checked against exhaustive search over every legal build order for 45
combinations of Palace shape and Yard contents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Three to six players compete to win hands into a temporary common Yard, and
the winner of each hand builds what they won into the permanent Ice Palace,
whose size rule runs opposite to the Yard's. Scores come from whoever owns
the pyramid on top of each Palace stack.

Extends GameBaseSequenced: the winner of a hand passes to close it and then
acts again to build, so one seat acts twice before the seat cycle completes.

Notable rules decisions, all recorded in comments:
  - Stacking ignores colour, per "Any color pyramid may be added to a stack
    if it is bigger than the current top pyramid". The French translation's
    "de la couleur d'un joueur" is a translation artifact.
  - A new stack must match one adjacent top, not all of them, per "adjacent
    to any existing stack ... the same color as the adjacent top pyramid".
  - Adjacency is orthogonal, per the rules' own grid illustration.
  - A hand ends on as many consecutive passes as there are players, so the
    last placer gets the option to extend rather than close.
  - The rules never say how the first pyramid enters an empty Palace, so it
    goes anywhere.
  - Replenishing is checked per size, since draws are made by size.
  - Building never auto-commits; the builder decides who scores what and
    wants to rearrange before it becomes permanent.

Hands are public. The rules never address concealment either way, and it is
a presentation decision that does not touch state or legality.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Covers the game description and name, the validation messages for both
building codes and the build-minimum prompts, the sidebar statuses, and the
place/remove move-log lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The Yard and the Ice Palace are both unbounded grids that have to be visible
at once, which no single bounded board style supports. Freespace places them
side by side, each padded by a ring of empty cells so there is somewhere to
click when founding a stack on the frontier.

Stacks are drawn bottom to top with each pyramid rising slightly above the
one below, so the true order is readable. That matters here because the Yard
stacks bigger over smaller and the Palace does the reverse, and a nesting
convention would read one of them backwards.

Freespace reports board clicks as continuous coordinates rather than grid
indices, so handleClick inverts the same layout; clicks on a drawn pyramid
carry their own cell id instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Verified against the real renderer for the first time, which turned up two
defects that typechecking alone could not:

  - Freespace coordinates are in the renderer's own cellsize units, and
    pieces are scaled to it. The layout used 1 unit per cell instead of 50,
    so every glyph drew at roughly its native size and the whole canvas came
    out as one black rectangle.
  - Rows are now pitched further apart than columns. A full three-pyramid
    stack rises two risers above its cell, which at the old pitch overlapped
    whatever stood in the row above.

Board labels are structured render labels resolved by the front end rather
than English baked into the engine.

Confirmed by rendering a position through the real FreespaceRenderer: the
Palace stacks read large to small upward and the Yard reads small to large,
so the two opposite size rules are visually distinguishable, and Black and
White are legible against the board.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Two changes, both about matching house style.

Volcano's look comes from the `pyramid-flat-*` glyphs, which are the side-on
triangle with size pips along the base, not `pyramid-up-*`, which is the
top-down square with an X through it. Switching over reads far better here,
and it makes the two building codes obvious at a glance: a Palace stack
tapers upward while a Yard stack is top-heavy.

Those glyphs carry no full-cell sizing box, so the renderer normalises all
three sizes to the same footprint and small, medium and large come out
identical. Scaling each legend entry back to the true Icehouse height ratios
restores the distinction.

The rules helpers and build solver move into icepalace.ts, and the two test
files become one, since splitting a game across modules is not the norm here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
A player holding only a small Black has no legal placement and must pass, and
that happens often. The front drives both auto-passing and the Pass button off
the enumerated move list, so declaring no-moves suppressed the list and left
those players typing "pass" by hand every time.

Hands enumerate cheaply, so the flag was only ever needed for the build phase.
Builds are still not enumerated, but rather than offer a single worked build in
the move dropdown, where it would read as the only legal arrangement, the build
phase offers nothing to pick from and is entered by clicking. Choosing the
arrangement is the point of the phase. randomMove supplies a real build so bots
and random play keep working.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
…idebar

Freespace is not needed here. One stacking-3D squares board carries both
structures: the Palace on the left and, while a hand is being played, the Yard
to its right, each padded by a ring of empty cells so founding has somewhere to
click. During the build the Yard has already been taken up into the stock, so
only the Palace is on the board. Coordinate labels are hidden, since the two
regions do not share a coordinate space.

The current player's hand, or the builder's stock, is a pieces area, which is
where a pyramid is picked from; every hand is also listed in the status panel
as pyramids. That replaces the player-stashes mechanism.

Board clicks now arrive as grid row and column, which map back through the
same layout the render uses. Clicking a pyramid in the pieces area yields a
bare pyramid id; validation previously rejected anything without a target
cell, so a selection could never survive as a partial move. Both phases now
accept a trailing bare pyramid as a partial move and prompt for a space.

Verified by rendering through the renderer's public entry point, which also
validates the JSON against its schema.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Legend keys double as SVG element ids, and the renderer later looks them up
with querySelector("#" + key). A pyramid id such as "1M" starts with a digit,
which is not a valid selector in a real browser; svgdom accepted it, so the
headless render passed and only a Chromium render exposed it. Keys now carry
a letter prefix, and clicks from the pieces area, which hand back the key,
accept it with or without the prefix.

Verified by rendering the position in Chromium through the renderer's own
playground harness, which also confirmed the pieces area draws below the
board with the current player's owner mark and hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The -3D pyramid glyphs are drawn for Volcano's nests: all three sizes share an
apex and grow downward from it, so a medium's base sits 15 units lower than a
small's and a large's 30 lower. Volcano lists nests small-first and its default
rise of 0.15 exactly cancels those depths, which is why its stacks look planted.
Ice Palace stacks pyramids on one another, with a large at the bottom of every
Palace tower, so the bottom piece sat up to 30 units low in its cell.

Each size now carries a legend nudge that lifts it by its extra depth, so the
bottom of any stack sits on the cell centre whatever its size. With bases
aligned, the default rise would leave every apex in a stack coincident and the
piece drawn last would paint over the tips below it; a rise of 0.25 keeps each
tip visible without pulling a stack apart into separate pieces.

Checked in Chromium against lone pyramids of each size and both stacking
orders, and against Volcano's own playground example.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The previous fix nudged each size up to stand on the cell and widened the rise
to 0.25 so tips would not coincide. That was a compromise: a single rise cannot
suit both a medium on a small and a small on a large, so pieces either floated
or overlapped a little. Volcano does not tune the rise at all. It spends "-"
placeholders, which the renderer skips but still counts, so that with the
default rise of 0.15 one array index is exactly one glyph step: a small's base
sits on the cell at index 0, a medium's at index 1, a large's at index 2, and
their heights are one, two and three steps. A piece whose base should sit
`top` steps up goes at index `top + height - 1`.

stackColumn lays every stack out that way, so a Palace tower is
["-", "-", L, "-", M, S] and a Yard stack [S, "-", M, "-", "-", L], each
pyramid standing on the apex of the one below it in either order, and a lone
large is ["-", "-", L]. The nudges are gone and the rise is pinned to 0.15,
which the layout depends on.

Verified in Chromium against both stacking orders and lone pyramids of every
size, and pinned by a test on the emitted columns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The previous layout lifted each pyramid by the full height of the one beneath
it, so a medium over a large sat three rise-steps up and tips ended up thirty
units apart, which read as gaps. Volcano's rule is simpler: each piece sits one
index above the last, and "-" placeholders are spent only to keep a base from
sinking below the ground. A small stands on the cell at index 0, a medium at
index 1 and a large at index 2, so a tower of large, medium, small is
["-", "-", L, M, S] and a Yard stack of small, medium, large needs no
placeholders at all. Tips nest fifteen units apart, with no gaps, matching
Volcano's own positions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The board already grew and shrank with the structures each render, but it did
so after every single placement. stacking-3D refits its perspective to the
board's width and height, so each change of shape moved every piece, and the
Yard, which starts empty every hand, lurched from one cell to three to four
over the first few placements.

Each structure's region now always covers a five-cell box centred on the
origin, where the first pyramid goes, so a structure can grow in any direction
for a while before the board changes shape, and past that the board grows only
in the direction the structure did. With the ring of padding that gives a 7x7
floor for the Palace alone during the build, and 16x7 during a hand with two
empty columns between the Palace and the Yard. A click anywhere in an empty
region places the lead at the origin, keeping it in the middle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
A pyramid picked from the hand or stock now marks every cell it could
legally go. The renderer's own dots annotation is not projected onto the
stacking-3D board, so each mark is a small glyph placed where the
pyramid would land instead.

The status panel puts a button, a plain piece in the seventh colour, in
front of the hand of whoever leads the current hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The front draws a status-panel glyph from its `glyph` field, as
Catapult and Entropy supply it, not from a legend glyph's `name`. The
hands and the button were using the legend shape and would have drawn
blank.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
The "expanding" display uses the stacking-expanding renderer: the board
is seen from above, each stack's pyramids drawn translucently over one
another, and hovering a cell lays its stack out beside the board through
renderColumn. That renderer draws no pieces area, so the current hand,
or the stock during the build, is offered below the board as a local
stash of nests, one per colour. The board keeps the same footprint and
padding as the perspective display, and the legal-cell dots work in
both.

Rotation is turned off for both displays with custom-rotation, since
neither renderer can turn the board and the front would otherwise offer
buttons that do nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
…lainly

When a hand ends, the event log now says who builds, how many pyramids
that takes, and one legal way to build them, quoted so it can be pasted
into the move box. The build messages state the computed maximum
without qualification; validation keeps accepting that many or more, in
case an arrangement the solver did not find exists.

The move log moves to the structured collector, and an implementation
note records the at-least-one matching neighbour decision and how White
and Black match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
@Perlkonig

Copy link
Copy Markdown
Member

You're going mad with power! 🤪 Will take a look when I can.

@Perlkonig

Copy link
Copy Markdown
Member

Please do not edit the locale-src sidecars, and do not insert placeholder translations in other languages. If you want to submit the Esperanto translations with the PR, that's fine, but let the automated translation system deal with the rest.

Reverts the seeded "Ice Palace" names in de, fr, it and es-US and their
locale-src sidecars, as requested in review.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
@samtcifihi

samtcifihi commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

I'll send a commit with those things removed and update the instructions I'm giving Claude to avoid them.

EDIT: that should be the above commit

@Perlkonig

Copy link
Copy Markdown
Member

A couple more points, with my insertions in square brackets. Only the first one is essential. The rest are recommendations. Once the first one is fixed, I'll merge and you can test on Dev. Thanks for tackling this!

Gaps and risks

  1. move() and complete === -1 (recommended fix)
    move() only checks valid, not complete when partial is false. Many games guard (like Morphos).

Recommendation: Mirror morphos.ts: throw VALIDATION_FAILSAFE when !partial && result.complete === -1.

  1. Sequenced export / getRounds() untested
    No tests for turnModel(): "sequenced", sparse rows, or “pass ends hand → same seat builds” export shape. Worth a small test file patterned on test/games/sequencedTurnModel.test.ts or a few getRounds() expectations after a scripted hand+build.

  2. Serialization test is shallow
    Round-trip via clone() after two Yard moves checks yard/hands but not palace, phase, buildMin, lead, or serialize()/JSON.parse with Map revival. One fuller stack-state test would match the Map-heavy state.

  3. recordExportExclude
    announce results are pushed every hand end. Default export only strips eog/winners. If published gamerecords should not carry build cheat-sheets, override recordExportExclude() to include "announce" (chat log can keep them).

[You may skip this for now if you like. We have a larger issue where generated records have more results fields than they really need. Ideally, each game would exclude results that are trivially inferred from the notation.]

  1. Metadata polish
    bggid: "61898" (wiki/BGG) missing — optional but common.
    people: no coder / AP id entry (many games list implementer).

[I'm happy to do this when I merge.]

@samtcifihi

Copy link
Copy Markdown
Contributor Author

I'll tackle those in couple of hours once my usage limits in Claude reset. That will be more valuable long term for me submitting PRs with less problems in the future than if you just fixed them now.

samtcifihi and others added 2 commits September 21, 2026 20:23
move() now throws VALIDATION_FAILSAFE when a non-partial move validates
as incomplete, as Morphos does. The build announcement is excluded from
published records while staying in the chat log. Adds the BGG id, and
tests for the sequenced turn model and record export, a full serialized
round trip mid-build with Map revival, and the failsafe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Lists the BoardGameGeek page and the archived original rules alongside
the Icehouse wiki entry, and credits the implementer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Wtg37k4AQMgCQjK1fHuuy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants