Lost Lily/Grid ToolkitDocs 1.0
Table of Contents

Display action feedback

When to use

Display action feedback when a preview or committed result should highlight affected or invalid cells. Visual feedback is presentation state: it does not mutate the board or advance its revision.

Before you start

Choose the logical request in Choose a built-in action. Connect a GridBoardView to a ready renderer with a compatible intent renderer.

Procedure

Publish preview feedback

Preview the action, then give the same action and result to the view's GridVisualState.

Excerpt — publish a preview result. Tested using public APIs.

GridActionResult preview = GridActionRunner.Preview(state, action);
boardView.VisualState.SetActionPreview(action, preview);

Apply and replace the channel

Apply only after a successful preview. SetActionResult replaces the action feedback channel with the committed result.

Excerpt — apply and publish the result. Tested using public APIs.

GridActionResult applied = GridActionRunner.Apply(state, action);
boardView.VisualState.SetActionResult(action, applied);

Choose the feedback route

Feedback Use
Generic affected or invalid cells SetActionPreview or SetActionResult
Placement footprint SetPlacementPreview
Inventory move, rotate, sort, transfer, or merge Inventory Visualization bridge
Project-specific semantics IGridActionVisualIntentContributor

A custom contributor that returns no valid intents falls back to generic action feedback. Inventory quantity changes also use generic action feedback because Inventory has no quantity-specific intent ID.

GridVisualIntentIds defines the Stable built-in intent and channel IDs:

Purpose Intent ID Channel ID
Hover grid.hover grid.visual.channel.hover
Selection grid.selection grid.visual.channel.selection
Valid placement grid.placement.valid grid.visual.channel.placement
Invalid placement grid.placement.invalid grid.visual.channel.placement
Affected action cells grid.action.affected grid.visual.channel.action-affected
Invalid action cells grid.action.invalid grid.visual.channel.action-invalid
Project overlay grid.overlay Project-owned channel

GridInventoryVisualIntentIds defines these Preview intent IDs:

Operation Accepted intent Rejected intent
Move grid.inventory.move grid.inventory.move.invalid
Transfer grid.inventory.transfer grid.inventory.transfer.invalid
Merge grid.inventory.merge grid.inventory.merge.invalid
Rotate grid.inventory.rotate grid.inventory.rotate.invalid
Sort grid.inventory.sort grid.inventory.sort.invalid

The current Inventory bridge emits grid.inventory.move, grid.inventory.rotate, and grid.inventory.sort channels for same-board actions.

Cross-board operations use one channel per participant:

  • transfers use grid.inventory.transfer.source and grid.inventory.transfer.destination;
  • merges use grid.inventory.merge.source and grid.inventory.merge.destination.

These channel strings are current Preview-tier behavior rather than Stable built-in constants.

Clear temporary feedback

Excerpt — clear the action-feedback channels. Tested using public APIs.

if (boardView == null)
    boardView = GetComponent<GridBoardView>();
if (boardView == null)
    return;

boardView.VisualState.ClearIntentChannel(GridVisualIntentIds.ActionAffectedChannel);
boardView.VisualState.ClearIntentChannel(GridVisualIntentIds.ActionInvalidChannel);
Complete tested example
using LostLily.GridToolkit.Actions;
using LostLily.GridToolkit.Authoring;
using LostLily.GridToolkit.Board;
using LostLily.GridToolkit.Topology.Square;
using LostLily.GridToolkit.Visualization;
using UnityEngine;

namespace GridToolkit.Tutorials
{
    [RequireComponent(typeof(GridBoardAuthoring), typeof(GridBoardView))]
    public sealed class ActionFeedbackExample : MonoBehaviour
    {
        [SerializeField] private GridBoardAuthoring boardAuthoring;
        [SerializeField] private GridBoardView boardView;

        private void Start()
        {
            AddCell();
        }

        public GridActionResult AddCell()
        {
            return AddCell(new SquareGridCoordinate(8, 0));
        }

        public GridActionResult AddCell(SquareGridCoordinate coordinate)
        {
            if (boardAuthoring == null)
                boardAuthoring = GetComponent<GridBoardAuthoring>();
            if (boardView == null)
                boardView = GetComponent<GridBoardView>();
            GridBoardState state = boardAuthoring.State;
            if (state == null || state.IsDisposed || boardView == null)
            {
                Debug.LogError("Action feedback needs a ready board owner and view.", this);
                return null;
            }

            IGridAction action = GridActions.AddCell(coordinate);

            GridActionResult preview = GridActionRunner.Preview(state, action);
            boardView.VisualState.SetActionPreview(action, preview);

            if (!preview.Success)
            {
                Debug.LogError(preview.FormatDiagnostics(), this);
                return preview;
            }

            GridActionResult applied = GridActionRunner.Apply(state, action);
            boardView.VisualState.SetActionResult(action, applied);

            if (!applied.Success)
            {
                Debug.LogError(applied.FormatDiagnostics(), this);
                return applied;
            }

            Debug.Log($"Added cell {coordinate}. Board revision {state.Revision}.", this);
            return applied;
        }

        public void ClearFeedback()
        {
            if (boardView == null)
                boardView = GetComponent<GridBoardView>();
            if (boardView == null)
                return;

            boardView.VisualState.ClearIntentChannel(GridVisualIntentIds.ActionAffectedChannel);
            boardView.VisualState.ClearIntentChannel(GridVisualIntentIds.ActionInvalidChannel);
        }
    }
}

The complete component calls AddCell() from Start to add (8, 0) beside the tutorial's 8 by 8 board. AddCell(coordinate) uses the same workflow for another target. Remove Start when your own input should supply the coordinate. It returns the rejected preview or committed action result; the view receives that result for feedback. ClearFeedback() removes the two action channels.

Result

Preview feedback appears without a commit. Apply publishes the committed result, and the renderer keeps each feedback node under a stable key that includes its owning channel. Replacing or clearing that channel updates or removes only its own retained nodes.

Troubleshooting

Symptom Check
The action succeeds but nothing appears Assign an intent renderer whose required capabilities match the active backend.
Old feedback remains visible Clear the exact owner channel or the relevant transient visual category.
Custom feedback disappears Return at least one valid intent and keep its channel ID stable.